From c1bcb0f6c783093391d8878f822c821eb7675609 Mon Sep 17 00:00:00 2001 From: akildemir Date: Wed, 27 Mar 2024 14:46:15 +0300 Subject: [PATCH 01/18] add genesis liquidity implementation --- Cargo.lock | 24 ++ substrate/genesis-liquidity/pallet/Cargo.toml | 46 +++ substrate/genesis-liquidity/pallet/LICENSE | 15 + substrate/genesis-liquidity/pallet/src/lib.rs | 271 ++++++++++++++++++ .../genesis-liquidity/primitives/Cargo.toml | 25 ++ .../genesis-liquidity/primitives/LICENSE | 21 ++ .../genesis-liquidity/primitives/src/lib.rs | 17 ++ substrate/in-instructions/pallet/Cargo.toml | 2 + substrate/in-instructions/pallet/src/lib.rs | 14 +- .../in-instructions/primitives/src/lib.rs | 10 + substrate/runtime/Cargo.toml | 2 + substrate/runtime/src/lib.rs | 7 + 12 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 substrate/genesis-liquidity/pallet/Cargo.toml create mode 100644 substrate/genesis-liquidity/pallet/LICENSE create mode 100644 substrate/genesis-liquidity/pallet/src/lib.rs create mode 100644 substrate/genesis-liquidity/primitives/Cargo.toml create mode 100644 substrate/genesis-liquidity/primitives/LICENSE create mode 100644 substrate/genesis-liquidity/primitives/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1ae1d4633..fde62b3a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7526,6 +7526,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "serai-genesis-liquidity-pallet" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serai-coins-pallet", + "serai-dex-pallet", + "serai-genesis-liquidity-primitives", + "serai-primitives", + "sp-std", +] + +[[package]] +name = "serai-genesis-liquidity-primitives" +version = "0.1.0" +dependencies = [ + "serai-primitives", +] + [[package]] name = "serai-in-instructions-pallet" version = "0.1.0" @@ -7536,6 +7558,7 @@ dependencies = [ "scale-info", "serai-coins-pallet", "serai-dex-pallet", + "serai-genesis-liquidity-pallet", "serai-in-instructions-primitives", "serai-primitives", "serai-validator-sets-pallet", @@ -7797,6 +7820,7 @@ dependencies = [ "scale-info", "serai-coins-pallet", "serai-dex-pallet", + "serai-genesis-liquidity-pallet", "serai-in-instructions-pallet", "serai-primitives", "serai-signals-pallet", diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml new file mode 100644 index 000000000..35551626d --- /dev/null +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "serai-genesis-liquidity-pallet" +version = "0.1.0" +description = "Genesis liquidity pallet for Serai" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity/pallet" +authors = ["Akil Demir "] +edition = "2021" +rust-version = "1.77" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.cargo-machete] +ignored = ["scale", "scale-info"] + +[lints] +workspace = true + + +[dependencies] +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } +frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } + +sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } + +dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } +coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } + +serai-primitives = { path = "../../primitives", default-features = false } +genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../primitives", default-features = false } + +[features] +std = [ + "frame-system/std", + "frame-support/std", + + "coins-pallet/std", + "dex-pallet/std", +] + +default = ["std"] \ No newline at end of file diff --git a/substrate/genesis-liquidity/pallet/LICENSE b/substrate/genesis-liquidity/pallet/LICENSE new file mode 100644 index 000000000..e091b1498 --- /dev/null +++ b/substrate/genesis-liquidity/pallet/LICENSE @@ -0,0 +1,15 @@ +AGPL-3.0-only license + +Copyright (c) 2024 Luke Parker + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License Version 3 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs new file mode 100644 index 000000000..f43df0b41 --- /dev/null +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -0,0 +1,271 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[allow(clippy::cast_possible_truncation, clippy::no_effect_underscore_binding)] +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_system::{pallet_prelude::*, RawOrigin}; + use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion}; + + use sp_std::{vec, collections::btree_map::BTreeMap}; + + use dex_pallet::{Pallet as Dex, Config as DexConfig}; + use coins_pallet::{ + primitives::{OutInstructionWithBalance, OutInstruction}, + Config as CoinsConfig, Pallet as Coins, AllowMint, + }; + + use serai_primitives::*; + pub use genesis_liquidity_primitives as primitives; + use primitives::*; + + /// LiquidityTokens Pallet as an instance of coins pallet. + pub type LiquidityTokens = coins_pallet::Pallet; + + #[pallet::config] + pub trait Config: + frame_system::Config + DexConfig + CoinsConfig + coins_pallet::Config + { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::error] + pub enum Error { + GenesisPeriodEnded, + AmountOverflowed, + NotEnoughLiquidity, + CanOnlyRemoveFullAmount, + } + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + GenesisLiquidityAdded { by: SeraiAddress, balance: Balance }, + GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance }, + GenesisLiquidityAddedToPool { coin1: Balance, coin2: Balance }, + EconomicSecurityReached { network: NetworkId }, + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::storage] + pub(crate) type Liquidity = + StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, SubstrateAmount, OptionQuery>; + + #[pallet::storage] + pub(crate) type LiquidityTokensPerAddress = + StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, SubstrateAmount, OptionQuery>; + + #[pallet::storage] + pub(crate) type EconomicSecurityReached = + StorageMap<_, Identity, NetworkId, BlockNumberFor, ValueQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_finalize(n: BlockNumberFor) { + // Distribute the genesis sri to pools after a month + if n > BLOCKS_PER_MONTH.into() { + // mint the SRI + Coins::::mint( + GENESIS_LIQUIDITY_ACCOUNT.into(), + Balance { coin: Coin::Serai, amount: Amount(GENESIS_SRI) }, + ) + .unwrap(); + + // get coin values & total + let mut account_values = BTreeMap::new(); + let mut pool_values = BTreeMap::new(); + let mut total_value: u64 = 0; + for coin in COINS { + // TODO: following line is just a place holder till we get the actual coin value + // in terms of btc. + let value = Dex::::security_oracle_value(coin).unwrap_or(Amount(0)).0; + account_values.insert(coin, vec![]); + let mut pool_amount: u64 = 0; + for (account, amount) in Liquidity::::iter_prefix(coin) { + pool_amount += amount; + let value_this_addr = amount * value; + account_values.get_mut(&coin).unwrap().push((account, value_this_addr)) + } + + let pool_value = pool_amount * value; + total_value += pool_value; + pool_values.insert(coin, (pool_amount, pool_value)); + } + + // add the liquidity per pool + for (coin, (amount, value)) in &pool_values { + let sri_amount = (GENESIS_SRI * value) / total_value; + let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); + Dex::::add_liquidity( + origin.into(), + *coin, + *amount, + sri_amount, + *amount, + sri_amount, + GENESIS_LIQUIDITY_ACCOUNT.into(), + ) + .unwrap(); + + // set liquidity tokens per account + let tokens = LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), *coin).0; + let mut total_tokens_this_coin: u64 = 0; + for (acc, value) in account_values.get(coin).unwrap() { + let liq_tokens_this_acc = (tokens * value) / pool_values.get(coin).unwrap().1; + total_tokens_this_coin += liq_tokens_this_acc; + LiquidityTokensPerAddress::::set(coin, acc, Some(liq_tokens_this_acc)); + } + assert_eq!(tokens, total_tokens_this_coin); + } + + // we shouldn't have any coin left in our account at this moment, including SRI. + for coin in COINS { + assert_eq!(Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin), Amount(0)); + } + } + + // we accept we reached economic security once we can mint smallest amount of a network's coin + for coin in COINS { + let existing = EconomicSecurityReached::::get(coin.network()); + if existing == 0u32.into() && + ::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) }) + { + EconomicSecurityReached::::set(coin.network(), n); + Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() }); + } + } + } + } + + impl Pallet { + /// Add genesis liquidity for the given account. All accounts that provide liquidity + /// will receive the genesis SRI according to their liquidity ratio. + pub fn add_coin_liquidity(account: PublicKey, balance: Balance) -> DispatchResult { + // check we are still in genesis period + if Self::genesis_ended() { + Err(Error::::GenesisPeriodEnded)?; + } + + // mint the coins + Coins::::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), balance)?; + + // save + let existing = Liquidity::::get(balance.coin, account).unwrap_or(0); + let new = existing.checked_add(balance.amount.0).ok_or(Error::::AmountOverflowed)?; + Liquidity::::set(balance.coin, account, Some(new)); + + Self::deposit_event(Event::GenesisLiquidityAdded { by: account.into(), balance }); + Ok(()) + } + + /// Remove the provided genesis liquidity for an account. If called pre-economic security era, + pub fn remove_coin_liquidity( + account: PublicKey, + balance: Balance, + out_address: ExternalAddress, + ) -> DispatchResult { + let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); + + // check we are still in genesis period + if Self::genesis_ended() { + // check user have enough to remove + let existing = LiquidityTokensPerAddress::::get(balance.coin, account).unwrap_or(0); + if balance.amount.0 > existing { + Err(Error::::NotEnoughLiquidity)?; + } + + // remove liquidity from pool + let prev_sri = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai); + let prev_coin = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin); + Dex::::remove_liquidity( + origin.clone().into(), + balance.coin, + balance.amount.0, + 1, + 1, + GENESIS_LIQUIDITY_ACCOUNT.into(), + )?; + let current_sri = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai); + let current_coin = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin); + + // burn the SRI if necessary + let mut sri = current_sri.0 - prev_sri.0; + let burn_sri_amount = (sri * + (GENESIS_SRI_TRICKLE_FEED - Self::blocks_since_ec_security(&balance.coin))) / + GENESIS_SRI_TRICKLE_FEED; + Coins::::burn( + origin.clone().into(), + Balance { coin: Coin::Serai, amount: Amount(burn_sri_amount) }, + )?; + sri -= burn_sri_amount; + + // transfer to owner + let coin_out = current_coin.0 - prev_coin.0; + Coins::::transfer( + origin.clone().into(), + account, + Balance { coin: balance.coin, amount: Amount(coin_out) }, + )?; + Coins::::transfer( + origin.into(), + account, + Balance { coin: Coin::Serai, amount: Amount(sri) }, + )?; + + // save + let existing = LiquidityTokensPerAddress::::get(balance.coin, account).unwrap_or(0); + let new = existing.checked_sub(balance.amount.0).ok_or(Error::::AmountOverflowed)?; + LiquidityTokensPerAddress::::set(balance.coin, account, Some(new)); + } else { + let existing = Liquidity::::get(balance.coin, account).unwrap_or(0); + if balance.amount.0 > existing || balance.amount.0 == 0 { + Err(Error::::NotEnoughLiquidity)?; + } + if balance.amount.0 < existing { + Err(Error::::CanOnlyRemoveFullAmount)?; + } + + // TODO: do internal transfer instead? + let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); + let instruction = OutInstructionWithBalance { + instruction: OutInstruction { address: out_address, data: None }, + balance, + }; + Coins::::burn_with_instruction(origin.into(), instruction)?; + + // save + Liquidity::::set(balance.coin, account, None); + } + + Self::deposit_event(Event::GenesisLiquidityRemoved { by: account.into(), balance }); + Ok(()) + } + + // Returns the number of blocks since the coin's network reached economic security first time. + // If the network is yet to be reached that threshold 0 is returned, and maximum of + // GENESIS_SRI_TRICKLE_FEED returned. + fn blocks_since_ec_security(coin: &Coin) -> u64 { + let ec_security_block = + EconomicSecurityReached::::get(coin.network()).saturated_into::(); + let current = >::block_number().saturated_into::(); + if ec_security_block > 0 { + let diff = current - ec_security_block; + if diff > GENESIS_SRI_TRICKLE_FEED { + return GENESIS_SRI_TRICKLE_FEED; + } + + return diff; + } + + 0 + } + + fn genesis_ended() -> bool { + >::block_number() >= BLOCKS_PER_MONTH.into() + } + } +} + +pub use pallet::*; diff --git a/substrate/genesis-liquidity/primitives/Cargo.toml b/substrate/genesis-liquidity/primitives/Cargo.toml new file mode 100644 index 000000000..1e10e840a --- /dev/null +++ b/substrate/genesis-liquidity/primitives/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "serai-genesis-liquidity-primitives" +version = "0.1.0" +description = "Serai genesis liquidity primitives" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity/primitives" +authors = ["Akil Demir "] +edition = "2021" +rust-version = "1.77" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true + +[dependencies] +serai-primitives = { path = "../../primitives", default-features = false } + +[features] +std = [ + "serai-primitives/std", +] +default = ["std"] diff --git a/substrate/genesis-liquidity/primitives/LICENSE b/substrate/genesis-liquidity/primitives/LICENSE new file mode 100644 index 000000000..659881f1a --- /dev/null +++ b/substrate/genesis-liquidity/primitives/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs new file mode 100644 index 000000000..40c8ecb3c --- /dev/null +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -0,0 +1,17 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +use serai_primitives::*; + +// amount of blocks in 30 days for 6s per block. +pub const BLOCKS_PER_MONTH: u32 = 10 * 60 * 24 * 30; + +/// 180 days of blocks +pub const GENESIS_SRI_TRICKLE_FEED: u64 = 10 * 60 * 24 * 180; + +// 100 Million SRI +pub const GENESIS_SRI: u64 = 100_000_000 * 10_u64.pow(8); + +// This is the account which will be the origin for any dispatched `InInstruction`s. +pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); diff --git a/substrate/in-instructions/pallet/Cargo.toml b/substrate/in-instructions/pallet/Cargo.toml index f313a22a6..c91c32508 100644 --- a/substrate/in-instructions/pallet/Cargo.toml +++ b/substrate/in-instructions/pallet/Cargo.toml @@ -37,6 +37,7 @@ in-instructions-primitives = { package = "serai-in-instructions-primitives", pat coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false } +genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../../genesis-liquidity/pallet", default-features = false } [features] std = [ @@ -58,5 +59,6 @@ std = [ "coins-pallet/std", "dex-pallet/std", "validator-sets-pallet/std", + "genesis-liquidity-pallet/std", ] default = ["std"] diff --git a/substrate/in-instructions/pallet/src/lib.rs b/substrate/in-instructions/pallet/src/lib.rs index 3ec63ae58..2e45cffa1 100644 --- a/substrate/in-instructions/pallet/src/lib.rs +++ b/substrate/in-instructions/pallet/src/lib.rs @@ -33,10 +33,14 @@ pub mod pallet { Config as ValidatorSetsConfig, Pallet as ValidatorSets, }; + use genesis_liquidity_pallet::{Pallet as GenesisLiq, Config as GenesisLiqConfig}; + use super::*; #[pallet::config] - pub trait Config: frame_system::Config + CoinsConfig + DexConfig + ValidatorSetsConfig { + pub trait Config: + frame_system::Config + CoinsConfig + DexConfig + ValidatorSetsConfig + GenesisLiqConfig + { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -200,6 +204,14 @@ pub mod pallet { } } } + InInstruction::GenesisLiquidity(ops) => match ops { + GenesisLiquidityOperation::Add(address, balance) => { + GenesisLiq::::add_coin_liquidity(address.into(), balance)?; + } + GenesisLiquidityOperation::Remove(address, balance, out_address) => { + GenesisLiq::::remove_coin_liquidity(address.into(), balance, out_address)?; + } + }, } Ok(()) } diff --git a/substrate/in-instructions/primitives/src/lib.rs b/substrate/in-instructions/primitives/src/lib.rs index afa11cac1..fb2e9503b 100644 --- a/substrate/in-instructions/primitives/src/lib.rs +++ b/substrate/in-instructions/primitives/src/lib.rs @@ -71,6 +71,15 @@ pub enum DexCall { Swap(Balance, OutAddress), } +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Zeroize))] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum GenesisLiquidityOperation { + Add(SeraiAddress, Balance), + Remove(SeraiAddress, Balance, ExternalAddress), +} + #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize))] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] @@ -78,6 +87,7 @@ pub enum DexCall { pub enum InInstruction { Transfer(SeraiAddress), Dex(DexCall), + GenesisLiquidity(GenesisLiquidityOperation), } #[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index e4b7d639e..8efe0c85b 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -59,6 +59,7 @@ coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", defau dex-pallet = { package = "serai-dex-pallet", path = "../dex/pallet", default-features = false } validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false } +genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../genesis-liquidity/pallet", default-features = false } in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false } @@ -112,6 +113,7 @@ std = [ "dex-pallet/std", "validator-sets-pallet/std", + "genesis-liquidity-pallet/std", "in-instructions-pallet/std", diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 9a534a725..d79f2f03d 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -32,6 +32,8 @@ pub use signals_pallet as signals; pub use pallet_babe as babe; pub use pallet_grandpa as grandpa; +pub use genesis_liquidity_pallet as genesis_liquidity; + // Actually used by the runtime use sp_core::OpaqueMetadata; use sp_std::prelude::*; @@ -288,6 +290,10 @@ impl in_instructions::Config for Runtime { type RuntimeEvent = RuntimeEvent; } +impl genesis_liquidity::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + // for publishing equivocation evidences. impl frame_system::offchain::SendTransactionTypes for Runtime where @@ -362,6 +368,7 @@ construct_runtime!( Coins: coins, LiquidityTokens: coins::::{Pallet, Call, Storage, Event}, Dex: dex, + GenesisLiquidity: genesis_liquidity, ValidatorSets: validator_sets, From b9df73e41810cd75a61ba851beb072442b2dc0c2 Mon Sep 17 00:00:00 2001 From: akildemir Date: Wed, 27 Mar 2024 15:26:14 +0300 Subject: [PATCH 02/18] add missing deposit event --- substrate/genesis-liquidity/pallet/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index f43df0b41..46f31a36d 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -108,6 +108,10 @@ pub mod pallet { GENESIS_LIQUIDITY_ACCOUNT.into(), ) .unwrap(); + Self::deposit_event(Event::GenesisLiquidityAddedToPool { + coin1: Balance { coin: *coin, amount: Amount(*amount) }, + coin2: Balance { coin: Coin::Serai, amount: Amount(sri_amount) }, + }); // set liquidity tokens per account let tokens = LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), *coin).0; From 2b32fe90cab7532a710912fa37b22e6e26eb4f29 Mon Sep 17 00:00:00 2001 From: akildemir Date: Thu, 28 Mar 2024 11:06:04 +0300 Subject: [PATCH 03/18] fix CI issues --- deny.toml | 2 ++ substrate/abi/src/genesis_liquidity.rs | 19 +++++++++++++++++++ substrate/abi/src/lib.rs | 4 ++++ 3 files changed, 25 insertions(+) create mode 100644 substrate/abi/src/genesis_liquidity.rs diff --git a/deny.toml b/deny.toml index 2e516b996..93dc2f968 100644 --- a/deny.toml +++ b/deny.toml @@ -55,6 +55,8 @@ exceptions = [ { allow = ["AGPL-3.0"], name = "serai-coins-pallet" }, { allow = ["AGPL-3.0"], name = "serai-dex-pallet" }, + + { allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" }, { allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" }, diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs new file mode 100644 index 000000000..391912233 --- /dev/null +++ b/substrate/abi/src/genesis_liquidity.rs @@ -0,0 +1,19 @@ +use serai_primitives::*; + +#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] +#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Call { + // This call is just a place holder so that abi works as expected. + empty_call, +} + +#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] +#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Event { + GenesisLiquidityAdded { by: SeraiAddress, balance: Balance }, + GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance }, + GenesisLiquidityAddedToPool { coin1: Balance, coin2: Balance }, + EconomicSecurityReached { network: NetworkId }, +} diff --git a/substrate/abi/src/lib.rs b/substrate/abi/src/lib.rs index 2e873f4c3..e59f30856 100644 --- a/substrate/abi/src/lib.rs +++ b/substrate/abi/src/lib.rs @@ -11,6 +11,8 @@ pub mod validator_sets; pub mod in_instructions; pub mod signals; +pub mod genesis_liquidity; + pub mod babe; pub mod grandpa; @@ -24,6 +26,7 @@ pub enum Call { Coins(coins::Call), LiquidityTokens(coins::Call), Dex(dex::Call), + GenesisLiquidity(genesis_liquidity::Call), ValidatorSets(validator_sets::Call), InInstructions(in_instructions::Call), Signals(signals::Call), @@ -45,6 +48,7 @@ pub enum Event { Coins(coins::Event), LiquidityTokens(coins::Event), Dex(dex::Event), + GenesisLiquidity(genesis_liquidity::Event), ValidatorSets(validator_sets::Event), InInstructions(in_instructions::Event), Signals(signals::Event), From 207f2bd28a36c23d1a24152c13387a7f24f525c6 Mon Sep 17 00:00:00 2001 From: akildemir Date: Fri, 29 Mar 2024 11:38:48 +0300 Subject: [PATCH 04/18] minor fixes --- substrate/genesis-liquidity/pallet/Cargo.toml | 8 ++++++++ substrate/genesis-liquidity/pallet/src/lib.rs | 6 +++--- substrate/genesis-liquidity/primitives/src/lib.rs | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml index 35551626d..873261d9f 100644 --- a/substrate/genesis-liquidity/pallet/Cargo.toml +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -36,11 +36,19 @@ genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", [features] std = [ + "scale/std", + "scale-info/std", + "frame-system/std", "frame-support/std", + "sp-std/std", + "coins-pallet/std", "dex-pallet/std", + + "serai-primitives/std", + "genesis-liquidity-primitives/std", ] default = ["std"] \ No newline at end of file diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 46f31a36d..a03864681 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -247,9 +247,9 @@ pub mod pallet { Ok(()) } - // Returns the number of blocks since the coin's network reached economic security first time. - // If the network is yet to be reached that threshold 0 is returned, and maximum of - // GENESIS_SRI_TRICKLE_FEED returned. + /// Returns the number of blocks since the coin's network reached economic security first time. + /// If the network is yet to be reached that threshold, 0 is returned. And maximum of + /// `GENESIS_SRI_TRICKLE_FEED` returned. fn blocks_since_ec_security(coin: &Coin) -> u64 { let ec_security_block = EconomicSecurityReached::::get(coin.network()).saturated_into::(); diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index 40c8ecb3c..0ce758240 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -13,5 +13,5 @@ pub const GENESIS_SRI_TRICKLE_FEED: u64 = 10 * 60 * 24 * 180; // 100 Million SRI pub const GENESIS_SRI: u64 = 100_000_000 * 10_u64.pow(8); -// This is the account which will be the origin for any dispatched `InInstruction`s. +// This is the account to hold and manage the genesis liquidity. pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); From c32040240c35b781ba0ac27b28f651b9d10a845d Mon Sep 17 00:00:00 2001 From: akildemir Date: Mon, 8 Apr 2024 12:08:48 +0300 Subject: [PATCH 05/18] make math safer --- substrate/genesis-liquidity/pallet/src/lib.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index a03864681..e2d36bcf7 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -65,7 +65,7 @@ pub mod pallet { impl Hooks> for Pallet { fn on_finalize(n: BlockNumberFor) { // Distribute the genesis sri to pools after a month - if n > BLOCKS_PER_MONTH.into() { + if n == BLOCKS_PER_MONTH.into() { // mint the SRI Coins::::mint( GENESIS_LIQUIDITY_ACCOUNT.into(), @@ -84,19 +84,19 @@ pub mod pallet { account_values.insert(coin, vec![]); let mut pool_amount: u64 = 0; for (account, amount) in Liquidity::::iter_prefix(coin) { - pool_amount += amount; - let value_this_addr = amount * value; + pool_amount = pool_amount.saturating_add(amount); + let value_this_addr = amount.saturating_mul(value); account_values.get_mut(&coin).unwrap().push((account, value_this_addr)) } - let pool_value = pool_amount * value; - total_value += pool_value; + let pool_value = pool_amount.saturating_mul(value); + total_value = total_value.saturating_add(pool_value); pool_values.insert(coin, (pool_amount, pool_value)); } // add the liquidity per pool for (coin, (amount, value)) in &pool_values { - let sri_amount = (GENESIS_SRI * value) / total_value; + let sri_amount = GENESIS_SRI.saturating_mul(*value) / total_value; let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); Dex::::add_liquidity( origin.into(), @@ -117,8 +117,8 @@ pub mod pallet { let tokens = LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), *coin).0; let mut total_tokens_this_coin: u64 = 0; for (acc, value) in account_values.get(coin).unwrap() { - let liq_tokens_this_acc = (tokens * value) / pool_values.get(coin).unwrap().1; - total_tokens_this_coin += liq_tokens_this_acc; + let liq_tokens_this_acc = tokens.saturating_mul(*value) / pool_values.get(coin).unwrap().1; + total_tokens_this_coin = total_tokens_this_coin.saturating_add(liq_tokens_this_acc); LiquidityTokensPerAddress::::set(coin, acc, Some(liq_tokens_this_acc)); } assert_eq!(tokens, total_tokens_this_coin); @@ -195,10 +195,10 @@ pub mod pallet { let current_coin = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin); // burn the SRI if necessary - let mut sri = current_sri.0 - prev_sri.0; - let burn_sri_amount = (sri * - (GENESIS_SRI_TRICKLE_FEED - Self::blocks_since_ec_security(&balance.coin))) / - GENESIS_SRI_TRICKLE_FEED; + let mut sri = current_sri.0.saturating_sub(prev_sri.0); + let burn_sri_amount = sri.saturating_mul( + GENESIS_SRI_TRICKLE_FEED - Self::blocks_since_ec_security(&balance.coin), + ) / GENESIS_SRI_TRICKLE_FEED; Coins::::burn( origin.clone().into(), Balance { coin: Coin::Serai, amount: Amount(burn_sri_amount) }, From 8be0538ebab64f3cf64d2bfd873478486dbe160b Mon Sep 17 00:00:00 2001 From: akildemir Date: Tue, 9 Apr 2024 10:51:28 +0300 Subject: [PATCH 06/18] fix fmt --- substrate/genesis-liquidity/pallet/src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index e2d36bcf7..a37b5d641 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -117,7 +117,8 @@ pub mod pallet { let tokens = LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), *coin).0; let mut total_tokens_this_coin: u64 = 0; for (acc, value) in account_values.get(coin).unwrap() { - let liq_tokens_this_acc = tokens.saturating_mul(*value) / pool_values.get(coin).unwrap().1; + let liq_tokens_this_acc = + tokens.saturating_mul(*value) / pool_values.get(coin).unwrap().1; total_tokens_this_coin = total_tokens_this_coin.saturating_add(liq_tokens_this_acc); LiquidityTokensPerAddress::::set(coin, acc, Some(liq_tokens_this_acc)); } @@ -196,9 +197,9 @@ pub mod pallet { // burn the SRI if necessary let mut sri = current_sri.0.saturating_sub(prev_sri.0); - let burn_sri_amount = sri.saturating_mul( - GENESIS_SRI_TRICKLE_FEED - Self::blocks_since_ec_security(&balance.coin), - ) / GENESIS_SRI_TRICKLE_FEED; + let distance_to_full_pay = + GENESIS_SRI_TRICKLE_FEED - Self::blocks_since_ec_security(&balance.coin); + let burn_sri_amount = sri.saturating_mul(distance_to_full_pay) / GENESIS_SRI_TRICKLE_FEED; Coins::::burn( origin.clone().into(), Balance { coin: Coin::Serai, amount: Amount(burn_sri_amount) }, From 7041dbeb0b06114b1580f71af9b795103582857b Mon Sep 17 00:00:00 2001 From: akildemir Date: Wed, 17 Apr 2024 13:54:23 +0300 Subject: [PATCH 07/18] make remove liquidity an authorized call --- substrate/abi/src/genesis_liquidity.rs | 3 +- substrate/genesis-liquidity/pallet/src/lib.rs | 75 +++++++++---------- substrate/in-instructions/pallet/src/lib.rs | 11 +-- .../in-instructions/primitives/src/lib.rs | 11 +-- substrate/runtime/src/lib.rs | 3 + 5 files changed, 43 insertions(+), 60 deletions(-) diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs index 391912233..dda30023a 100644 --- a/substrate/abi/src/genesis_liquidity.rs +++ b/substrate/abi/src/genesis_liquidity.rs @@ -4,8 +4,7 @@ use serai_primitives::*; #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Call { - // This call is just a place holder so that abi works as expected. - empty_call, + remove_coin_liquidity { balance: Balance }, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index a37b5d641..0ee8514c7 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -10,10 +10,7 @@ pub mod pallet { use sp_std::{vec, collections::btree_map::BTreeMap}; use dex_pallet::{Pallet as Dex, Config as DexConfig}; - use coins_pallet::{ - primitives::{OutInstructionWithBalance, OutInstruction}, - Config as CoinsConfig, Pallet as Coins, AllowMint, - }; + use coins_pallet::{Config as CoinsConfig, Pallet as Coins, AllowMint}; use serai_primitives::*; pub use genesis_liquidity_primitives as primitives; @@ -165,12 +162,37 @@ pub mod pallet { Ok(()) } - /// Remove the provided genesis liquidity for an account. If called pre-economic security era, - pub fn remove_coin_liquidity( - account: PublicKey, - balance: Balance, - out_address: ExternalAddress, - ) -> DispatchResult { + /// Returns the number of blocks since the coin's network reached economic security first time. + /// If the network is yet to be reached that threshold, 0 is returned. And maximum of + /// `GENESIS_SRI_TRICKLE_FEED` returned. + fn blocks_since_ec_security(coin: &Coin) -> u64 { + let ec_security_block = + EconomicSecurityReached::::get(coin.network()).saturated_into::(); + let current = >::block_number().saturated_into::(); + if ec_security_block > 0 { + let diff = current - ec_security_block; + if diff > GENESIS_SRI_TRICKLE_FEED { + return GENESIS_SRI_TRICKLE_FEED; + } + + return diff; + } + + 0 + } + + fn genesis_ended() -> bool { + >::block_number() >= BLOCKS_PER_MONTH.into() + } + } + + #[pallet::call] + impl Pallet { + /// Remove the provided genesis liquidity for an account. + #[pallet::call_index(0)] + #[pallet::weight((0, DispatchClass::Operational))] // TODO + pub fn remove_coin_liquidity(origin: OriginFor, balance: Balance) -> DispatchResult { + let account = ensure_signed(origin)?; let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); // check we are still in genesis period @@ -232,13 +254,9 @@ pub mod pallet { Err(Error::::CanOnlyRemoveFullAmount)?; } - // TODO: do internal transfer instead? - let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); - let instruction = OutInstructionWithBalance { - instruction: OutInstruction { address: out_address, data: None }, - balance, - }; - Coins::::burn_with_instruction(origin.into(), instruction)?; + // TODO: do external transfer instead for making it easier for the user? + // or do we even want to make it easier? + Coins::::transfer(origin.into(), account, balance)?; // save Liquidity::::set(balance.coin, account, None); @@ -247,29 +265,6 @@ pub mod pallet { Self::deposit_event(Event::GenesisLiquidityRemoved { by: account.into(), balance }); Ok(()) } - - /// Returns the number of blocks since the coin's network reached economic security first time. - /// If the network is yet to be reached that threshold, 0 is returned. And maximum of - /// `GENESIS_SRI_TRICKLE_FEED` returned. - fn blocks_since_ec_security(coin: &Coin) -> u64 { - let ec_security_block = - EconomicSecurityReached::::get(coin.network()).saturated_into::(); - let current = >::block_number().saturated_into::(); - if ec_security_block > 0 { - let diff = current - ec_security_block; - if diff > GENESIS_SRI_TRICKLE_FEED { - return GENESIS_SRI_TRICKLE_FEED; - } - - return diff; - } - - 0 - } - - fn genesis_ended() -> bool { - >::block_number() >= BLOCKS_PER_MONTH.into() - } } } diff --git a/substrate/in-instructions/pallet/src/lib.rs b/substrate/in-instructions/pallet/src/lib.rs index 2e45cffa1..97ffa5603 100644 --- a/substrate/in-instructions/pallet/src/lib.rs +++ b/substrate/in-instructions/pallet/src/lib.rs @@ -204,14 +204,9 @@ pub mod pallet { } } } - InInstruction::GenesisLiquidity(ops) => match ops { - GenesisLiquidityOperation::Add(address, balance) => { - GenesisLiq::::add_coin_liquidity(address.into(), balance)?; - } - GenesisLiquidityOperation::Remove(address, balance, out_address) => { - GenesisLiq::::remove_coin_liquidity(address.into(), balance, out_address)?; - } - }, + InInstruction::GenesisLiquidity(address) => { + GenesisLiq::::add_coin_liquidity(address.into(), instruction.balance)?; + } } Ok(()) } diff --git a/substrate/in-instructions/primitives/src/lib.rs b/substrate/in-instructions/primitives/src/lib.rs index fb2e9503b..87d9ce373 100644 --- a/substrate/in-instructions/primitives/src/lib.rs +++ b/substrate/in-instructions/primitives/src/lib.rs @@ -71,15 +71,6 @@ pub enum DexCall { Swap(Balance, OutAddress), } -#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] -#[cfg_attr(feature = "std", derive(Zeroize))] -#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum GenesisLiquidityOperation { - Add(SeraiAddress, Balance), - Remove(SeraiAddress, Balance, ExternalAddress), -} - #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize))] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] @@ -87,7 +78,7 @@ pub enum GenesisLiquidityOperation { pub enum InInstruction { Transfer(SeraiAddress), Dex(DexCall), - GenesisLiquidity(GenesisLiquidityOperation), + GenesisLiquidity(SeraiAddress), } #[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index d79f2f03d..11517a34c 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -177,6 +177,9 @@ impl Contains for CallFilter { }, RuntimeCall::Dex(call) => !matches!(call, dex::Call::__Ignore(_, _)), RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)), + RuntimeCall::GenesisLiquidity(call) => { + !matches!(call, genesis_liquidity::Call::__Ignore(_, _)) + } RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)), RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)), From 826f9986e45016d51ff18a614a0ea7e396c2e3d6 Mon Sep 17 00:00:00 2001 From: akildemir Date: Sat, 20 Apr 2024 10:53:25 +0300 Subject: [PATCH 08/18] implement setting initial values for coins --- Cargo.lock | 11 +++ substrate/abi/Cargo.toml | 3 + substrate/abi/src/genesis_liquidity.rs | 2 + substrate/genesis-liquidity/pallet/Cargo.toml | 8 +- substrate/genesis-liquidity/pallet/src/lib.rs | 92 ++++++++++++++++++- .../genesis-liquidity/primitives/Cargo.toml | 15 ++- .../genesis-liquidity/primitives/src/lib.rs | 30 ++++++ substrate/node/src/chain_spec.rs | 3 + 8 files changed, 155 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3aba7b2c9..bed90168a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7329,6 +7329,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serai-coins-primitives", + "serai-genesis-liquidity-primitives", "serai-in-instructions-primitives", "serai-primitives", "serai-signals-primitives", @@ -7527,6 +7528,9 @@ dependencies = [ "serai-dex-pallet", "serai-genesis-liquidity-primitives", "serai-primitives", + "serai-validator-sets-primitives", + "sp-application-crypto", + "sp-core", "sp-std", ] @@ -7534,7 +7538,14 @@ dependencies = [ name = "serai-genesis-liquidity-primitives" version = "0.1.0" dependencies = [ + "borsh", + "parity-scale-codec", + "scale-info", "serai-primitives", + "serai-validator-sets-primitives", + "serde", + "sp-std", + "zeroize", ] [[package]] diff --git a/substrate/abi/Cargo.toml b/substrate/abi/Cargo.toml index 043504868..45e809b5f 100644 --- a/substrate/abi/Cargo.toml +++ b/substrate/abi/Cargo.toml @@ -31,6 +31,7 @@ sp-consensus-grandpa = { git = "https://github.com/serai-dex/substrate" } serai-primitives = { path = "../primitives", version = "0.1" } serai-coins-primitives = { path = "../coins/primitives", version = "0.1" } serai-validator-sets-primitives = { path = "../validator-sets/primitives", version = "0.1" } +serai-genesis-liquidity-primitives = { path = "../genesis-liquidity/primitives", version = "0.1" } serai-in-instructions-primitives = { path = "../in-instructions/primitives", version = "0.1" } serai-signals-primitives = { path = "../signals/primitives", version = "0.1" } @@ -42,6 +43,7 @@ borsh = [ "serai-primitives/borsh", "serai-coins-primitives/borsh", "serai-validator-sets-primitives/borsh", + "serai-genesis-liquidity-primitives/borsh", "serai-in-instructions-primitives/borsh", "serai-signals-primitives/borsh", ] @@ -50,6 +52,7 @@ serde = [ "serai-primitives/serde", "serai-coins-primitives/serde", "serai-validator-sets-primitives/serde", + "serai-genesis-liquidity-primitives/serde", "serai-in-instructions-primitives/serde", "serai-signals-primitives/serde", ] diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs index dda30023a..7837c2b1a 100644 --- a/substrate/abi/src/genesis_liquidity.rs +++ b/substrate/abi/src/genesis_liquidity.rs @@ -1,10 +1,12 @@ use serai_primitives::*; +use serai_genesis_liquidity_primitives::*; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Call { remove_coin_liquidity { balance: Balance }, + set_initial_price { prices: Prices }, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml index 873261d9f..456a38504 100644 --- a/substrate/genesis-liquidity/pallet/Cargo.toml +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -27,12 +27,15 @@ frame-system = { git = "https://github.com/serai-dex/substrate", default-feature frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false } dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } serai-primitives = { path = "../../primitives", default-features = false } genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../primitives", default-features = false } +validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false } [features] std = [ @@ -43,12 +46,15 @@ std = [ "frame-support/std", "sp-std/std", + "sp-core/std", + "sp-application-crypto/std", "coins-pallet/std", "dex-pallet/std", "serai-primitives/std", "genesis-liquidity-primitives/std", + "validator-sets-primitives/std", ] -default = ["std"] \ No newline at end of file +default = ["std"] diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 0ee8514c7..64207127d 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -5,14 +5,20 @@ pub mod pallet { use super::*; use frame_system::{pallet_prelude::*, RawOrigin}; - use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion}; + use frame_support::{ + pallet_prelude::*, + sp_runtime::{self, SaturatedConversion}, + }; - use sp_std::{vec, collections::btree_map::BTreeMap}; + use sp_std::{vec, vec::Vec, collections::btree_map::BTreeMap}; + use sp_core::sr25519::Signature; + use sp_application_crypto::RuntimePublic; use dex_pallet::{Pallet as Dex, Config as DexConfig}; use coins_pallet::{Config as CoinsConfig, Pallet as Coins, AllowMint}; use serai_primitives::*; + use validator_sets_primitives::{ValidatorSet, Session, musig_key}; pub use genesis_liquidity_primitives as primitives; use primitives::*; @@ -26,6 +32,19 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + #[pallet::genesis_config] + #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] + pub struct GenesisConfig { + /// List of participants to place in the initial validator sets. + pub participants: Vec, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { participants: Default::default() } + } + } + #[pallet::error] pub enum Error { GenesisPeriodEnded, @@ -58,6 +77,20 @@ pub mod pallet { pub(crate) type EconomicSecurityReached = StorageMap<_, Identity, NetworkId, BlockNumberFor, ValueQuery>; + #[pallet::storage] + pub(crate) type Participants = + StorageMap<_, Identity, NetworkId, BoundedVec>, ValueQuery>; + + #[pallet::storage] + pub(crate) type Oracle = StorageMap<_, Identity, Coin, u64, ValueQuery>; + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + Participants::::set(NetworkId::Serai, self.participants.clone().try_into().unwrap()); + } + } + #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(n: BlockNumberFor) { @@ -75,9 +108,14 @@ pub mod pallet { let mut pool_values = BTreeMap::new(); let mut total_value: u64 = 0; for coin in COINS { - // TODO: following line is just a place holder till we get the actual coin value - // in terms of btc. - let value = Dex::::security_oracle_value(coin).unwrap_or(Amount(0)).0; + if coin == Coin::Serai { + continue; + } + + // initial coin value in terms of btc + let value = Oracle::::get(coin); + + // get the pool & individual address values account_values.insert(coin, vec![]); let mut pool_amount: u64 = 0; for (account, amount) in Liquidity::::iter_prefix(coin) { @@ -265,6 +303,50 @@ pub mod pallet { Self::deposit_event(Event::GenesisLiquidityRemoved { by: account.into(), balance }); Ok(()) } + + /// A call to submit the initial coi values. + #[pallet::call_index(1)] + #[pallet::weight((0, DispatchClass::Operational))] // TODO + pub fn set_initial_price( + origin: OriginFor, + prices: Prices, + _signature: Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + // set the prices + Oracle::::set(Coin::Bitcoin, prices.bitcoin); + Oracle::::set(Coin::Monero, prices.monero); + Oracle::::set(Coin::Ether, prices.ethereum); + Oracle::::set(Coin::Dai, prices.dai); + Ok(()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity { + match call { + Call::set_initial_price { ref prices, ref signature } => { + let set = ValidatorSet { network: NetworkId::Serai, session: Session(0) }; + let signers = Participants::::get(NetworkId::Serai); + + if !musig_key(set, &signers).verify(&set_initial_price_message(&set, prices), signature) { + Err(InvalidTransaction::BadProof)?; + } + + ValidTransaction::with_tag_prefix("GenesisLiquidity") + .and_provides((0, set)) + .longevity(u64::MAX) + .propagate(true) + .build() + } + Call::remove_coin_liquidity { .. } => Err(InvalidTransaction::Call)?, + Call::__Ignore(_, _) => unreachable!(), + } + } } } diff --git a/substrate/genesis-liquidity/primitives/Cargo.toml b/substrate/genesis-liquidity/primitives/Cargo.toml index 1e10e840a..a3911b81c 100644 --- a/substrate/genesis-liquidity/primitives/Cargo.toml +++ b/substrate/genesis-liquidity/primitives/Cargo.toml @@ -16,10 +16,19 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] +zeroize = { version = "^1.5", features = ["derive"], optional = true } + +borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true } +serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } + serai-primitives = { path = "../../primitives", default-features = false } +validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false } [features] -std = [ - "serai-primitives/std", -] +std = ["serai-primitives/std", "validator-sets-primitives/std", "sp-std/std"] default = ["std"] diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index 0ce758240..9846c1132 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -2,7 +2,21 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "std")] +use zeroize::Zeroize; + +#[cfg(feature = "borsh")] +use borsh::{BorshSerialize, BorshDeserialize}; +#[cfg(feature = "serde")] +use serde::{Serialize, Deserialize}; + +use sp_std::vec::Vec; + +use scale::{Encode, Decode, MaxEncodedLen}; +use scale_info::TypeInfo; + use serai_primitives::*; +use validator_sets_primitives::ValidatorSet; // amount of blocks in 30 days for 6s per block. pub const BLOCKS_PER_MONTH: u32 = 10 * 60 * 24 * 30; @@ -15,3 +29,19 @@ pub const GENESIS_SRI: u64 = 100_000_000 * 10_u64.pow(8); // This is the account to hold and manage the genesis liquidity. pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); + +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Zeroize))] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Prices { + pub bitcoin: u64, + pub monero: u64, + pub ethereum: u64, + pub dai: u64, +} + +/// The message for the set_initial_price signature. +pub fn set_initial_price_message(set: &ValidatorSet, prices: &Prices) -> Vec { + (b"GenesisLiquidity-set_initial_price", set, prices).encode() +} diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 6fa8d6c3e..428f929c7 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -8,6 +8,7 @@ use sc_service::ChainType; use serai_runtime::{ primitives::*, WASM_BINARY, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, SystemConfig, CoinsConfig, DexConfig, ValidatorSetsConfig, SignalsConfig, BabeConfig, GrandpaConfig, + GenesisLiquidityConfig, }; pub type ChainSpec = sc_service::GenericChainSpec; @@ -60,6 +61,7 @@ fn devnet_genesis( .collect(), participants: validators.clone(), }, + genesis_liquidity: GenesisLiquidityConfig { participants: validators.clone() }, signals: SignalsConfig::default(), babe: BabeConfig { authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(), @@ -111,6 +113,7 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime .collect(), participants: validators.clone(), }, + genesis_liquidity: GenesisLiquidityConfig { participants: validators.clone() }, signals: SignalsConfig::default(), babe: BabeConfig { authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(), From 926ddd09db2db225492bfa37219b73fbd6d366f3 Mon Sep 17 00:00:00 2001 From: akildemir Date: Mon, 29 Apr 2024 23:19:55 +0300 Subject: [PATCH 09/18] add genesis liquidity test & misc fixes --- substrate/abi/src/genesis_liquidity.rs | 7 +- substrate/abi/src/liquidity_tokens.rs | 18 ++ substrate/client/src/serai/dex.rs | 21 +- .../client/src/serai/genesis_liquidity.rs | 73 ++++++ .../client/src/serai/liquidity_tokens.rs | 41 ++++ substrate/client/src/serai/mod.rs | 51 ++-- substrate/client/tests/common/mod.rs | 64 +++++ substrate/client/tests/genesis_liquidity.rs | 232 ++++++++++++++++++ substrate/genesis-liquidity/pallet/Cargo.toml | 1 + substrate/genesis-liquidity/pallet/src/lib.rs | 83 +++++-- .../genesis-liquidity/primitives/Cargo.toml | 13 +- .../genesis-liquidity/primitives/src/lib.rs | 2 +- substrate/runtime/Cargo.toml | 2 +- substrate/runtime/src/lib.rs | 29 ++- 14 files changed, 590 insertions(+), 47 deletions(-) create mode 100644 substrate/abi/src/liquidity_tokens.rs create mode 100644 substrate/client/src/serai/genesis_liquidity.rs create mode 100644 substrate/client/src/serai/liquidity_tokens.rs create mode 100644 substrate/client/tests/genesis_liquidity.rs diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs index 7837c2b1a..2b0c208c8 100644 --- a/substrate/abi/src/genesis_liquidity.rs +++ b/substrate/abi/src/genesis_liquidity.rs @@ -1,12 +1,13 @@ +pub use serai_genesis_liquidity_primitives as primitives; + use serai_primitives::*; -use serai_genesis_liquidity_primitives::*; +use primitives::*; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Call { remove_coin_liquidity { balance: Balance }, - set_initial_price { prices: Prices }, + set_initial_price { prices: Prices, signature: Signature }, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] diff --git a/substrate/abi/src/liquidity_tokens.rs b/substrate/abi/src/liquidity_tokens.rs new file mode 100644 index 000000000..6bdc651b6 --- /dev/null +++ b/substrate/abi/src/liquidity_tokens.rs @@ -0,0 +1,18 @@ +use serai_primitives::{Balance, SeraiAddress}; + +#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] +#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Call { + burn { balance: Balance }, + transfer { to: SeraiAddress, balance: Balance }, +} + +#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] +#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Event { + Mint { to: SeraiAddress, balance: Balance }, + Burn { from: SeraiAddress, balance: Balance }, + Transfer { from: SeraiAddress, to: SeraiAddress, balance: Balance }, +} diff --git a/substrate/client/src/serai/dex.rs b/substrate/client/src/serai/dex.rs index 00108dfef..183411257 100644 --- a/substrate/client/src/serai/dex.rs +++ b/substrate/client/src/serai/dex.rs @@ -1,7 +1,9 @@ use sp_core::bounded_vec::BoundedVec; use serai_abi::primitives::{SeraiAddress, Amount, Coin}; -use crate::{SeraiError, TemporalSerai}; +use scale::{decode_from_bytes, Encode}; + +use crate::{SeraiError, hex_decode, TemporalSerai}; pub type DexEvent = serai_abi::dex::Event; @@ -57,4 +59,21 @@ impl<'a> SeraiDex<'a> { send_to: address, }) } + + pub async fn get_reserves( + &self, + coin1: Coin, + coin2: Coin, + ) -> Result, SeraiError> { + let hash = self + .0 + .serai + .call("state_call", ["DexApi_get_reserves".to_string(), hex::encode((coin1, coin2).encode())]) + .await?; + let bytes = hex_decode(hash) + .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; + let resut = decode_from_bytes::>(bytes.into()) + .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; + Ok(resut.map(|amounts| (Amount(amounts.0), Amount(amounts.1)))) + } } diff --git a/substrate/client/src/serai/genesis_liquidity.rs b/substrate/client/src/serai/genesis_liquidity.rs new file mode 100644 index 000000000..b8882bd7a --- /dev/null +++ b/substrate/client/src/serai/genesis_liquidity.rs @@ -0,0 +1,73 @@ +pub use serai_abi::genesis_liquidity::primitives; +use primitives::Prices; + +use serai_abi::primitives::*; + +use sp_core::sr25519::Signature; + +use scale::Encode; + +use crate::{Serai, SeraiError, TemporalSerai, Transaction}; + +pub type GenesisLiquidityEvent = serai_abi::genesis_liquidity::Event; + +const PALLET: &str = "GenesisLiquidity"; + +#[derive(Clone, Copy)] +pub struct SeraiGenesisLiquidity<'a>(pub(crate) &'a TemporalSerai<'a>); +impl<'a> SeraiGenesisLiquidity<'a> { + pub async fn events(&self) -> Result, SeraiError> { + self + .0 + .events(|event| { + if let serai_abi::Event::GenesisLiquidity(event) = event { + Some(event.clone()) + } else { + None + } + }) + .await + } + + pub async fn liquidity_tokens( + &self, + address: &SeraiAddress, + coin: Coin, + ) -> Result { + Ok( + self + .0 + .storage( + PALLET, + "LiquidityTokensPerAddress", + (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), + ) + .await? + .unwrap_or(Amount(0)), + ) + } + + pub fn set_initial_price(prices: Prices, signature: Signature) -> Transaction { + Serai::unsigned(serai_abi::Call::GenesisLiquidity( + serai_abi::genesis_liquidity::Call::set_initial_price { prices, signature }, + )) + } + + pub fn remove_coin_liquidity(balance: Balance) -> serai_abi::Call { + serai_abi::Call::GenesisLiquidity(serai_abi::genesis_liquidity::Call::remove_coin_liquidity { + balance, + }) + } + + pub async fn liquidity(&self, address: &SeraiAddress, coin: Coin) -> Option { + self + .0 + .storage( + PALLET, + "Liquidity", + (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), + ) + .await + .unwrap() + } +} diff --git a/substrate/client/src/serai/liquidity_tokens.rs b/substrate/client/src/serai/liquidity_tokens.rs new file mode 100644 index 000000000..22fcd49e1 --- /dev/null +++ b/substrate/client/src/serai/liquidity_tokens.rs @@ -0,0 +1,41 @@ +use scale::Encode; + +use serai_abi::primitives::{SeraiAddress, Amount, Coin, Balance}; + +use crate::{TemporalSerai, SeraiError}; + +const PALLET: &str = "LiquidityTokens"; + +#[derive(Clone, Copy)] +pub struct SeraiLiquidityTokens<'a>(pub(crate) &'a TemporalSerai<'a>); +impl<'a> SeraiLiquidityTokens<'a> { + pub async fn token_supply(&self, coin: Coin) -> Result { + Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(Amount(0))) + } + + pub async fn token_balance( + &self, + coin: Coin, + address: SeraiAddress, + ) -> Result { + Ok( + self + .0 + .storage( + PALLET, + "Balances", + (sp_core::hashing::blake2_128(&address.encode()), &address.0, coin), + ) + .await? + .unwrap_or(Amount(0)), + ) + } + + pub fn transfer(to: SeraiAddress, balance: Balance) -> serai_abi::Call { + serai_abi::Call::Coins(serai_abi::coins::Call::transfer { to, balance }) + } + + pub fn burn(balance: Balance) -> serai_abi::Call { + serai_abi::Call::Coins(serai_abi::coins::Call::burn { balance }) + } +} diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index 1347fc058..fc4a9ea7b 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -1,3 +1,4 @@ +use hex::FromHexError; use thiserror::Error; use async_lock::RwLock; @@ -26,6 +27,10 @@ pub mod in_instructions; pub use in_instructions::SeraiInInstructions; pub mod validator_sets; pub use validator_sets::SeraiValidatorSets; +pub mod genesis_liquidity; +pub use genesis_liquidity::SeraiGenesisLiquidity; +pub mod liquidity_tokens; +pub use liquidity_tokens::SeraiLiquidityTokens; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode)] pub struct Block { @@ -82,6 +87,14 @@ impl<'a> Clone for TemporalSerai<'a> { } } +pub fn hex_decode(str: String) -> Result, FromHexError> { + if let Some(stripped) = str.strip_prefix("0x") { + hex::decode(stripped) + } else { + hex::decode(str) + } +} + impl Serai { pub async fn call( &self, @@ -134,19 +147,11 @@ impl Serai { } } - fn hex_decode(str: String) -> Result, SeraiError> { - (if let Some(stripped) = str.strip_prefix("0x") { - hex::decode(stripped) - } else { - hex::decode(str) - }) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string())) - } - pub async fn block_hash(&self, number: u64) -> Result, SeraiError> { let hash: Option = self.call("chain_getBlockHash", [number]).await?; let Some(hash) = hash else { return Ok(None) }; - Self::hex_decode(hash)? + hex_decode(hash) + .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))? .try_into() .map_err(|_| SeraiError::InvalidNode("didn't respond to getBlockHash with hash".to_string())) .map(Some) @@ -195,11 +200,13 @@ impl Serai { Ok(()) } + // TODO: move this into substrate/client/src/validator_sets.rs async fn active_network_validators(&self, network: NetworkId) -> Result, SeraiError> { let hash: String = self .call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())]) .await?; - let bytes = Self::hex_decode(hash)?; + let bytes = hex_decode(hash) + .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; let r = Vec::::decode(&mut bytes.as_slice()) .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; Ok(r) @@ -207,9 +214,12 @@ impl Serai { pub async fn latest_finalized_block_hash(&self) -> Result<[u8; 32], SeraiError> { let hash: String = self.call("chain_getFinalizedHead", ()).await?; - Self::hex_decode(hash)?.try_into().map_err(|_| { - SeraiError::InvalidNode("didn't respond to getFinalizedHead with hash".to_string()) - }) + hex_decode(hash) + .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))? + .try_into() + .map_err(|_| { + SeraiError::InvalidNode("didn't respond to getFinalizedHead with hash".to_string()) + }) } pub async fn header(&self, hash: [u8; 32]) -> Result, SeraiError> { @@ -219,7 +229,7 @@ impl Serai { pub async fn block(&self, hash: [u8; 32]) -> Result, SeraiError> { let block: Option = self.call("chain_getBlockBin", [hex::encode(hash)]).await?; let Some(block) = block else { return Ok(None) }; - let Ok(bytes) = Self::hex_decode(block) else { + let Ok(bytes) = hex_decode(block) else { Err(SeraiError::InvalidNode("didn't return a hex-encoded block".to_string()))? }; let Ok(block) = Block::decode(&mut bytes.as_slice()) else { @@ -365,7 +375,8 @@ impl<'a> TemporalSerai<'a> { let res: Option = self.serai.call("state_getStorage", [hex::encode(full_key), hex::encode(self.block)]).await?; let Some(res) = res else { return Ok(None) }; - let res = Serai::hex_decode(res)?; + let res = hex_decode(res) + .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; Ok(Some(R::decode(&mut res.as_slice()).map_err(|_| { SeraiError::InvalidRuntime("different type present at storage location".to_string()) })?)) @@ -386,4 +397,12 @@ impl<'a> TemporalSerai<'a> { pub fn validator_sets(&'a self) -> SeraiValidatorSets<'a> { SeraiValidatorSets(self) } + + pub fn genesis_liquidity(&'a self) -> SeraiGenesisLiquidity { + SeraiGenesisLiquidity(self) + } + + pub fn liquidity_tokens(&'a self) -> SeraiLiquidityTokens { + SeraiLiquidityTokens(self) + } } diff --git a/substrate/client/tests/common/mod.rs b/substrate/client/tests/common/mod.rs index d7e8436b2..e9d88594c 100644 --- a/substrate/client/tests/common/mod.rs +++ b/substrate/client/tests/common/mod.rs @@ -66,3 +66,67 @@ macro_rules! serai_test { )* } } + +#[macro_export] +macro_rules! serai_test_fast_epoch { + ($($name: ident: $test: expr)*) => { + $( + #[tokio::test] + async fn $name() { + use std::collections::HashMap; + use dockertest::{ + PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image, + TestBodySpecification, DockerTest, + }; + + serai_docker_tests::build("serai-fast-epoch".to_string()); + + let handle = concat!("serai_client-serai_node-", stringify!($name)); + + let composition = TestBodySpecification::with_image( + Image::with_repository("serai-dev-serai-fast-epoch").pull_policy(PullPolicy::Never), + ) + .replace_cmd(vec![ + "serai-node".to_string(), + "--dev".to_string(), + "--unsafe-rpc-external".to_string(), + "--rpc-cors".to_string(), + "all".to_string(), + ]) + .replace_env( + HashMap::from([ + ("RUST_LOG".to_string(), "runtime=debug".to_string()), + ("KEY".to_string(), " ".to_string()), + ]) + ) + .set_publish_all_ports(true) + .set_handle(handle) + .set_start_policy(StartPolicy::Strict) + .set_log_options(Some(LogOptions { + action: LogAction::Forward, + policy: LogPolicy::Always, + source: LogSource::Both, + })); + + let mut test = DockerTest::new().with_network(dockertest::Network::Isolated); + test.provide_container(composition); + test.run_async(|ops| async move { + // Sleep until the Substrate RPC starts + let serai_rpc = ops.handle(handle).host_port(9944).unwrap(); + let serai_rpc = format!("http://{}:{}", serai_rpc.0, serai_rpc.1); + // Bound execution to 60 seconds + for _ in 0 .. 60 { + tokio::time::sleep(core::time::Duration::from_secs(1)).await; + let Ok(client) = Serai::new(serai_rpc.clone()).await else { continue }; + if client.latest_finalized_block_hash().await.is_err() { + continue; + } + break; + } + #[allow(clippy::redundant_closure_call)] + $test(Serai::new(serai_rpc).await.unwrap()).await; + }).await; + } + )* + } +} diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs new file mode 100644 index 000000000..9efc8f092 --- /dev/null +++ b/substrate/client/tests/genesis_liquidity.rs @@ -0,0 +1,232 @@ +use std::{time::Duration, collections::HashMap}; + +use rand_core::{RngCore, OsRng}; +use zeroize::Zeroizing; + +use ciphersuite::{Ciphersuite, Ristretto}; +use frost::dkg::musig::musig; +use schnorrkel::Schnorrkel; + +use serai_client::{ + genesis_liquidity::{ + primitives::{GENESIS_LIQUIDITY_ACCOUNT, GENESIS_SRI}, + SeraiGenesisLiquidity, + }, + validator_sets::primitives::{musig_context, Session, ValidatorSet}, +}; + +use serai_abi::{ + genesis_liquidity::primitives::{set_initial_price_message, Prices}, + primitives::COINS, +}; + +use sp_core::{sr25519::Signature, Pair as PairTrait}; + +use serai_client::{ + primitives::{ + Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name, + }, + in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch}, + Serai, +}; + +mod common; +use common::{in_instructions::provide_batch, tx::publish_tx}; + +serai_test_fast_epoch!( + genesis_liquidity: (|serai: Serai| async move { + test_genesis_liquidity(serai).await; + }) +); + +async fn test_genesis_liquidity(serai: Serai) { + // amounts + let amounts = vec![ + Amount(5_53246991), + Amount(3_14562819), + Amount(9_33648912), + Amount(150_873639000000), + Amount(248_665228000000), + Amount(451_765529000000), + ]; + + // addresses + let mut btc_addresses = vec![]; + let mut xmr_addresses = vec![]; + let addr_count = amounts.len(); + for (i, amount) in amounts.into_iter().enumerate() { + let mut address = SeraiAddress::new([0; 32]); + OsRng.fill_bytes(&mut address.0); + if i < addr_count / 2 { + btc_addresses.push((address, amount)); + } else { + xmr_addresses.push((address, amount)); + } + } + btc_addresses.sort_by(|a1, a2| a1.0.cmp(&a2.0)); + xmr_addresses.sort_by(|a1, a2| a1.0.cmp(&a2.0)); + + // btc batch + let mut block_hash = BlockHash([0; 32]); + OsRng.fill_bytes(&mut block_hash.0); + let btc_ins = btc_addresses + .iter() + .map(|(addr, amount)| InInstructionWithBalance { + instruction: InInstruction::GenesisLiquidity(*addr), + balance: Balance { coin: Coin::Bitcoin, amount: *amount }, + }) + .collect::>(); + let batch = + Batch { network: NetworkId::Bitcoin, id: 0, block: block_hash, instructions: btc_ins }; + provide_batch(&serai, batch).await; + + // xmr batch + let mut block_hash = BlockHash([0; 32]); + OsRng.fill_bytes(&mut block_hash.0); + let xmr_ins = xmr_addresses + .iter() + .map(|(addr, amount)| InInstructionWithBalance { + instruction: InInstruction::GenesisLiquidity(*addr), + balance: Balance { coin: Coin::Monero, amount: *amount }, + }) + .collect::>(); + let batch = Batch { network: NetworkId::Monero, id: 0, block: block_hash, instructions: xmr_ins }; + provide_batch(&serai, batch).await; + + // set prices + let prices = Prices { bitcoin: 10u64.pow(8), monero: 184100, ethereum: 4785000, dai: 1500 }; + set_prices(&serai, &prices).await; + + // wait until genesis ends.. + tokio::time::timeout(tokio::time::Duration::from_secs(300), async { + while serai.latest_finalized_block().await.unwrap().number() < 25 { + tokio::time::sleep(Duration::from_secs(6)).await; + } + }) + .await + .unwrap(); + + // check total SRI supply is +100M + let last_block = serai.latest_finalized_block().await.unwrap().hash(); + let serai = serai.as_of(last_block); + // Check balance instead of supply + let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap(); + // there are 6 endowed accounts in dev-net. Take this into consideration when checking + // for the total sri minted at this time. + let endowed_amount: u64 = 1 << 60; + let total_sri = (6 * endowed_amount) + GENESIS_SRI; + assert_eq!(sri, Amount(total_sri)); + + // check genesis account has no coins, all transferred to pools. + for coin in COINS { + let amount = serai.coins().coin_balance(coin, GENESIS_LIQUIDITY_ACCOUNT).await.unwrap(); + assert_eq!(amount.0, 0); + } + + // check pools has proper liquidity + let pool_btc = btc_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); + let pool_xmr = xmr_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); + + let pool_btc_value = (pool_btc * u128::from(prices.bitcoin)) / 10u128.pow(8); + let pool_xmr_value = (pool_xmr * u128::from(prices.monero)) / 10u128.pow(12); + let total_value = pool_btc_value + pool_xmr_value; + + // calculated distributed SRI. We know that xmr is at the end of COINS array + // so it will be approximated to roof instead of floor after integer division. + let btc_sri = (pool_btc_value * u128::from(GENESIS_SRI)) / total_value; + let xmr_sri = ((pool_xmr_value * u128::from(GENESIS_SRI)) / total_value) + 1; + + let btc_reserves = serai.dex().get_reserves(Coin::Bitcoin, Coin::Serai).await.unwrap().unwrap(); + assert_eq!(u128::from(btc_reserves.0 .0), pool_btc); + assert_eq!(u128::from(btc_reserves.1 .0), btc_sri); + + let xmr_reserves = serai.dex().get_reserves(Coin::Monero, Coin::Serai).await.unwrap().unwrap(); + assert_eq!(u128::from(xmr_reserves.0 .0), pool_xmr); + assert_eq!(u128::from(xmr_reserves.1 .0), xmr_sri); + + // check each btc liq provider got liq tokens proportional to their value + let btc_liq_token_supply = u128::from( + serai + .liquidity_tokens() + .token_balance(Coin::Bitcoin, GENESIS_LIQUIDITY_ACCOUNT) + .await + .unwrap() + .0, + ); + let mut total_tokens_this_coin: u128 = 0; + for (i, (addr, amount)) in btc_addresses.iter().enumerate() { + let addr_value = (u128::from(amount.0) * u128::from(prices.bitcoin)) / 10u128.pow(8); + let addr_liq_tokens = if i == btc_addresses.len() - 1 { + btc_liq_token_supply - total_tokens_this_coin + } else { + (addr_value * btc_liq_token_supply) / pool_btc_value + }; + + let addr_actual_token_amount = + serai.genesis_liquidity().liquidity_tokens(addr, Coin::Bitcoin).await.unwrap(); + + assert_eq!(addr_liq_tokens, addr_actual_token_amount.0.into()); + total_tokens_this_coin += addr_liq_tokens; + } + + // check each xmr liq provider got liq tokens proportional to their value + let xmr_liq_token_supply = u128::from( + serai + .liquidity_tokens() + .token_balance(Coin::Monero, GENESIS_LIQUIDITY_ACCOUNT) + .await + .unwrap() + .0, + ); + total_tokens_this_coin = 0; + for (i, (addr, amount)) in xmr_addresses.iter().enumerate() { + let addr_value = (u128::from(amount.0) * u128::from(prices.monero)) / 10u128.pow(12); + let addr_liq_tokens = if i == xmr_addresses.len() - 1 { + xmr_liq_token_supply - total_tokens_this_coin + } else { + (addr_value * xmr_liq_token_supply) / pool_xmr_value + }; + + let addr_actual_token_amount = + serai.genesis_liquidity().liquidity_tokens(addr, Coin::Monero).await.unwrap(); + + assert_eq!(addr_liq_tokens, addr_actual_token_amount.0.into()); + total_tokens_this_coin += addr_liq_tokens; + } + + // TODO: remove the liq before/after genesis ended. +} + +async fn set_prices(serai: &Serai, prices: &Prices) { + // prepare a Musig tx to set the initial prices + let pair = insecure_pair_from_name("Alice"); + let public = pair.public(); + let set = ValidatorSet { session: Session(0), network: NetworkId::Serai }; + + let public_key = ::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap(); + let secret_key = ::read_F::<&[u8]>( + &mut pair.as_ref().secret.to_bytes()[.. 32].as_ref(), + ) + .unwrap(); + + assert_eq!(Ristretto::generator() * secret_key, public_key); + let threshold_keys = + musig::(&musig_context(set), &Zeroizing::new(secret_key), &[public_key]).unwrap(); + + let sig = frost::tests::sign_without_caching( + &mut OsRng, + frost::tests::algorithm_machines( + &mut OsRng, + &Schnorrkel::new(b"substrate"), + &HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]), + ), + &set_initial_price_message(&set, prices), + ); + + // set initial prices + let _ = publish_tx( + serai, + &SeraiGenesisLiquidity::set_initial_price(*prices, Signature(sig.to_bytes())), + ) + .await; +} diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml index 456a38504..5ce41383a 100644 --- a/substrate/genesis-liquidity/pallet/Cargo.toml +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -56,5 +56,6 @@ std = [ "genesis-liquidity-primitives/std", "validator-sets-primitives/std", ] +fast-epoch = [] default = ["std"] diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 64207127d..6e6f2a1f9 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -94,8 +94,14 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(n: BlockNumberFor) { + #[cfg(feature = "fast-epoch")] + let final_block = 25u32; + + #[cfg(not(feature = "fast-epoch"))] + let final_block = BLOCKS_PER_MONTH; + // Distribute the genesis sri to pools after a month - if n == BLOCKS_PER_MONTH.into() { + if n == final_block.into() { // mint the SRI Coins::::mint( GENESIS_LIQUIDITY_ACCOUNT.into(), @@ -105,8 +111,8 @@ pub mod pallet { // get coin values & total let mut account_values = BTreeMap::new(); - let mut pool_values = BTreeMap::new(); - let mut total_value: u64 = 0; + let mut pool_values = vec![]; + let mut total_value: u128 = 0; for coin in COINS { if coin == Coin::Serai { continue; @@ -117,50 +123,85 @@ pub mod pallet { // get the pool & individual address values account_values.insert(coin, vec![]); - let mut pool_amount: u64 = 0; + let mut pool_amount: u128 = 0; for (account, amount) in Liquidity::::iter_prefix(coin) { - pool_amount = pool_amount.saturating_add(amount); - let value_this_addr = amount.saturating_mul(value); + pool_amount = pool_amount.saturating_add(amount.into()); + let value_this_addr = + u128::from(amount).saturating_mul(value.into()) / 10u128.pow(coin.decimals()); account_values.get_mut(&coin).unwrap().push((account, value_this_addr)) } + // sort, so that everyone has a consistent accounts vector per coin + account_values.get_mut(&coin).unwrap().sort(); - let pool_value = pool_amount.saturating_mul(value); + let pool_value = pool_amount.saturating_mul(value.into()) / 10u128.pow(coin.decimals()); total_value = total_value.saturating_add(pool_value); - pool_values.insert(coin, (pool_amount, pool_value)); + pool_values.push((coin, pool_amount, pool_value)); } // add the liquidity per pool - for (coin, (amount, value)) in &pool_values { - let sri_amount = GENESIS_SRI.saturating_mul(*value) / total_value; + let mut total_sri_distributed = 0; + let pool_values_len = pool_values.len(); + for (i, (coin, pool_amount, pool_value)) in pool_values.into_iter().enumerate() { + // whatever sri left for the last coin should be ~= it's ratio + let sri_amount = if i == (pool_values_len - 1) { + GENESIS_SRI - total_sri_distributed + } else { + u64::try_from(u128::from(GENESIS_SRI).saturating_mul(pool_value) / total_value).unwrap() + }; + total_sri_distributed += sri_amount; + + // we can't add 0 liquidity + if !(pool_amount > 0 && sri_amount > 0) { + continue; + } + + // actually add the liquidity to dex let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); Dex::::add_liquidity( origin.into(), - *coin, - *amount, + coin, + u64::try_from(pool_amount).unwrap(), sri_amount, - *amount, + u64::try_from(pool_amount).unwrap(), sri_amount, GENESIS_LIQUIDITY_ACCOUNT.into(), ) .unwrap(); + + // let everyone know about the event Self::deposit_event(Event::GenesisLiquidityAddedToPool { - coin1: Balance { coin: *coin, amount: Amount(*amount) }, + coin1: Balance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) }, coin2: Balance { coin: Coin::Serai, amount: Amount(sri_amount) }, }); // set liquidity tokens per account - let tokens = LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), *coin).0; - let mut total_tokens_this_coin: u64 = 0; - for (acc, value) in account_values.get(coin).unwrap() { - let liq_tokens_this_acc = - tokens.saturating_mul(*value) / pool_values.get(coin).unwrap().1; + let tokens = + u128::from(LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin).0); + let mut total_tokens_this_coin: u128 = 0; + + let accounts = account_values.get(&coin).unwrap(); + for (i, (acc, acc_value)) in accounts.iter().enumerate() { + // give whatever left to the last account not to have rounding errors. + let liq_tokens_this_acc = if i == accounts.len() - 1 { + tokens - total_tokens_this_coin + } else { + tokens.saturating_mul(*acc_value) / pool_value + }; + total_tokens_this_coin = total_tokens_this_coin.saturating_add(liq_tokens_this_acc); - LiquidityTokensPerAddress::::set(coin, acc, Some(liq_tokens_this_acc)); + + LiquidityTokensPerAddress::::set( + coin, + acc, + Some(u64::try_from(liq_tokens_this_acc).unwrap()), + ); } assert_eq!(tokens, total_tokens_this_coin); } + assert_eq!(total_sri_distributed, GENESIS_SRI); - // we shouldn't have any coin left in our account at this moment, including SRI. + // we shouldn't have left any coin in genesis account at this moment, including SRI. + // All transferred to the pools. for coin in COINS { assert_eq!(Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin), Amount(0)); } diff --git a/substrate/genesis-liquidity/primitives/Cargo.toml b/substrate/genesis-liquidity/primitives/Cargo.toml index a3911b81c..e795ff24b 100644 --- a/substrate/genesis-liquidity/primitives/Cargo.toml +++ b/substrate/genesis-liquidity/primitives/Cargo.toml @@ -30,5 +30,16 @@ serai-primitives = { path = "../../primitives", default-features = false } validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false } [features] -std = ["serai-primitives/std", "validator-sets-primitives/std", "sp-std/std"] +std = [ + "zeroize", + "scale/std", + "borsh?/std", + "serde?/std", + "scale-info/std", + + "serai-primitives/std", + "validator-sets-primitives/std", + + "sp-std/std" +] default = ["std"] diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index 9846c1132..f334ec74e 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -30,7 +30,7 @@ pub const GENESIS_SRI: u64 = 100_000_000 * 10_u64.pow(8); // This is the account to hold and manage the genesis liquidity. pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); -#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize))] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 8efe0c85b..7a8169ec6 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -126,7 +126,7 @@ std = [ "pallet-transaction-payment-rpc-runtime-api/std", ] -fast-epoch = [] +fast-epoch = ["genesis-liquidity-pallet/fast-epoch"] runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 11517a34c..b95593f91 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -11,7 +11,6 @@ use core::marker::PhantomData; // Re-export all components pub use serai_primitives as primitives; pub use primitives::{BlockNumber, Header}; -use primitives::{NetworkId, NETWORKS}; pub use frame_system as system; pub use frame_support as support; @@ -49,7 +48,7 @@ use sp_runtime::{ BoundedVec, Perbill, ApplyExtrinsicResult, }; -use primitives::{PublicKey, AccountLookup, SubstrateAmount}; +use primitives::{NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, NETWORKS}; use support::{ traits::{ConstU8, ConstU16, ConstU32, ConstU64, Contains}, @@ -323,7 +322,7 @@ pub type ReportLongevity = ::EpochDuration; impl babe::Config for Runtime { #[cfg(feature = "fast-epoch")] - type EpochDuration = ConstU64<{ MINUTES / 2 }>; // 30 seconds + type EpochDuration = ConstU64<{ HOURS / 2 }>; // 30 minutes #[cfg(not(feature = "fast-epoch"))] type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>; @@ -638,4 +637,28 @@ sp_api::impl_runtime_apis! { } } } + + impl dex::DexApi for Runtime { + fn quote_price_exact_tokens_for_tokens( + asset1: Coin, + asset2: Coin, + amount: SubstrateAmount, + include_fee: bool + ) -> Option { + Dex::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) + } + + fn quote_price_tokens_for_exact_tokens( + asset1: Coin, + asset2: Coin, + amount: SubstrateAmount, + include_fee: bool + ) -> Option { + Dex::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) + } + + fn get_reserves(asset1: Coin, asset2: Coin) -> Option<(SubstrateAmount, SubstrateAmount)> { + Dex::get_reserves(&asset1, &asset2).ok() + } + } } From 20cf4c930ca9ef084614a391a9ec9fce4f8a20f0 Mon Sep 17 00:00:00 2001 From: akildemir Date: Tue, 30 Apr 2024 10:00:21 +0300 Subject: [PATCH 10/18] updato develop latest --- coins/ethereum/src/abi/router.rs | 1164 +++++++++++++++++++++++++++++ coins/ethereum/src/abi/schnorr.rs | 410 ++++++++++ 2 files changed, 1574 insertions(+) create mode 100644 coins/ethereum/src/abi/router.rs create mode 100644 coins/ethereum/src/abi/schnorr.rs diff --git a/coins/ethereum/src/abi/router.rs b/coins/ethereum/src/abi/router.rs new file mode 100644 index 000000000..3b7e6f988 --- /dev/null +++ b/coins/ethereum/src/abi/router.rs @@ -0,0 +1,1164 @@ +pub use router::*; +/// This module was auto-generated with ethers-rs Abigen. +/// More information at: +#[allow( + clippy::enum_variant_names, + clippy::too_many_arguments, + clippy::upper_case_acronyms, + clippy::type_complexity, + dead_code, + non_camel_case_types, +)] +pub mod router { + #[allow(deprecated)] + fn __abi() -> ::ethers_core::abi::Abi { + ::ethers_core::abi::ethabi::Contract { + constructor: ::core::option::Option::Some(::ethers_core::abi::ethabi::Constructor { + inputs: ::std::vec![], + }), + functions: ::core::convert::From::from([ + ( + ::std::borrow::ToOwned::to_owned("KEY_PARITY"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("KEY_PARITY"), + inputs: ::std::vec![], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint8"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("Q"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("Q"), + inputs: ::std::vec![], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint256"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("execute"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("execute"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("transactions"), + kind: ::ethers_core::abi::ethabi::ParamType::Array( + ::std::boxed::Box::new( + ::ethers_core::abi::ethabi::ParamType::Tuple( + ::std::vec![ + ::ethers_core::abi::ethabi::ParamType::Address, + ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + ::ethers_core::abi::ethabi::ParamType::Bytes, + ], + ), + ), + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned( + "struct Router.OutInstruction[]", + ), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("sig"), + kind: ::ethers_core::abi::ethabi::ParamType::Tuple( + ::std::vec![ + ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), + ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), + ], + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("struct Router.Signature"), + ), + }, + ], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("initSeraiKey"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("initSeraiKey"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("_seraiKey"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("initializer"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("initializer"), + inputs: ::std::vec![], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Address, + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("address"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("nonce"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("nonce"), + inputs: ::std::vec![], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint256"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("seraiKey"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("seraiKey"), + inputs: ::std::vec![], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("updateSeraiKey"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("updateSeraiKey"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("_seraiKey"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("sig"), + kind: ::ethers_core::abi::ethabi::ParamType::Tuple( + ::std::vec![ + ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), + ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), + ], + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("struct Router.Signature"), + ), + }, + ], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("verify"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("verify"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("parity"), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint8"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("px"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("message"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("c"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("s"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Bool, + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bool"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ]), + events: ::core::convert::From::from([ + ( + ::std::borrow::ToOwned::to_owned("Executed"), + ::std::vec![ + ::ethers_core::abi::ethabi::Event { + name: ::std::borrow::ToOwned::to_owned("Executed"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::EventParam { + name: ::std::borrow::ToOwned::to_owned("nonce"), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + indexed: false, + }, + ::ethers_core::abi::ethabi::EventParam { + name: ::std::borrow::ToOwned::to_owned("batch"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + indexed: false, + }, + ::ethers_core::abi::ethabi::EventParam { + name: ::std::borrow::ToOwned::to_owned("success"), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + indexed: false, + }, + ], + anonymous: false, + }, + ], + ), + ]), + errors: ::core::convert::From::from([ + ( + ::std::borrow::ToOwned::to_owned("AlreadyInitialized"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned("AlreadyInitialized"), + inputs: ::std::vec![], + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("InvalidKey"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned("InvalidKey"), + inputs: ::std::vec![], + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("InvalidSOrA"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned("InvalidSOrA"), + inputs: ::std::vec![], + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("InvalidSignature"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned("InvalidSignature"), + inputs: ::std::vec![], + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("NotInitializer"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned("NotInitializer"), + inputs: ::std::vec![], + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("TooManyTransactions"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned( + "TooManyTransactions", + ), + inputs: ::std::vec![], + }, + ], + ), + ]), + receive: false, + fallback: false, + } + } + ///The parsed JSON ABI of the contract. + pub static ROUTER_ABI: ::ethers_contract::Lazy<::ethers_core::abi::Abi> = ::ethers_contract::Lazy::new( + __abi, + ); + pub struct Router(::ethers_contract::Contract); + impl ::core::clone::Clone for Router { + fn clone(&self) -> Self { + Self(::core::clone::Clone::clone(&self.0)) + } + } + impl ::core::ops::Deref for Router { + type Target = ::ethers_contract::Contract; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl ::core::ops::DerefMut for Router { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + impl ::core::fmt::Debug for Router { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_tuple(::core::stringify!(Router)).field(&self.address()).finish() + } + } + impl Router { + /// Creates a new contract instance with the specified `ethers` client at + /// `address`. The contract derefs to a `ethers::Contract` object. + pub fn new>( + address: T, + client: ::std::sync::Arc, + ) -> Self { + Self( + ::ethers_contract::Contract::new( + address.into(), + ROUTER_ABI.clone(), + client, + ), + ) + } + ///Calls the contract's `KEY_PARITY` (0x7e7777a7) function + pub fn key_parity(&self) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([126, 119, 119, 167], ()) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `Q` (0xe493ef8c) function + pub fn q( + &self, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([228, 147, 239, 140], ()) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `execute` (0xb839b1a1) function + pub fn execute( + &self, + transactions: ::std::vec::Vec, + sig: Signature, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([184, 57, 177, 161], (transactions, sig)) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `initSeraiKey` (0x3d54f51e) function + pub fn init_serai_key( + &self, + serai_key: [u8; 32], + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([61, 84, 245, 30], serai_key) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `initializer` (0x9ce110d7) function + pub fn initializer( + &self, + ) -> ::ethers_contract::builders::ContractCall< + M, + ::ethers_core::types::Address, + > { + self.0 + .method_hash([156, 225, 16, 215], ()) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `nonce` (0xaffed0e0) function + pub fn nonce( + &self, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([175, 254, 208, 224], ()) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `seraiKey` (0x9d6eea0a) function + pub fn serai_key( + &self, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([157, 110, 234, 10], ()) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `updateSeraiKey` (0xb5071c6a) function + pub fn update_serai_key( + &self, + serai_key: [u8; 32], + sig: Signature, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([181, 7, 28, 106], (serai_key, sig)) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `verify` (0x9186da4c) function + pub fn verify( + &self, + parity: u8, + px: [u8; 32], + message: [u8; 32], + c: [u8; 32], + s: [u8; 32], + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([145, 134, 218, 76], (parity, px, message, c, s)) + .expect("method not found (this should never happen)") + } + ///Gets the contract's `Executed` event + pub fn executed_filter( + &self, + ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, ExecutedFilter> { + self.0.event() + } + /// Returns an `Event` builder for all the events of this contract. + pub fn events( + &self, + ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, ExecutedFilter> { + self.0.event_with_filter(::core::default::Default::default()) + } + } + impl From<::ethers_contract::Contract> + for Router { + fn from(contract: ::ethers_contract::Contract) -> Self { + Self::new(contract.address(), contract.client()) + } + } + ///Custom Error type `AlreadyInitialized` with signature `AlreadyInitialized()` and selector `0x0dc149f0` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "AlreadyInitialized", abi = "AlreadyInitialized()")] + pub struct AlreadyInitialized; + ///Custom Error type `InvalidKey` with signature `InvalidKey()` and selector `0x76d4e1e8` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "InvalidKey", abi = "InvalidKey()")] + pub struct InvalidKey; + ///Custom Error type `InvalidSOrA` with signature `InvalidSOrA()` and selector `0x4e99a12e` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "InvalidSOrA", abi = "InvalidSOrA()")] + pub struct InvalidSOrA; + ///Custom Error type `InvalidSignature` with signature `InvalidSignature()` and selector `0x8baa579f` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "InvalidSignature", abi = "InvalidSignature()")] + pub struct InvalidSignature; + ///Custom Error type `NotInitializer` with signature `NotInitializer()` and selector `0xceeb95b3` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "NotInitializer", abi = "NotInitializer()")] + pub struct NotInitializer; + ///Custom Error type `TooManyTransactions` with signature `TooManyTransactions()` and selector `0xfb4593ba` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "TooManyTransactions", abi = "TooManyTransactions()")] + pub struct TooManyTransactions; + ///Container type for all of the contract's custom errors + #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] + pub enum RouterErrors { + AlreadyInitialized(AlreadyInitialized), + InvalidKey(InvalidKey), + InvalidSOrA(InvalidSOrA), + InvalidSignature(InvalidSignature), + NotInitializer(NotInitializer), + TooManyTransactions(TooManyTransactions), + /// The standard solidity revert string, with selector + /// Error(string) -- 0x08c379a0 + RevertString(::std::string::String), + } + impl ::ethers_core::abi::AbiDecode for RouterErrors { + fn decode( + data: impl AsRef<[u8]>, + ) -> ::core::result::Result { + let data = data.as_ref(); + if let Ok(decoded) = <::std::string::String as ::ethers_core::abi::AbiDecode>::decode( + data, + ) { + return Ok(Self::RevertString(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::AlreadyInitialized(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::InvalidKey(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::InvalidSOrA(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::InvalidSignature(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::NotInitializer(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::TooManyTransactions(decoded)); + } + Err(::ethers_core::abi::Error::InvalidData.into()) + } + } + impl ::ethers_core::abi::AbiEncode for RouterErrors { + fn encode(self) -> ::std::vec::Vec { + match self { + Self::AlreadyInitialized(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::InvalidKey(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::InvalidSOrA(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::InvalidSignature(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::NotInitializer(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::TooManyTransactions(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::RevertString(s) => ::ethers_core::abi::AbiEncode::encode(s), + } + } + } + impl ::ethers_contract::ContractRevert for RouterErrors { + fn valid_selector(selector: [u8; 4]) -> bool { + match selector { + [0x08, 0xc3, 0x79, 0xa0] => true, + _ if selector + == ::selector() => { + true + } + _ if selector + == ::selector() => true, + _ if selector + == ::selector() => true, + _ if selector + == ::selector() => { + true + } + _ if selector + == ::selector() => { + true + } + _ if selector + == ::selector() => { + true + } + _ => false, + } + } + } + impl ::core::fmt::Display for RouterErrors { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Self::AlreadyInitialized(element) => { + ::core::fmt::Display::fmt(element, f) + } + Self::InvalidKey(element) => ::core::fmt::Display::fmt(element, f), + Self::InvalidSOrA(element) => ::core::fmt::Display::fmt(element, f), + Self::InvalidSignature(element) => ::core::fmt::Display::fmt(element, f), + Self::NotInitializer(element) => ::core::fmt::Display::fmt(element, f), + Self::TooManyTransactions(element) => { + ::core::fmt::Display::fmt(element, f) + } + Self::RevertString(s) => ::core::fmt::Display::fmt(s, f), + } + } + } + impl ::core::convert::From<::std::string::String> for RouterErrors { + fn from(value: String) -> Self { + Self::RevertString(value) + } + } + impl ::core::convert::From for RouterErrors { + fn from(value: AlreadyInitialized) -> Self { + Self::AlreadyInitialized(value) + } + } + impl ::core::convert::From for RouterErrors { + fn from(value: InvalidKey) -> Self { + Self::InvalidKey(value) + } + } + impl ::core::convert::From for RouterErrors { + fn from(value: InvalidSOrA) -> Self { + Self::InvalidSOrA(value) + } + } + impl ::core::convert::From for RouterErrors { + fn from(value: InvalidSignature) -> Self { + Self::InvalidSignature(value) + } + } + impl ::core::convert::From for RouterErrors { + fn from(value: NotInitializer) -> Self { + Self::NotInitializer(value) + } + } + impl ::core::convert::From for RouterErrors { + fn from(value: TooManyTransactions) -> Self { + Self::TooManyTransactions(value) + } + } + #[derive( + Clone, + ::ethers_contract::EthEvent, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethevent(name = "Executed", abi = "Executed(uint256,bytes32,uint256)")] + pub struct ExecutedFilter { + pub nonce: ::ethers_core::types::U256, + pub batch: [u8; 32], + pub success: ::ethers_core::types::U256, + } + ///Container type for all input parameters for the `KEY_PARITY` function with signature `KEY_PARITY()` and selector `0x7e7777a7` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "KEY_PARITY", abi = "KEY_PARITY()")] + pub struct KeyParityCall; + ///Container type for all input parameters for the `Q` function with signature `Q()` and selector `0xe493ef8c` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "Q", abi = "Q()")] + pub struct QCall; + ///Container type for all input parameters for the `execute` function with signature `execute((address,uint256,bytes)[],(bytes32,bytes32))` and selector `0xb839b1a1` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall( + name = "execute", + abi = "execute((address,uint256,bytes)[],(bytes32,bytes32))" + )] + pub struct ExecuteCall { + pub transactions: ::std::vec::Vec, + pub sig: Signature, + } + ///Container type for all input parameters for the `initSeraiKey` function with signature `initSeraiKey(bytes32)` and selector `0x3d54f51e` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "initSeraiKey", abi = "initSeraiKey(bytes32)")] + pub struct InitSeraiKeyCall { + pub serai_key: [u8; 32], + } + ///Container type for all input parameters for the `initializer` function with signature `initializer()` and selector `0x9ce110d7` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "initializer", abi = "initializer()")] + pub struct InitializerCall; + ///Container type for all input parameters for the `nonce` function with signature `nonce()` and selector `0xaffed0e0` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "nonce", abi = "nonce()")] + pub struct NonceCall; + ///Container type for all input parameters for the `seraiKey` function with signature `seraiKey()` and selector `0x9d6eea0a` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "seraiKey", abi = "seraiKey()")] + pub struct SeraiKeyCall; + ///Container type for all input parameters for the `updateSeraiKey` function with signature `updateSeraiKey(bytes32,(bytes32,bytes32))` and selector `0xb5071c6a` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall( + name = "updateSeraiKey", + abi = "updateSeraiKey(bytes32,(bytes32,bytes32))" + )] + pub struct UpdateSeraiKeyCall { + pub serai_key: [u8; 32], + pub sig: Signature, + } + ///Container type for all input parameters for the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "verify", abi = "verify(uint8,bytes32,bytes32,bytes32,bytes32)")] + pub struct VerifyCall { + pub parity: u8, + pub px: [u8; 32], + pub message: [u8; 32], + pub c: [u8; 32], + pub s: [u8; 32], + } + ///Container type for all of the contract's call + #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] + pub enum RouterCalls { + KeyParity(KeyParityCall), + Q(QCall), + Execute(ExecuteCall), + InitSeraiKey(InitSeraiKeyCall), + Initializer(InitializerCall), + Nonce(NonceCall), + SeraiKey(SeraiKeyCall), + UpdateSeraiKey(UpdateSeraiKeyCall), + Verify(VerifyCall), + } + impl ::ethers_core::abi::AbiDecode for RouterCalls { + fn decode( + data: impl AsRef<[u8]>, + ) -> ::core::result::Result { + let data = data.as_ref(); + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::KeyParity(decoded)); + } + if let Ok(decoded) = ::decode(data) { + return Ok(Self::Q(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::Execute(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::InitSeraiKey(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::Initializer(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::Nonce(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::SeraiKey(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::UpdateSeraiKey(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::Verify(decoded)); + } + Err(::ethers_core::abi::Error::InvalidData.into()) + } + } + impl ::ethers_core::abi::AbiEncode for RouterCalls { + fn encode(self) -> Vec { + match self { + Self::KeyParity(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::Q(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::Execute(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::InitSeraiKey(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::Initializer(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::Nonce(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::SeraiKey(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::UpdateSeraiKey(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::Verify(element) => ::ethers_core::abi::AbiEncode::encode(element), + } + } + } + impl ::core::fmt::Display for RouterCalls { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Self::KeyParity(element) => ::core::fmt::Display::fmt(element, f), + Self::Q(element) => ::core::fmt::Display::fmt(element, f), + Self::Execute(element) => ::core::fmt::Display::fmt(element, f), + Self::InitSeraiKey(element) => ::core::fmt::Display::fmt(element, f), + Self::Initializer(element) => ::core::fmt::Display::fmt(element, f), + Self::Nonce(element) => ::core::fmt::Display::fmt(element, f), + Self::SeraiKey(element) => ::core::fmt::Display::fmt(element, f), + Self::UpdateSeraiKey(element) => ::core::fmt::Display::fmt(element, f), + Self::Verify(element) => ::core::fmt::Display::fmt(element, f), + } + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: KeyParityCall) -> Self { + Self::KeyParity(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: QCall) -> Self { + Self::Q(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: ExecuteCall) -> Self { + Self::Execute(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: InitSeraiKeyCall) -> Self { + Self::InitSeraiKey(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: InitializerCall) -> Self { + Self::Initializer(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: NonceCall) -> Self { + Self::Nonce(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: SeraiKeyCall) -> Self { + Self::SeraiKey(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: UpdateSeraiKeyCall) -> Self { + Self::UpdateSeraiKey(value) + } + } + impl ::core::convert::From for RouterCalls { + fn from(value: VerifyCall) -> Self { + Self::Verify(value) + } + } + ///Container type for all return fields from the `KEY_PARITY` function with signature `KEY_PARITY()` and selector `0x7e7777a7` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct KeyParityReturn(pub u8); + ///Container type for all return fields from the `Q` function with signature `Q()` and selector `0xe493ef8c` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct QReturn(pub ::ethers_core::types::U256); + ///Container type for all return fields from the `initializer` function with signature `initializer()` and selector `0x9ce110d7` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct InitializerReturn(pub ::ethers_core::types::Address); + ///Container type for all return fields from the `nonce` function with signature `nonce()` and selector `0xaffed0e0` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct NonceReturn(pub ::ethers_core::types::U256); + ///Container type for all return fields from the `seraiKey` function with signature `seraiKey()` and selector `0x9d6eea0a` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct SeraiKeyReturn(pub [u8; 32]); + ///Container type for all return fields from the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct VerifyReturn(pub bool); + ///`OutInstruction(address,uint256,bytes)` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct OutInstruction { + pub to: ::ethers_core::types::Address, + pub value: ::ethers_core::types::U256, + pub data: ::ethers_core::types::Bytes, + } + ///`Signature(bytes32,bytes32)` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct Signature { + pub c: [u8; 32], + pub s: [u8; 32], + } +} diff --git a/coins/ethereum/src/abi/schnorr.rs b/coins/ethereum/src/abi/schnorr.rs new file mode 100644 index 000000000..f0c7ee4e3 --- /dev/null +++ b/coins/ethereum/src/abi/schnorr.rs @@ -0,0 +1,410 @@ +pub use schnorr::*; +/// This module was auto-generated with ethers-rs Abigen. +/// More information at: +#[allow( + clippy::enum_variant_names, + clippy::too_many_arguments, + clippy::upper_case_acronyms, + clippy::type_complexity, + dead_code, + non_camel_case_types, +)] +pub mod schnorr { + #[allow(deprecated)] + fn __abi() -> ::ethers_core::abi::Abi { + ::ethers_core::abi::ethabi::Contract { + constructor: ::core::option::Option::None, + functions: ::core::convert::From::from([ + ( + ::std::borrow::ToOwned::to_owned("Q"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("Q"), + inputs: ::std::vec![], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint256"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("verify"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("verify"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("parity"), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("uint8"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("px"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("message"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("c"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::borrow::ToOwned::to_owned("s"), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bytes32"), + ), + }, + ], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Bool, + internal_type: ::core::option::Option::Some( + ::std::borrow::ToOwned::to_owned("bool"), + ), + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, + }, + ], + ), + ]), + events: ::std::collections::BTreeMap::new(), + errors: ::core::convert::From::from([ + ( + ::std::borrow::ToOwned::to_owned("InvalidSOrA"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned("InvalidSOrA"), + inputs: ::std::vec![], + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("InvalidSignature"), + ::std::vec![ + ::ethers_core::abi::ethabi::AbiError { + name: ::std::borrow::ToOwned::to_owned("InvalidSignature"), + inputs: ::std::vec![], + }, + ], + ), + ]), + receive: false, + fallback: false, + } + } + ///The parsed JSON ABI of the contract. + pub static SCHNORR_ABI: ::ethers_contract::Lazy<::ethers_core::abi::Abi> = ::ethers_contract::Lazy::new( + __abi, + ); + pub struct Schnorr(::ethers_contract::Contract); + impl ::core::clone::Clone for Schnorr { + fn clone(&self) -> Self { + Self(::core::clone::Clone::clone(&self.0)) + } + } + impl ::core::ops::Deref for Schnorr { + type Target = ::ethers_contract::Contract; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl ::core::ops::DerefMut for Schnorr { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + impl ::core::fmt::Debug for Schnorr { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_tuple(::core::stringify!(Schnorr)).field(&self.address()).finish() + } + } + impl Schnorr { + /// Creates a new contract instance with the specified `ethers` client at + /// `address`. The contract derefs to a `ethers::Contract` object. + pub fn new>( + address: T, + client: ::std::sync::Arc, + ) -> Self { + Self( + ::ethers_contract::Contract::new( + address.into(), + SCHNORR_ABI.clone(), + client, + ), + ) + } + ///Calls the contract's `Q` (0xe493ef8c) function + pub fn q( + &self, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([228, 147, 239, 140], ()) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `verify` (0x9186da4c) function + pub fn verify( + &self, + parity: u8, + px: [u8; 32], + message: [u8; 32], + c: [u8; 32], + s: [u8; 32], + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([145, 134, 218, 76], (parity, px, message, c, s)) + .expect("method not found (this should never happen)") + } + } + impl From<::ethers_contract::Contract> + for Schnorr { + fn from(contract: ::ethers_contract::Contract) -> Self { + Self::new(contract.address(), contract.client()) + } + } + ///Custom Error type `InvalidSOrA` with signature `InvalidSOrA()` and selector `0x4e99a12e` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "InvalidSOrA", abi = "InvalidSOrA()")] + pub struct InvalidSOrA; + ///Custom Error type `InvalidSignature` with signature `InvalidSignature()` and selector `0x8baa579f` + #[derive( + Clone, + ::ethers_contract::EthError, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[etherror(name = "InvalidSignature", abi = "InvalidSignature()")] + pub struct InvalidSignature; + ///Container type for all of the contract's custom errors + #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] + pub enum SchnorrErrors { + InvalidSOrA(InvalidSOrA), + InvalidSignature(InvalidSignature), + /// The standard solidity revert string, with selector + /// Error(string) -- 0x08c379a0 + RevertString(::std::string::String), + } + impl ::ethers_core::abi::AbiDecode for SchnorrErrors { + fn decode( + data: impl AsRef<[u8]>, + ) -> ::core::result::Result { + let data = data.as_ref(); + if let Ok(decoded) = <::std::string::String as ::ethers_core::abi::AbiDecode>::decode( + data, + ) { + return Ok(Self::RevertString(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::InvalidSOrA(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::InvalidSignature(decoded)); + } + Err(::ethers_core::abi::Error::InvalidData.into()) + } + } + impl ::ethers_core::abi::AbiEncode for SchnorrErrors { + fn encode(self) -> ::std::vec::Vec { + match self { + Self::InvalidSOrA(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::InvalidSignature(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::RevertString(s) => ::ethers_core::abi::AbiEncode::encode(s), + } + } + } + impl ::ethers_contract::ContractRevert for SchnorrErrors { + fn valid_selector(selector: [u8; 4]) -> bool { + match selector { + [0x08, 0xc3, 0x79, 0xa0] => true, + _ if selector + == ::selector() => true, + _ if selector + == ::selector() => { + true + } + _ => false, + } + } + } + impl ::core::fmt::Display for SchnorrErrors { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Self::InvalidSOrA(element) => ::core::fmt::Display::fmt(element, f), + Self::InvalidSignature(element) => ::core::fmt::Display::fmt(element, f), + Self::RevertString(s) => ::core::fmt::Display::fmt(s, f), + } + } + } + impl ::core::convert::From<::std::string::String> for SchnorrErrors { + fn from(value: String) -> Self { + Self::RevertString(value) + } + } + impl ::core::convert::From for SchnorrErrors { + fn from(value: InvalidSOrA) -> Self { + Self::InvalidSOrA(value) + } + } + impl ::core::convert::From for SchnorrErrors { + fn from(value: InvalidSignature) -> Self { + Self::InvalidSignature(value) + } + } + ///Container type for all input parameters for the `Q` function with signature `Q()` and selector `0xe493ef8c` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "Q", abi = "Q()")] + pub struct QCall; + ///Container type for all input parameters for the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "verify", abi = "verify(uint8,bytes32,bytes32,bytes32,bytes32)")] + pub struct VerifyCall { + pub parity: u8, + pub px: [u8; 32], + pub message: [u8; 32], + pub c: [u8; 32], + pub s: [u8; 32], + } + ///Container type for all of the contract's call + #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] + pub enum SchnorrCalls { + Q(QCall), + Verify(VerifyCall), + } + impl ::ethers_core::abi::AbiDecode for SchnorrCalls { + fn decode( + data: impl AsRef<[u8]>, + ) -> ::core::result::Result { + let data = data.as_ref(); + if let Ok(decoded) = ::decode(data) { + return Ok(Self::Q(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::Verify(decoded)); + } + Err(::ethers_core::abi::Error::InvalidData.into()) + } + } + impl ::ethers_core::abi::AbiEncode for SchnorrCalls { + fn encode(self) -> Vec { + match self { + Self::Q(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::Verify(element) => ::ethers_core::abi::AbiEncode::encode(element), + } + } + } + impl ::core::fmt::Display for SchnorrCalls { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Self::Q(element) => ::core::fmt::Display::fmt(element, f), + Self::Verify(element) => ::core::fmt::Display::fmt(element, f), + } + } + } + impl ::core::convert::From for SchnorrCalls { + fn from(value: QCall) -> Self { + Self::Q(value) + } + } + impl ::core::convert::From for SchnorrCalls { + fn from(value: VerifyCall) -> Self { + Self::Verify(value) + } + } + ///Container type for all return fields from the `Q` function with signature `Q()` and selector `0xe493ef8c` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct QReturn(pub ::ethers_core::types::U256); + ///Container type for all return fields from the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct VerifyReturn(pub bool); +} From 04fcb2bba3b76f183e65d665727828d78e3f0438 Mon Sep 17 00:00:00 2001 From: akildemir Date: Fri, 3 May 2024 13:34:01 +0300 Subject: [PATCH 11/18] fix rotation test --- substrate/client/tests/validator_sets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/client/tests/validator_sets.rs b/substrate/client/tests/validator_sets.rs index 8ae150eca..a44a0ac43 100644 --- a/substrate/client/tests/validator_sets.rs +++ b/substrate/client/tests/validator_sets.rs @@ -14,7 +14,7 @@ use serai_client::{ mod common; use common::validator_sets::{set_keys, allocate_stake, deallocate_stake}; -const EPOCH_INTERVAL: u64 = 5; +const EPOCH_INTERVAL: u64 = 300; serai_test!( set_keys_test: (|serai: Serai| async move { From 28edc6615e39975858750d186501e0d192d6171b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Jun 2024 09:40:13 -0400 Subject: [PATCH 12/18] Finish merging develop --- substrate/abi/Cargo.toml | 2 +- substrate/client/src/serai/mod.rs | 2 +- substrate/genesis-liquidity/pallet/Cargo.toml | 1 + substrate/runtime/src/abi.rs | 22 ++++++++++++++++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/substrate/abi/Cargo.toml b/substrate/abi/Cargo.toml index 8212e4359..c2947aaab 100644 --- a/substrate/abi/Cargo.toml +++ b/substrate/abi/Cargo.toml @@ -33,7 +33,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur serai-primitives = { path = "../primitives", version = "0.1", default-features = false } serai-coins-primitives = { path = "../coins/primitives", version = "0.1", default-features = false } serai-validator-sets-primitives = { path = "../validator-sets/primitives", version = "0.1", default-features = false } -serai-genesis-liquidity-primitives = { path = "../genesis-liquidity/primitives", version = "0.1" } +serai-genesis-liquidity-primitives = { path = "../genesis-liquidity/primitives", version = "0.1", default-features = false } serai-in-instructions-primitives = { path = "../in-instructions/primitives", version = "0.1", default-features = false } serai-signals-primitives = { path = "../signals/primitives", version = "0.1", default-features = false } diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index 93060c41c..6048b6db7 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -204,7 +204,7 @@ impl Serai { let validators: String = self .call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())]) .await?; - let bytes = hex_decode(hash) + let bytes = hex_decode(validators) .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; let r = Vec::::decode(&mut bytes.as_slice()) .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml index 5ce41383a..068902d90 100644 --- a/substrate/genesis-liquidity/pallet/Cargo.toml +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -56,6 +56,7 @@ std = [ "genesis-liquidity-primitives/std", "validator-sets-primitives/std", ] +try-runtime = [] # TODO fast-epoch = [] default = ["std"] diff --git a/substrate/runtime/src/abi.rs b/substrate/runtime/src/abi.rs index 45c2aa334..146ce8aec 100644 --- a/substrate/runtime/src/abi.rs +++ b/substrate/runtime/src/abi.rs @@ -7,7 +7,7 @@ use serai_abi::Call; use crate::{ Vec, primitives::{PublicKey, SeraiAddress}, - timestamp, coins, dex, + timestamp, coins, dex, genesis_liquidity, validator_sets::{self, MembershipProof}, in_instructions, signals, babe, grandpa, RuntimeCall, }; @@ -89,6 +89,17 @@ impl From for RuntimeCall { send_to: send_to.into(), }), }, + Call::GenesisLiquidity(gl) => match gl { + serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } => { + RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::remove_coin_liquidity { balance }) + } + serai_abi::genesis_liquidity::Call::set_initial_price { prices, signature } => { + RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::set_initial_price { + prices, + signature, + }) + } + }, Call::ValidatorSets(vs) => match vs { serai_abi::validator_sets::Call::set_keys { network, @@ -261,6 +272,15 @@ impl TryInto for RuntimeCall { } _ => Err(())?, }), + RuntimeCall::GenesisLiquidity(call) => Call::GenesisLiquidity(match call { + genesis_liquidity::Call::remove_coin_liquidity { balance } => { + serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } + } + genesis_liquidity::Call::set_initial_price { prices, signature } => { + serai_abi::genesis_liquidity::Call::set_initial_price { prices, signature } + } + _ => Err(())?, + }), RuntimeCall::ValidatorSets(call) => Call::ValidatorSets(match call { validator_sets::Call::set_keys { network, removed_participants, key_pair, signature } => { serai_abi::validator_sets::Call::set_keys { From f75fe6749354790c71add7ee194a67815e774551 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Jun 2024 09:44:01 -0400 Subject: [PATCH 13/18] Remove accidentally committed ETH files --- coins/ethereum/src/abi/router.rs | 1164 ----------------------------- coins/ethereum/src/abi/schnorr.rs | 410 ---------- deny.toml | 2 +- 3 files changed, 1 insertion(+), 1575 deletions(-) delete mode 100644 coins/ethereum/src/abi/router.rs delete mode 100644 coins/ethereum/src/abi/schnorr.rs diff --git a/coins/ethereum/src/abi/router.rs b/coins/ethereum/src/abi/router.rs deleted file mode 100644 index 3b7e6f988..000000000 --- a/coins/ethereum/src/abi/router.rs +++ /dev/null @@ -1,1164 +0,0 @@ -pub use router::*; -/// This module was auto-generated with ethers-rs Abigen. -/// More information at: -#[allow( - clippy::enum_variant_names, - clippy::too_many_arguments, - clippy::upper_case_acronyms, - clippy::type_complexity, - dead_code, - non_camel_case_types, -)] -pub mod router { - #[allow(deprecated)] - fn __abi() -> ::ethers_core::abi::Abi { - ::ethers_core::abi::ethabi::Contract { - constructor: ::core::option::Option::Some(::ethers_core::abi::ethabi::Constructor { - inputs: ::std::vec![], - }), - functions: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("KEY_PARITY"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("KEY_PARITY"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint8"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("Q"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("Q"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("execute"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("execute"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("transactions"), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Address, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Bytes, - ], - ), - ), - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned( - "struct Router.OutInstruction[]", - ), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("sig"), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ], - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("struct Router.Signature"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("initSeraiKey"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("initSeraiKey"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("_seraiKey"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("initializer"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("initializer"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("nonce"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("nonce"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("seraiKey"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("seraiKey"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("updateSeraiKey"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("updateSeraiKey"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("_seraiKey"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("sig"), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ], - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("struct Router.Signature"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("verify"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("verify"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("parity"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint8"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("px"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("message"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("c"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("s"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ]), - events: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("Executed"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("Executed"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("nonce"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("batch"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("success"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ]), - errors: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("AlreadyInitialized"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned("AlreadyInitialized"), - inputs: ::std::vec![], - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("InvalidKey"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned("InvalidKey"), - inputs: ::std::vec![], - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("InvalidSOrA"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned("InvalidSOrA"), - inputs: ::std::vec![], - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("InvalidSignature"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned("InvalidSignature"), - inputs: ::std::vec![], - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("NotInitializer"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned("NotInitializer"), - inputs: ::std::vec![], - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("TooManyTransactions"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned( - "TooManyTransactions", - ), - inputs: ::std::vec![], - }, - ], - ), - ]), - receive: false, - fallback: false, - } - } - ///The parsed JSON ABI of the contract. - pub static ROUTER_ABI: ::ethers_contract::Lazy<::ethers_core::abi::Abi> = ::ethers_contract::Lazy::new( - __abi, - ); - pub struct Router(::ethers_contract::Contract); - impl ::core::clone::Clone for Router { - fn clone(&self) -> Self { - Self(::core::clone::Clone::clone(&self.0)) - } - } - impl ::core::ops::Deref for Router { - type Target = ::ethers_contract::Contract; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl ::core::ops::DerefMut for Router { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - impl ::core::fmt::Debug for Router { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(::core::stringify!(Router)).field(&self.address()).finish() - } - } - impl Router { - /// Creates a new contract instance with the specified `ethers` client at - /// `address`. The contract derefs to a `ethers::Contract` object. - pub fn new>( - address: T, - client: ::std::sync::Arc, - ) -> Self { - Self( - ::ethers_contract::Contract::new( - address.into(), - ROUTER_ABI.clone(), - client, - ), - ) - } - ///Calls the contract's `KEY_PARITY` (0x7e7777a7) function - pub fn key_parity(&self) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([126, 119, 119, 167], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `Q` (0xe493ef8c) function - pub fn q( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([228, 147, 239, 140], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `execute` (0xb839b1a1) function - pub fn execute( - &self, - transactions: ::std::vec::Vec, - sig: Signature, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([184, 57, 177, 161], (transactions, sig)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `initSeraiKey` (0x3d54f51e) function - pub fn init_serai_key( - &self, - serai_key: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([61, 84, 245, 30], serai_key) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `initializer` (0x9ce110d7) function - pub fn initializer( - &self, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::ethers_core::types::Address, - > { - self.0 - .method_hash([156, 225, 16, 215], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `nonce` (0xaffed0e0) function - pub fn nonce( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([175, 254, 208, 224], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `seraiKey` (0x9d6eea0a) function - pub fn serai_key( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([157, 110, 234, 10], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `updateSeraiKey` (0xb5071c6a) function - pub fn update_serai_key( - &self, - serai_key: [u8; 32], - sig: Signature, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([181, 7, 28, 106], (serai_key, sig)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `verify` (0x9186da4c) function - pub fn verify( - &self, - parity: u8, - px: [u8; 32], - message: [u8; 32], - c: [u8; 32], - s: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([145, 134, 218, 76], (parity, px, message, c, s)) - .expect("method not found (this should never happen)") - } - ///Gets the contract's `Executed` event - pub fn executed_filter( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, ExecutedFilter> { - self.0.event() - } - /// Returns an `Event` builder for all the events of this contract. - pub fn events( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, ExecutedFilter> { - self.0.event_with_filter(::core::default::Default::default()) - } - } - impl From<::ethers_contract::Contract> - for Router { - fn from(contract: ::ethers_contract::Contract) -> Self { - Self::new(contract.address(), contract.client()) - } - } - ///Custom Error type `AlreadyInitialized` with signature `AlreadyInitialized()` and selector `0x0dc149f0` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "AlreadyInitialized", abi = "AlreadyInitialized()")] - pub struct AlreadyInitialized; - ///Custom Error type `InvalidKey` with signature `InvalidKey()` and selector `0x76d4e1e8` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "InvalidKey", abi = "InvalidKey()")] - pub struct InvalidKey; - ///Custom Error type `InvalidSOrA` with signature `InvalidSOrA()` and selector `0x4e99a12e` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "InvalidSOrA", abi = "InvalidSOrA()")] - pub struct InvalidSOrA; - ///Custom Error type `InvalidSignature` with signature `InvalidSignature()` and selector `0x8baa579f` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "InvalidSignature", abi = "InvalidSignature()")] - pub struct InvalidSignature; - ///Custom Error type `NotInitializer` with signature `NotInitializer()` and selector `0xceeb95b3` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "NotInitializer", abi = "NotInitializer()")] - pub struct NotInitializer; - ///Custom Error type `TooManyTransactions` with signature `TooManyTransactions()` and selector `0xfb4593ba` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "TooManyTransactions", abi = "TooManyTransactions()")] - pub struct TooManyTransactions; - ///Container type for all of the contract's custom errors - #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] - pub enum RouterErrors { - AlreadyInitialized(AlreadyInitialized), - InvalidKey(InvalidKey), - InvalidSOrA(InvalidSOrA), - InvalidSignature(InvalidSignature), - NotInitializer(NotInitializer), - TooManyTransactions(TooManyTransactions), - /// The standard solidity revert string, with selector - /// Error(string) -- 0x08c379a0 - RevertString(::std::string::String), - } - impl ::ethers_core::abi::AbiDecode for RouterErrors { - fn decode( - data: impl AsRef<[u8]>, - ) -> ::core::result::Result { - let data = data.as_ref(); - if let Ok(decoded) = <::std::string::String as ::ethers_core::abi::AbiDecode>::decode( - data, - ) { - return Ok(Self::RevertString(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::AlreadyInitialized(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::InvalidKey(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::InvalidSOrA(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::InvalidSignature(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::NotInitializer(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::TooManyTransactions(decoded)); - } - Err(::ethers_core::abi::Error::InvalidData.into()) - } - } - impl ::ethers_core::abi::AbiEncode for RouterErrors { - fn encode(self) -> ::std::vec::Vec { - match self { - Self::AlreadyInitialized(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::InvalidKey(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::InvalidSOrA(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::InvalidSignature(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::NotInitializer(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::TooManyTransactions(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RevertString(s) => ::ethers_core::abi::AbiEncode::encode(s), - } - } - } - impl ::ethers_contract::ContractRevert for RouterErrors { - fn valid_selector(selector: [u8; 4]) -> bool { - match selector { - [0x08, 0xc3, 0x79, 0xa0] => true, - _ if selector - == ::selector() => { - true - } - _ if selector - == ::selector() => true, - _ if selector - == ::selector() => true, - _ if selector - == ::selector() => { - true - } - _ if selector - == ::selector() => { - true - } - _ if selector - == ::selector() => { - true - } - _ => false, - } - } - } - impl ::core::fmt::Display for RouterErrors { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::AlreadyInitialized(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::InvalidKey(element) => ::core::fmt::Display::fmt(element, f), - Self::InvalidSOrA(element) => ::core::fmt::Display::fmt(element, f), - Self::InvalidSignature(element) => ::core::fmt::Display::fmt(element, f), - Self::NotInitializer(element) => ::core::fmt::Display::fmt(element, f), - Self::TooManyTransactions(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::RevertString(s) => ::core::fmt::Display::fmt(s, f), - } - } - } - impl ::core::convert::From<::std::string::String> for RouterErrors { - fn from(value: String) -> Self { - Self::RevertString(value) - } - } - impl ::core::convert::From for RouterErrors { - fn from(value: AlreadyInitialized) -> Self { - Self::AlreadyInitialized(value) - } - } - impl ::core::convert::From for RouterErrors { - fn from(value: InvalidKey) -> Self { - Self::InvalidKey(value) - } - } - impl ::core::convert::From for RouterErrors { - fn from(value: InvalidSOrA) -> Self { - Self::InvalidSOrA(value) - } - } - impl ::core::convert::From for RouterErrors { - fn from(value: InvalidSignature) -> Self { - Self::InvalidSignature(value) - } - } - impl ::core::convert::From for RouterErrors { - fn from(value: NotInitializer) -> Self { - Self::NotInitializer(value) - } - } - impl ::core::convert::From for RouterErrors { - fn from(value: TooManyTransactions) -> Self { - Self::TooManyTransactions(value) - } - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "Executed", abi = "Executed(uint256,bytes32,uint256)")] - pub struct ExecutedFilter { - pub nonce: ::ethers_core::types::U256, - pub batch: [u8; 32], - pub success: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `KEY_PARITY` function with signature `KEY_PARITY()` and selector `0x7e7777a7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "KEY_PARITY", abi = "KEY_PARITY()")] - pub struct KeyParityCall; - ///Container type for all input parameters for the `Q` function with signature `Q()` and selector `0xe493ef8c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "Q", abi = "Q()")] - pub struct QCall; - ///Container type for all input parameters for the `execute` function with signature `execute((address,uint256,bytes)[],(bytes32,bytes32))` and selector `0xb839b1a1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "execute", - abi = "execute((address,uint256,bytes)[],(bytes32,bytes32))" - )] - pub struct ExecuteCall { - pub transactions: ::std::vec::Vec, - pub sig: Signature, - } - ///Container type for all input parameters for the `initSeraiKey` function with signature `initSeraiKey(bytes32)` and selector `0x3d54f51e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "initSeraiKey", abi = "initSeraiKey(bytes32)")] - pub struct InitSeraiKeyCall { - pub serai_key: [u8; 32], - } - ///Container type for all input parameters for the `initializer` function with signature `initializer()` and selector `0x9ce110d7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "initializer", abi = "initializer()")] - pub struct InitializerCall; - ///Container type for all input parameters for the `nonce` function with signature `nonce()` and selector `0xaffed0e0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "nonce", abi = "nonce()")] - pub struct NonceCall; - ///Container type for all input parameters for the `seraiKey` function with signature `seraiKey()` and selector `0x9d6eea0a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "seraiKey", abi = "seraiKey()")] - pub struct SeraiKeyCall; - ///Container type for all input parameters for the `updateSeraiKey` function with signature `updateSeraiKey(bytes32,(bytes32,bytes32))` and selector `0xb5071c6a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "updateSeraiKey", - abi = "updateSeraiKey(bytes32,(bytes32,bytes32))" - )] - pub struct UpdateSeraiKeyCall { - pub serai_key: [u8; 32], - pub sig: Signature, - } - ///Container type for all input parameters for the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "verify", abi = "verify(uint8,bytes32,bytes32,bytes32,bytes32)")] - pub struct VerifyCall { - pub parity: u8, - pub px: [u8; 32], - pub message: [u8; 32], - pub c: [u8; 32], - pub s: [u8; 32], - } - ///Container type for all of the contract's call - #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] - pub enum RouterCalls { - KeyParity(KeyParityCall), - Q(QCall), - Execute(ExecuteCall), - InitSeraiKey(InitSeraiKeyCall), - Initializer(InitializerCall), - Nonce(NonceCall), - SeraiKey(SeraiKeyCall), - UpdateSeraiKey(UpdateSeraiKeyCall), - Verify(VerifyCall), - } - impl ::ethers_core::abi::AbiDecode for RouterCalls { - fn decode( - data: impl AsRef<[u8]>, - ) -> ::core::result::Result { - let data = data.as_ref(); - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::KeyParity(decoded)); - } - if let Ok(decoded) = ::decode(data) { - return Ok(Self::Q(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::Execute(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::InitSeraiKey(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::Initializer(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::Nonce(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::SeraiKey(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::UpdateSeraiKey(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::Verify(decoded)); - } - Err(::ethers_core::abi::Error::InvalidData.into()) - } - } - impl ::ethers_core::abi::AbiEncode for RouterCalls { - fn encode(self) -> Vec { - match self { - Self::KeyParity(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Q(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Execute(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::InitSeraiKey(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Initializer(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Nonce(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::SeraiKey(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::UpdateSeraiKey(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Verify(element) => ::ethers_core::abi::AbiEncode::encode(element), - } - } - } - impl ::core::fmt::Display for RouterCalls { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::KeyParity(element) => ::core::fmt::Display::fmt(element, f), - Self::Q(element) => ::core::fmt::Display::fmt(element, f), - Self::Execute(element) => ::core::fmt::Display::fmt(element, f), - Self::InitSeraiKey(element) => ::core::fmt::Display::fmt(element, f), - Self::Initializer(element) => ::core::fmt::Display::fmt(element, f), - Self::Nonce(element) => ::core::fmt::Display::fmt(element, f), - Self::SeraiKey(element) => ::core::fmt::Display::fmt(element, f), - Self::UpdateSeraiKey(element) => ::core::fmt::Display::fmt(element, f), - Self::Verify(element) => ::core::fmt::Display::fmt(element, f), - } - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: KeyParityCall) -> Self { - Self::KeyParity(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: QCall) -> Self { - Self::Q(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: ExecuteCall) -> Self { - Self::Execute(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: InitSeraiKeyCall) -> Self { - Self::InitSeraiKey(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: InitializerCall) -> Self { - Self::Initializer(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: NonceCall) -> Self { - Self::Nonce(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: SeraiKeyCall) -> Self { - Self::SeraiKey(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: UpdateSeraiKeyCall) -> Self { - Self::UpdateSeraiKey(value) - } - } - impl ::core::convert::From for RouterCalls { - fn from(value: VerifyCall) -> Self { - Self::Verify(value) - } - } - ///Container type for all return fields from the `KEY_PARITY` function with signature `KEY_PARITY()` and selector `0x7e7777a7` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct KeyParityReturn(pub u8); - ///Container type for all return fields from the `Q` function with signature `Q()` and selector `0xe493ef8c` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct QReturn(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `initializer` function with signature `initializer()` and selector `0x9ce110d7` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct InitializerReturn(pub ::ethers_core::types::Address); - ///Container type for all return fields from the `nonce` function with signature `nonce()` and selector `0xaffed0e0` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct NonceReturn(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `seraiKey` function with signature `seraiKey()` and selector `0x9d6eea0a` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SeraiKeyReturn(pub [u8; 32]); - ///Container type for all return fields from the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct VerifyReturn(pub bool); - ///`OutInstruction(address,uint256,bytes)` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct OutInstruction { - pub to: ::ethers_core::types::Address, - pub value: ::ethers_core::types::U256, - pub data: ::ethers_core::types::Bytes, - } - ///`Signature(bytes32,bytes32)` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct Signature { - pub c: [u8; 32], - pub s: [u8; 32], - } -} diff --git a/coins/ethereum/src/abi/schnorr.rs b/coins/ethereum/src/abi/schnorr.rs deleted file mode 100644 index f0c7ee4e3..000000000 --- a/coins/ethereum/src/abi/schnorr.rs +++ /dev/null @@ -1,410 +0,0 @@ -pub use schnorr::*; -/// This module was auto-generated with ethers-rs Abigen. -/// More information at: -#[allow( - clippy::enum_variant_names, - clippy::too_many_arguments, - clippy::upper_case_acronyms, - clippy::type_complexity, - dead_code, - non_camel_case_types, -)] -pub mod schnorr { - #[allow(deprecated)] - fn __abi() -> ::ethers_core::abi::Abi { - ::ethers_core::abi::ethabi::Contract { - constructor: ::core::option::Option::None, - functions: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("Q"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("Q"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("verify"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("verify"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("parity"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint8"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("px"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("message"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("c"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("s"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ]), - events: ::std::collections::BTreeMap::new(), - errors: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("InvalidSOrA"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned("InvalidSOrA"), - inputs: ::std::vec![], - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("InvalidSignature"), - ::std::vec![ - ::ethers_core::abi::ethabi::AbiError { - name: ::std::borrow::ToOwned::to_owned("InvalidSignature"), - inputs: ::std::vec![], - }, - ], - ), - ]), - receive: false, - fallback: false, - } - } - ///The parsed JSON ABI of the contract. - pub static SCHNORR_ABI: ::ethers_contract::Lazy<::ethers_core::abi::Abi> = ::ethers_contract::Lazy::new( - __abi, - ); - pub struct Schnorr(::ethers_contract::Contract); - impl ::core::clone::Clone for Schnorr { - fn clone(&self) -> Self { - Self(::core::clone::Clone::clone(&self.0)) - } - } - impl ::core::ops::Deref for Schnorr { - type Target = ::ethers_contract::Contract; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl ::core::ops::DerefMut for Schnorr { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - impl ::core::fmt::Debug for Schnorr { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(::core::stringify!(Schnorr)).field(&self.address()).finish() - } - } - impl Schnorr { - /// Creates a new contract instance with the specified `ethers` client at - /// `address`. The contract derefs to a `ethers::Contract` object. - pub fn new>( - address: T, - client: ::std::sync::Arc, - ) -> Self { - Self( - ::ethers_contract::Contract::new( - address.into(), - SCHNORR_ABI.clone(), - client, - ), - ) - } - ///Calls the contract's `Q` (0xe493ef8c) function - pub fn q( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([228, 147, 239, 140], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `verify` (0x9186da4c) function - pub fn verify( - &self, - parity: u8, - px: [u8; 32], - message: [u8; 32], - c: [u8; 32], - s: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([145, 134, 218, 76], (parity, px, message, c, s)) - .expect("method not found (this should never happen)") - } - } - impl From<::ethers_contract::Contract> - for Schnorr { - fn from(contract: ::ethers_contract::Contract) -> Self { - Self::new(contract.address(), contract.client()) - } - } - ///Custom Error type `InvalidSOrA` with signature `InvalidSOrA()` and selector `0x4e99a12e` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "InvalidSOrA", abi = "InvalidSOrA()")] - pub struct InvalidSOrA; - ///Custom Error type `InvalidSignature` with signature `InvalidSignature()` and selector `0x8baa579f` - #[derive( - Clone, - ::ethers_contract::EthError, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[etherror(name = "InvalidSignature", abi = "InvalidSignature()")] - pub struct InvalidSignature; - ///Container type for all of the contract's custom errors - #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] - pub enum SchnorrErrors { - InvalidSOrA(InvalidSOrA), - InvalidSignature(InvalidSignature), - /// The standard solidity revert string, with selector - /// Error(string) -- 0x08c379a0 - RevertString(::std::string::String), - } - impl ::ethers_core::abi::AbiDecode for SchnorrErrors { - fn decode( - data: impl AsRef<[u8]>, - ) -> ::core::result::Result { - let data = data.as_ref(); - if let Ok(decoded) = <::std::string::String as ::ethers_core::abi::AbiDecode>::decode( - data, - ) { - return Ok(Self::RevertString(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::InvalidSOrA(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::InvalidSignature(decoded)); - } - Err(::ethers_core::abi::Error::InvalidData.into()) - } - } - impl ::ethers_core::abi::AbiEncode for SchnorrErrors { - fn encode(self) -> ::std::vec::Vec { - match self { - Self::InvalidSOrA(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::InvalidSignature(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RevertString(s) => ::ethers_core::abi::AbiEncode::encode(s), - } - } - } - impl ::ethers_contract::ContractRevert for SchnorrErrors { - fn valid_selector(selector: [u8; 4]) -> bool { - match selector { - [0x08, 0xc3, 0x79, 0xa0] => true, - _ if selector - == ::selector() => true, - _ if selector - == ::selector() => { - true - } - _ => false, - } - } - } - impl ::core::fmt::Display for SchnorrErrors { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::InvalidSOrA(element) => ::core::fmt::Display::fmt(element, f), - Self::InvalidSignature(element) => ::core::fmt::Display::fmt(element, f), - Self::RevertString(s) => ::core::fmt::Display::fmt(s, f), - } - } - } - impl ::core::convert::From<::std::string::String> for SchnorrErrors { - fn from(value: String) -> Self { - Self::RevertString(value) - } - } - impl ::core::convert::From for SchnorrErrors { - fn from(value: InvalidSOrA) -> Self { - Self::InvalidSOrA(value) - } - } - impl ::core::convert::From for SchnorrErrors { - fn from(value: InvalidSignature) -> Self { - Self::InvalidSignature(value) - } - } - ///Container type for all input parameters for the `Q` function with signature `Q()` and selector `0xe493ef8c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "Q", abi = "Q()")] - pub struct QCall; - ///Container type for all input parameters for the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "verify", abi = "verify(uint8,bytes32,bytes32,bytes32,bytes32)")] - pub struct VerifyCall { - pub parity: u8, - pub px: [u8; 32], - pub message: [u8; 32], - pub c: [u8; 32], - pub s: [u8; 32], - } - ///Container type for all of the contract's call - #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] - pub enum SchnorrCalls { - Q(QCall), - Verify(VerifyCall), - } - impl ::ethers_core::abi::AbiDecode for SchnorrCalls { - fn decode( - data: impl AsRef<[u8]>, - ) -> ::core::result::Result { - let data = data.as_ref(); - if let Ok(decoded) = ::decode(data) { - return Ok(Self::Q(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::Verify(decoded)); - } - Err(::ethers_core::abi::Error::InvalidData.into()) - } - } - impl ::ethers_core::abi::AbiEncode for SchnorrCalls { - fn encode(self) -> Vec { - match self { - Self::Q(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Verify(element) => ::ethers_core::abi::AbiEncode::encode(element), - } - } - } - impl ::core::fmt::Display for SchnorrCalls { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::Q(element) => ::core::fmt::Display::fmt(element, f), - Self::Verify(element) => ::core::fmt::Display::fmt(element, f), - } - } - } - impl ::core::convert::From for SchnorrCalls { - fn from(value: QCall) -> Self { - Self::Q(value) - } - } - impl ::core::convert::From for SchnorrCalls { - fn from(value: VerifyCall) -> Self { - Self::Verify(value) - } - } - ///Container type for all return fields from the `Q` function with signature `Q()` and selector `0xe493ef8c` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct QReturn(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `verify` function with signature `verify(uint8,bytes32,bytes32,bytes32,bytes32)` and selector `0x9186da4c` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct VerifyReturn(pub bool); -} diff --git a/deny.toml b/deny.toml index fef45d1f6..960250516 100644 --- a/deny.toml +++ b/deny.toml @@ -52,7 +52,7 @@ exceptions = [ { allow = ["AGPL-3.0"], name = "serai-coins-pallet" }, { allow = ["AGPL-3.0"], name = "serai-dex-pallet" }, - + { allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" }, { allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" }, From c14923facbd9013cd3c431d44bda6e07d1994a69 Mon Sep 17 00:00:00 2001 From: akildemir Date: Fri, 28 Jun 2024 16:17:57 +0300 Subject: [PATCH 14/18] fix pr comments --- Cargo.lock | 1 + substrate/abi/src/coins.rs | 9 - substrate/abi/src/lib.rs | 5 +- substrate/client/src/serai/dex.rs | 23 +- .../client/src/serai/genesis_liquidity.rs | 44 ++-- .../client/src/serai/liquidity_tokens.rs | 4 +- substrate/client/src/serai/mod.rs | 38 ++- substrate/client/tests/genesis_liquidity.rs | 86 +++--- substrate/dex/pallet/src/tests.rs | 2 +- substrate/genesis-liquidity/pallet/Cargo.toml | 2 + substrate/genesis-liquidity/pallet/src/lib.rs | 244 ++++++++++-------- .../genesis-liquidity/primitives/src/lib.rs | 9 +- substrate/node/src/chain_spec.rs | 3 - substrate/primitives/src/constants.rs | 29 +++ substrate/primitives/src/lib.rs | 3 + substrate/runtime/src/abi.rs | 8 +- substrate/runtime/src/lib.rs | 29 +-- 17 files changed, 256 insertions(+), 283 deletions(-) create mode 100644 substrate/primitives/src/constants.rs diff --git a/Cargo.lock b/Cargo.lock index 387f8979f..7d661d297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7966,6 +7966,7 @@ dependencies = [ "serai-dex-pallet", "serai-genesis-liquidity-primitives", "serai-primitives", + "serai-validator-sets-pallet", "serai-validator-sets-primitives", "sp-application-crypto", "sp-core", diff --git a/substrate/abi/src/coins.rs b/substrate/abi/src/coins.rs index 56255b0a8..9466db0f9 100644 --- a/substrate/abi/src/coins.rs +++ b/substrate/abi/src/coins.rs @@ -13,15 +13,6 @@ pub enum Call { burn_with_instruction { instruction: OutInstructionWithBalance }, } -#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] -pub enum LiquidityTokensCall { - transfer { to: SeraiAddress, balance: Balance }, - burn { balance: Balance }, -} - #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))] diff --git a/substrate/abi/src/lib.rs b/substrate/abi/src/lib.rs index aef03963e..ac8b8824c 100644 --- a/substrate/abi/src/lib.rs +++ b/substrate/abi/src/lib.rs @@ -12,6 +12,7 @@ pub mod system; pub mod timestamp; pub mod coins; +pub mod liquidity_tokens; pub mod dex; pub mod validator_sets; @@ -29,7 +30,7 @@ pub mod tx; pub enum Call { Timestamp(timestamp::Call), Coins(coins::Call), - LiquidityTokens(coins::LiquidityTokensCall), + LiquidityTokens(liquidity_tokens::Call), Dex(dex::Call), GenesisLiquidity(genesis_liquidity::Call), ValidatorSets(validator_sets::Call), @@ -51,7 +52,7 @@ pub enum Event { Timestamp, TransactionPayment(TransactionPaymentEvent), Coins(coins::Event), - LiquidityTokens(coins::Event), + LiquidityTokens(liquidity_tokens::Event), Dex(dex::Event), GenesisLiquidity(genesis_liquidity::Event), ValidatorSets(validator_sets::Event), diff --git a/substrate/client/src/serai/dex.rs b/substrate/client/src/serai/dex.rs index 183411257..d9edc56b6 100644 --- a/substrate/client/src/serai/dex.rs +++ b/substrate/client/src/serai/dex.rs @@ -3,7 +3,7 @@ use serai_abi::primitives::{SeraiAddress, Amount, Coin}; use scale::{decode_from_bytes, Encode}; -use crate::{SeraiError, hex_decode, TemporalSerai}; +use crate::{Serai, SeraiError, TemporalSerai}; pub type DexEvent = serai_abi::dex::Event; @@ -60,20 +60,19 @@ impl<'a> SeraiDex<'a> { }) } - pub async fn get_reserves( - &self, - coin1: Coin, - coin2: Coin, - ) -> Result, SeraiError> { - let hash = self + /// Returns the reserves of `coin:SRI` pool. + pub async fn get_reserves(&self, coin: Coin) -> Result, SeraiError> { + let reserves = self .0 .serai - .call("state_call", ["DexApi_get_reserves".to_string(), hex::encode((coin1, coin2).encode())]) + .call( + "state_call", + ["DexApi_get_reserves".to_string(), hex::encode((coin, Coin::Serai).encode())], + ) .await?; - let bytes = hex_decode(hash) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; - let resut = decode_from_bytes::>(bytes.into()) + let bytes = Serai::hex_decode(reserves)?; + let result = decode_from_bytes::>(bytes.into()) .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; - Ok(resut.map(|amounts| (Amount(amounts.0), Amount(amounts.1)))) + Ok(result.map(|amounts| (Amount(amounts.0), Amount(amounts.1)))) } } diff --git a/substrate/client/src/serai/genesis_liquidity.rs b/substrate/client/src/serai/genesis_liquidity.rs index b8882bd7a..2a2072f09 100644 --- a/substrate/client/src/serai/genesis_liquidity.rs +++ b/substrate/client/src/serai/genesis_liquidity.rs @@ -29,24 +29,6 @@ impl<'a> SeraiGenesisLiquidity<'a> { .await } - pub async fn liquidity_tokens( - &self, - address: &SeraiAddress, - coin: Coin, - ) -> Result { - Ok( - self - .0 - .storage( - PALLET, - "LiquidityTokensPerAddress", - (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), - ) - .await? - .unwrap_or(Amount(0)), - ) - } - pub fn set_initial_price(prices: Prices, signature: Signature) -> Transaction { Serai::unsigned(serai_abi::Call::GenesisLiquidity( serai_abi::genesis_liquidity::Call::set_initial_price { prices, signature }, @@ -59,15 +41,21 @@ impl<'a> SeraiGenesisLiquidity<'a> { }) } - pub async fn liquidity(&self, address: &SeraiAddress, coin: Coin) -> Option { - self - .0 - .storage( - PALLET, - "Liquidity", - (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), - ) - .await - .unwrap() + pub async fn liquidity(&self, address: &SeraiAddress, coin: Coin) -> Result { + Ok( + self + .0 + .storage( + PALLET, + "Liquidity", + (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), + ) + .await? + .unwrap_or(Amount(0)), + ) + } + + pub async fn supply(&self, coin: Coin) -> Result<(Amount, Amount), SeraiError> { + Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or((Amount(0), Amount(0)))) } } diff --git a/substrate/client/src/serai/liquidity_tokens.rs b/substrate/client/src/serai/liquidity_tokens.rs index 22fcd49e1..3e9052b2c 100644 --- a/substrate/client/src/serai/liquidity_tokens.rs +++ b/substrate/client/src/serai/liquidity_tokens.rs @@ -32,10 +32,10 @@ impl<'a> SeraiLiquidityTokens<'a> { } pub fn transfer(to: SeraiAddress, balance: Balance) -> serai_abi::Call { - serai_abi::Call::Coins(serai_abi::coins::Call::transfer { to, balance }) + serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::transfer { to, balance }) } pub fn burn(balance: Balance) -> serai_abi::Call { - serai_abi::Call::Coins(serai_abi::coins::Call::burn { balance }) + serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::burn { balance }) } } diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index 6048b6db7..dfd14779e 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -1,4 +1,3 @@ -use hex::FromHexError; use thiserror::Error; use async_lock::RwLock; @@ -87,14 +86,6 @@ impl<'a> Clone for TemporalSerai<'a> { } } -pub fn hex_decode(str: String) -> Result, FromHexError> { - if let Some(stripped) = str.strip_prefix("0x") { - hex::decode(stripped) - } else { - hex::decode(str) - } -} - impl Serai { pub async fn call( &self, @@ -147,11 +138,19 @@ impl Serai { } } + fn hex_decode(str: String) -> Result, SeraiError> { + (if let Some(stripped) = str.strip_prefix("0x") { + hex::decode(stripped) + } else { + hex::decode(str) + }) + .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string())) + } + pub async fn block_hash(&self, number: u64) -> Result, SeraiError> { let hash: Option = self.call("chain_getBlockHash", [number]).await?; let Some(hash) = hash else { return Ok(None) }; - hex_decode(hash) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))? + Self::hex_decode(hash)? .try_into() .map_err(|_| SeraiError::InvalidNode("didn't respond to getBlockHash with hash".to_string())) .map(Some) @@ -204,8 +203,7 @@ impl Serai { let validators: String = self .call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())]) .await?; - let bytes = hex_decode(validators) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; + let bytes = Self::hex_decode(validators)?; let r = Vec::::decode(&mut bytes.as_slice()) .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; Ok(r) @@ -213,12 +211,9 @@ impl Serai { pub async fn latest_finalized_block_hash(&self) -> Result<[u8; 32], SeraiError> { let hash: String = self.call("chain_getFinalizedHead", ()).await?; - hex_decode(hash) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))? - .try_into() - .map_err(|_| { - SeraiError::InvalidNode("didn't respond to getFinalizedHead with hash".to_string()) - }) + Self::hex_decode(hash)?.try_into().map_err(|_| { + SeraiError::InvalidNode("didn't respond to getFinalizedHead with hash".to_string()) + }) } pub async fn header(&self, hash: [u8; 32]) -> Result, SeraiError> { @@ -228,7 +223,7 @@ impl Serai { pub async fn block(&self, hash: [u8; 32]) -> Result, SeraiError> { let block: Option = self.call("chain_getBlockBin", [hex::encode(hash)]).await?; let Some(block) = block else { return Ok(None) }; - let Ok(bytes) = hex_decode(block) else { + let Ok(bytes) = Self::hex_decode(block) else { Err(SeraiError::InvalidNode("didn't return a hex-encoded block".to_string()))? }; let Ok(block) = Block::decode(&mut bytes.as_slice()) else { @@ -374,8 +369,7 @@ impl<'a> TemporalSerai<'a> { let res: Option = self.serai.call("state_getStorage", [hex::encode(full_key), hex::encode(self.block)]).await?; let Some(res) = res else { return Ok(None) }; - let res = hex_decode(res) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; + let res = Serai::hex_decode(res)?; Ok(Some(R::decode(&mut res.as_slice()).map_err(|_| { SeraiError::InvalidRuntime(format!( "different type present at storage location, raw value: {}", diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs index 9efc8f092..ca1a12750 100644 --- a/substrate/client/tests/genesis_liquidity.rs +++ b/substrate/client/tests/genesis_liquidity.rs @@ -9,7 +9,7 @@ use schnorrkel::Schnorrkel; use serai_client::{ genesis_liquidity::{ - primitives::{GENESIS_LIQUIDITY_ACCOUNT, GENESIS_SRI}, + primitives::{GENESIS_LIQUIDITY_ACCOUNT, GENESIS_LP_SHARES}, SeraiGenesisLiquidity, }, validator_sets::primitives::{musig_context, Session, ValidatorSet}, @@ -24,7 +24,7 @@ use sp_core::{sr25519::Signature, Pair as PairTrait}; use serai_client::{ primitives::{ - Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name, + Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name, GENESIS_SRI, }, in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch}, Serai, @@ -63,8 +63,6 @@ async fn test_genesis_liquidity(serai: Serai) { xmr_addresses.push((address, amount)); } } - btc_addresses.sort_by(|a1, a2| a1.0.cmp(&a2.0)); - xmr_addresses.sort_by(|a1, a2| a1.0.cmp(&a2.0)); // btc batch let mut block_hash = BlockHash([0; 32]); @@ -93,23 +91,25 @@ async fn test_genesis_liquidity(serai: Serai) { let batch = Batch { network: NetworkId::Monero, id: 0, block: block_hash, instructions: xmr_ins }; provide_batch(&serai, batch).await; - // set prices - let prices = Prices { bitcoin: 10u64.pow(8), monero: 184100, ethereum: 4785000, dai: 1500 }; - set_prices(&serai, &prices).await; - // wait until genesis ends.. tokio::time::timeout(tokio::time::Duration::from_secs(300), async { - while serai.latest_finalized_block().await.unwrap().number() < 25 { + while serai.latest_finalized_block().await.unwrap().number() < 10 { tokio::time::sleep(Duration::from_secs(6)).await; } }) .await .unwrap(); + // set prices + let prices = Prices { bitcoin: 10u64.pow(8), monero: 184100, ethereum: 4785000, dai: 1500 }; + set_prices(&serai, &prices).await; + + // wait little bit.. + tokio::time::sleep(Duration::from_secs(12)).await; + // check total SRI supply is +100M let last_block = serai.latest_finalized_block().await.unwrap().hash(); let serai = serai.as_of(last_block); - // Check balance instead of supply let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap(); // there are 6 endowed accounts in dev-net. Take this into consideration when checking // for the total sri minted at this time. @@ -136,65 +136,37 @@ async fn test_genesis_liquidity(serai: Serai) { let btc_sri = (pool_btc_value * u128::from(GENESIS_SRI)) / total_value; let xmr_sri = ((pool_xmr_value * u128::from(GENESIS_SRI)) / total_value) + 1; - let btc_reserves = serai.dex().get_reserves(Coin::Bitcoin, Coin::Serai).await.unwrap().unwrap(); + let btc_reserves = serai.dex().get_reserves(Coin::Bitcoin).await.unwrap().unwrap(); assert_eq!(u128::from(btc_reserves.0 .0), pool_btc); assert_eq!(u128::from(btc_reserves.1 .0), btc_sri); - let xmr_reserves = serai.dex().get_reserves(Coin::Monero, Coin::Serai).await.unwrap().unwrap(); + let xmr_reserves = serai.dex().get_reserves(Coin::Monero).await.unwrap().unwrap(); assert_eq!(u128::from(xmr_reserves.0 .0), pool_xmr); assert_eq!(u128::from(xmr_reserves.1 .0), xmr_sri); // check each btc liq provider got liq tokens proportional to their value - let btc_liq_token_supply = u128::from( - serai - .liquidity_tokens() - .token_balance(Coin::Bitcoin, GENESIS_LIQUIDITY_ACCOUNT) - .await - .unwrap() - .0, - ); - let mut total_tokens_this_coin: u128 = 0; - for (i, (addr, amount)) in btc_addresses.iter().enumerate() { - let addr_value = (u128::from(amount.0) * u128::from(prices.bitcoin)) / 10u128.pow(8); - let addr_liq_tokens = if i == btc_addresses.len() - 1 { - btc_liq_token_supply - total_tokens_this_coin - } else { - (addr_value * btc_liq_token_supply) / pool_btc_value - }; - - let addr_actual_token_amount = - serai.genesis_liquidity().liquidity_tokens(addr, Coin::Bitcoin).await.unwrap(); - - assert_eq!(addr_liq_tokens, addr_actual_token_amount.0.into()); - total_tokens_this_coin += addr_liq_tokens; + let btc_liq_supply = serai.genesis_liquidity().supply(Coin::Bitcoin).await.unwrap(); + for (acc, amount) in btc_addresses { + let acc_liq_shares = serai.genesis_liquidity().liquidity(&acc, Coin::Bitcoin).await.unwrap().0; + + // since we can't test the ratios directly(due to integer division giving 0) + // we test whether they give the same result when multiplied by another constant. + // Following test ensures the account in fact has the right amount of shares. + let shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / btc_liq_supply.0 .0; + let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_btc).unwrap(); + assert_eq!(shares_ratio, amounts_ratio); } // check each xmr liq provider got liq tokens proportional to their value - let xmr_liq_token_supply = u128::from( - serai - .liquidity_tokens() - .token_balance(Coin::Monero, GENESIS_LIQUIDITY_ACCOUNT) - .await - .unwrap() - .0, - ); - total_tokens_this_coin = 0; - for (i, (addr, amount)) in xmr_addresses.iter().enumerate() { - let addr_value = (u128::from(amount.0) * u128::from(prices.monero)) / 10u128.pow(12); - let addr_liq_tokens = if i == xmr_addresses.len() - 1 { - xmr_liq_token_supply - total_tokens_this_coin - } else { - (addr_value * xmr_liq_token_supply) / pool_xmr_value - }; - - let addr_actual_token_amount = - serai.genesis_liquidity().liquidity_tokens(addr, Coin::Monero).await.unwrap(); - - assert_eq!(addr_liq_tokens, addr_actual_token_amount.0.into()); - total_tokens_this_coin += addr_liq_tokens; + let xmr_liq_supply = serai.genesis_liquidity().supply(Coin::Monero).await.unwrap(); + for (acc, amount) in xmr_addresses { + let acc_liq_shares = serai.genesis_liquidity().liquidity(&acc, Coin::Monero).await.unwrap().0; + let shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / xmr_liq_supply.0 .0; + let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_xmr).unwrap(); + assert_eq!(shares_ratio, amounts_ratio); } - // TODO: remove the liq before/after genesis ended. + // TODO: test remove the liq before/after genesis ended. } async fn set_prices(serai: &Serai, prices: &Prices) { diff --git a/substrate/dex/pallet/src/tests.rs b/substrate/dex/pallet/src/tests.rs index 80b45464d..b00141997 100644 --- a/substrate/dex/pallet/src/tests.rs +++ b/substrate/dex/pallet/src/tests.rs @@ -25,7 +25,7 @@ pub use coins_pallet as coins; use coins::Pallet as CoinsPallet; -use serai_primitives::*; +use serai_primitives::{Balance, COINS, PublicKey, system_address, Amount}; type LiquidityTokens = coins_pallet::Pallet; type LiquidityTokensError = coins_pallet::Error; diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml index 068902d90..befeb29bc 100644 --- a/substrate/genesis-liquidity/pallet/Cargo.toml +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -32,6 +32,7 @@ sp-application-crypto = { git = "https://github.com/serai-dex/substrate", defaul dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } +validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false } serai-primitives = { path = "../../primitives", default-features = false } genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../primitives", default-features = false } @@ -51,6 +52,7 @@ std = [ "coins-pallet/std", "dex-pallet/std", + "validator-sets-pallet/std", "serai-primitives/std", "genesis-liquidity-primitives/std", diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 6e6f2a1f9..259b0d6ef 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -5,19 +5,17 @@ pub mod pallet { use super::*; use frame_system::{pallet_prelude::*, RawOrigin}; - use frame_support::{ - pallet_prelude::*, - sp_runtime::{self, SaturatedConversion}, - }; + use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion}; - use sp_std::{vec, vec::Vec, collections::btree_map::BTreeMap}; + use sp_std::{vec, vec::Vec}; use sp_core::sr25519::Signature; use sp_application_crypto::RuntimePublic; use dex_pallet::{Pallet as Dex, Config as DexConfig}; use coins_pallet::{Config as CoinsConfig, Pallet as Coins, AllowMint}; + use validator_sets_pallet::{Config as VsConfig, Pallet as ValidatorSets}; - use serai_primitives::*; + use serai_primitives::{Coin, COINS, *}; use validator_sets_primitives::{ValidatorSet, Session, musig_key}; pub use genesis_liquidity_primitives as primitives; use primitives::*; @@ -27,24 +25,15 @@ pub mod pallet { #[pallet::config] pub trait Config: - frame_system::Config + DexConfig + CoinsConfig + coins_pallet::Config + frame_system::Config + + VsConfig + + DexConfig + + CoinsConfig + + coins_pallet::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[pallet::genesis_config] - #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub struct GenesisConfig { - /// List of participants to place in the initial validator sets. - pub participants: Vec, - } - - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { participants: Default::default() } - } - } - #[pallet::error] pub enum Error { GenesisPeriodEnded, @@ -69,39 +58,34 @@ pub mod pallet { pub(crate) type Liquidity = StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, SubstrateAmount, OptionQuery>; + /// Keeps the total shares and the total amount of coins per coin. #[pallet::storage] - pub(crate) type LiquidityTokensPerAddress = - StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, SubstrateAmount, OptionQuery>; + pub(crate) type Supply = StorageMap<_, Identity, Coin, (u64, u64), OptionQuery>; #[pallet::storage] pub(crate) type EconomicSecurityReached = - StorageMap<_, Identity, NetworkId, BlockNumberFor, ValueQuery>; + StorageMap<_, Identity, NetworkId, BlockNumberFor, OptionQuery>; #[pallet::storage] - pub(crate) type Participants = - StorageMap<_, Identity, NetworkId, BoundedVec>, ValueQuery>; + pub(crate) type Oracle = StorageMap<_, Identity, Coin, u64, OptionQuery>; #[pallet::storage] - pub(crate) type Oracle = StorageMap<_, Identity, Coin, u64, ValueQuery>; - - #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig { - fn build(&self) { - Participants::::set(NetworkId::Serai, self.participants.clone().try_into().unwrap()); - } - } + pub(crate) type GenesisComplete = StorageValue<_, bool, OptionQuery>; #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(n: BlockNumberFor) { #[cfg(feature = "fast-epoch")] - let final_block = 25u32; + let final_block = 10u64; #[cfg(not(feature = "fast-epoch"))] - let final_block = BLOCKS_PER_MONTH; + let final_block = MONTHS; // Distribute the genesis sri to pools after a month - if n == final_block.into() { + if n.saturated_into::() >= final_block && + Self::oraclization_is_done() && + GenesisComplete::::get().is_none() + { // mint the SRI Coins::::mint( GENESIS_LIQUIDITY_ACCOUNT.into(), @@ -109,8 +93,7 @@ pub mod pallet { ) .unwrap(); - // get coin values & total - let mut account_values = BTreeMap::new(); + // get pool & total values let mut pool_values = vec![]; let mut total_value: u128 = 0; for coin in COINS { @@ -119,20 +102,11 @@ pub mod pallet { } // initial coin value in terms of btc - let value = Oracle::::get(coin); - - // get the pool & individual address values - account_values.insert(coin, vec![]); - let mut pool_amount: u128 = 0; - for (account, amount) in Liquidity::::iter_prefix(coin) { - pool_amount = pool_amount.saturating_add(amount.into()); - let value_this_addr = - u128::from(amount).saturating_mul(value.into()) / 10u128.pow(coin.decimals()); - account_values.get_mut(&coin).unwrap().push((account, value_this_addr)) - } - // sort, so that everyone has a consistent accounts vector per coin - account_values.get_mut(&coin).unwrap().sort(); + let Some(value) = Oracle::::get(coin) else { + continue; + }; + let pool_amount = u128::from(Supply::::get(coin).unwrap_or((0, 0)).1); let pool_value = pool_amount.saturating_mul(value.into()) / 10u128.pow(coin.decimals()); total_value = total_value.saturating_add(pool_value); pool_values.push((coin, pool_amount, pool_value)); @@ -173,30 +147,6 @@ pub mod pallet { coin1: Balance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) }, coin2: Balance { coin: Coin::Serai, amount: Amount(sri_amount) }, }); - - // set liquidity tokens per account - let tokens = - u128::from(LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin).0); - let mut total_tokens_this_coin: u128 = 0; - - let accounts = account_values.get(&coin).unwrap(); - for (i, (acc, acc_value)) in accounts.iter().enumerate() { - // give whatever left to the last account not to have rounding errors. - let liq_tokens_this_acc = if i == accounts.len() - 1 { - tokens - total_tokens_this_coin - } else { - tokens.saturating_mul(*acc_value) / pool_value - }; - - total_tokens_this_coin = total_tokens_this_coin.saturating_add(liq_tokens_this_acc); - - LiquidityTokensPerAddress::::set( - coin, - acc, - Some(u64::try_from(liq_tokens_this_acc).unwrap()), - ); - } - assert_eq!(tokens, total_tokens_this_coin); } assert_eq!(total_sri_distributed, GENESIS_SRI); @@ -205,15 +155,17 @@ pub mod pallet { for coin in COINS { assert_eq!(Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin), Amount(0)); } + + GenesisComplete::::set(Some(true)); } // we accept we reached economic security once we can mint smallest amount of a network's coin for coin in COINS { let existing = EconomicSecurityReached::::get(coin.network()); - if existing == 0u32.into() && + if existing.is_none() && ::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) }) { - EconomicSecurityReached::::set(coin.network(), n); + EconomicSecurityReached::::set(coin.network(), Some(n)); Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() }); } } @@ -232,36 +184,77 @@ pub mod pallet { // mint the coins Coins::::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), balance)?; - // save - let existing = Liquidity::::get(balance.coin, account).unwrap_or(0); - let new = existing.checked_add(balance.amount.0).ok_or(Error::::AmountOverflowed)?; - Liquidity::::set(balance.coin, account, Some(new)); + // calculate new shares & supply + let (new_shares, new_supply) = if let Some(supply) = Supply::::get(balance.coin) { + // calculate amount of shares for this amount + let shares = Self::mul_div(supply.0, balance.amount.0, supply.1)?; + + // get new shares for this account + let existing = Liquidity::::get(balance.coin, account).unwrap_or(0); + let new = existing.checked_add(shares).ok_or(Error::::AmountOverflowed)?; + + ( + new, + ( + supply.0.checked_add(shares).ok_or(Error::::AmountOverflowed)?, + supply.1.checked_add(balance.amount.0).ok_or(Error::::AmountOverflowed)?, + ), + ) + } else { + (GENESIS_LP_SHARES, (GENESIS_LP_SHARES, balance.amount.0)) + }; + // save + Liquidity::::set(balance.coin, account, Some(new_shares)); + Supply::::set(balance.coin, Some(new_supply)); Self::deposit_event(Event::GenesisLiquidityAdded { by: account.into(), balance }); Ok(()) } - /// Returns the number of blocks since the coin's network reached economic security first time. - /// If the network is yet to be reached that threshold, 0 is returned. And maximum of - /// `GENESIS_SRI_TRICKLE_FEED` returned. - fn blocks_since_ec_security(coin: &Coin) -> u64 { - let ec_security_block = - EconomicSecurityReached::::get(coin.network()).saturated_into::(); - let current = >::block_number().saturated_into::(); - if ec_security_block > 0 { - let diff = current - ec_security_block; - if diff > GENESIS_SRI_TRICKLE_FEED { - return GENESIS_SRI_TRICKLE_FEED; + /// Returns the number of blocks since the all networks reached economic security first time. + /// If networks is yet to be reached that threshold, None is returned. + fn blocks_since_ec_security() -> Option { + let mut min = u64::MAX; + for n in NETWORKS { + let ec_security_block = EconomicSecurityReached::::get(n)?.saturated_into::(); + let current = >::block_number().saturated_into::(); + let diff = current.saturating_sub(ec_security_block); + min = diff.min(min); + } + Some(min) + } + + fn genesis_ended() -> bool { + Self::oraclization_is_done() && + >::block_number().saturated_into::() >= MONTHS + } + + fn oraclization_is_done() -> bool { + for c in COINS { + if c == Coin::Serai { + continue; } - return diff; + if Oracle::::get(c).is_none() { + return false; + } } - 0 + true } - fn genesis_ended() -> bool { - >::block_number() >= BLOCKS_PER_MONTH.into() + fn mul_div(a: u64, b: u64, c: u64) -> Result> { + let a = u128::from(a); + let b = u128::from(b); + let c = u128::from(c); + + let result = a + .checked_mul(b) + .ok_or(Error::::AmountOverflowed)? + .checked_div(c) + .ok_or(Error::::AmountOverflowed)?; + + result.try_into().map_err(|_| Error::::AmountOverflowed) } } @@ -276,11 +269,15 @@ pub mod pallet { // check we are still in genesis period if Self::genesis_ended() { - // check user have enough to remove - let existing = LiquidityTokensPerAddress::::get(balance.coin, account).unwrap_or(0); - if balance.amount.0 > existing { - Err(Error::::NotEnoughLiquidity)?; - } + // see how much liq tokens we have + let total_liq_tokens = + LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai).0; + + // get how much user wants to remove + let user_shares = Liquidity::::get(balance.coin, account).unwrap_or(0); + let total_shares = Supply::::get(balance.coin).unwrap_or((0, 0)).0; + let user_liq_tokens = Self::mul_div(total_liq_tokens, user_shares, total_shares)?; + let amount_to_remove = Self::mul_div(user_liq_tokens, balance.amount.0, GENESIS_LP_SHARES)?; // remove liquidity from pool let prev_sri = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai); @@ -288,7 +285,7 @@ pub mod pallet { Dex::::remove_liquidity( origin.clone().into(), balance.coin, - balance.amount.0, + amount_to_remove, 1, 1, GENESIS_LIQUIDITY_ACCOUNT.into(), @@ -297,9 +294,10 @@ pub mod pallet { let current_coin = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin); // burn the SRI if necessary + // TODO: take into consideration movement between pools. let mut sri = current_sri.0.saturating_sub(prev_sri.0); let distance_to_full_pay = - GENESIS_SRI_TRICKLE_FEED - Self::blocks_since_ec_security(&balance.coin); + GENESIS_SRI_TRICKLE_FEED.saturating_sub(Self::blocks_since_ec_security().unwrap_or(0)); let burn_sri_amount = sri.saturating_mul(distance_to_full_pay) / GENESIS_SRI_TRICKLE_FEED; Coins::::burn( origin.clone().into(), @@ -321,9 +319,13 @@ pub mod pallet { )?; // save - let existing = LiquidityTokensPerAddress::::get(balance.coin, account).unwrap_or(0); - let new = existing.checked_sub(balance.amount.0).ok_or(Error::::AmountOverflowed)?; - LiquidityTokensPerAddress::::set(balance.coin, account, Some(new)); + let new_shares = + user_shares.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?; + if new_shares == 0 { + Liquidity::::set(balance.coin, account, None); + } else { + Liquidity::::set(balance.coin, account, Some(new_shares)); + } } else { let existing = Liquidity::::get(balance.coin, account).unwrap_or(0); if balance.amount.0 > existing || balance.amount.0 == 0 { @@ -345,7 +347,7 @@ pub mod pallet { Ok(()) } - /// A call to submit the initial coi values. + /// A call to submit the initial coin values in terms of BTC. #[pallet::call_index(1)] #[pallet::weight((0, DispatchClass::Operational))] // TODO pub fn set_initial_price( @@ -356,10 +358,10 @@ pub mod pallet { ensure_none(origin)?; // set the prices - Oracle::::set(Coin::Bitcoin, prices.bitcoin); - Oracle::::set(Coin::Monero, prices.monero); - Oracle::::set(Coin::Ether, prices.ethereum); - Oracle::::set(Coin::Dai, prices.dai); + Oracle::::set(Coin::Bitcoin, Some(prices.bitcoin)); + Oracle::::set(Coin::Monero, Some(prices.monero)); + Oracle::::set(Coin::Ether, Some(prices.ethereum)); + Oracle::::set(Coin::Dai, Some(prices.dai)); Ok(()) } } @@ -371,8 +373,26 @@ pub mod pallet { fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { Call::set_initial_price { ref prices, ref signature } => { + // TODO: if this is supposed to be called after a month, serai set won't still be + // in the session 0? Ideally this should pull the session from Vs pallet? let set = ValidatorSet { network: NetworkId::Serai, session: Session(0) }; - let signers = Participants::::get(NetworkId::Serai); + let signers = ValidatorSets::::participants_for_latest_decided_set(NetworkId::Serai) + .expect("no participant in the current set") + .into_iter() + .map(|(p, _)| p) + .collect::>(); + + // check this didn't get called before + if Self::oraclization_is_done() { + Err(InvalidTransaction::Custom(0))?; + } + + // make sure signers settings the price at the end of the genesis period. + // we don't need this check for tests. + #[cfg(not(feature = "fast-epoch"))] + if >::block_number().saturated_into::() < MONTHS { + Err(InvalidTransaction::Custom(1))?; + } if !musig_key(set, &signers).verify(&set_initial_price_message(&set, prices), signature) { Err(InvalidTransaction::BadProof)?; diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index f334ec74e..7053e3f5b 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -18,14 +18,7 @@ use scale_info::TypeInfo; use serai_primitives::*; use validator_sets_primitives::ValidatorSet; -// amount of blocks in 30 days for 6s per block. -pub const BLOCKS_PER_MONTH: u32 = 10 * 60 * 24 * 30; - -/// 180 days of blocks -pub const GENESIS_SRI_TRICKLE_FEED: u64 = 10 * 60 * 24 * 180; - -// 100 Million SRI -pub const GENESIS_SRI: u64 = 100_000_000 * 10_u64.pow(8); +pub const GENESIS_LP_SHARES: u64 = 10_000; // This is the account to hold and manage the genesis liquidity. pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 976c54cd8..e66ee4a6d 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -8,7 +8,6 @@ use sc_service::ChainType; use serai_runtime::{ primitives::*, WASM_BINARY, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, SystemConfig, CoinsConfig, DexConfig, ValidatorSetsConfig, SignalsConfig, BabeConfig, GrandpaConfig, - GenesisLiquidityConfig, }; pub type ChainSpec = sc_service::GenericChainSpec; @@ -64,7 +63,6 @@ fn devnet_genesis( .collect(), participants: validators.clone(), }, - genesis_liquidity: GenesisLiquidityConfig { participants: validators.clone() }, signals: SignalsConfig::default(), babe: BabeConfig { authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(), @@ -116,7 +114,6 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime .collect(), participants: validators.clone(), }, - genesis_liquidity: GenesisLiquidityConfig { participants: validators.clone() }, signals: SignalsConfig::default(), babe: BabeConfig { authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(), diff --git a/substrate/primitives/src/constants.rs b/substrate/primitives/src/constants.rs new file mode 100644 index 000000000..c5c53d75d --- /dev/null +++ b/substrate/primitives/src/constants.rs @@ -0,0 +1,29 @@ +use crate::BlockNumber; + +// 1 MB +pub const BLOCK_SIZE: u32 = 1024 * 1024; +// 6 seconds +pub const TARGET_BLOCK_TIME: u64 = 6; + +/// Measured in blocks. +pub const MINUTES: BlockNumber = 60 / TARGET_BLOCK_TIME; +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; +pub const WEEKS: BlockNumber = DAYS * 7; +pub const MONTHS: BlockNumber = WEEKS * 4; + +/// 6 months of blocks +pub const GENESIS_SRI_TRICKLE_FEED: u64 = MONTHS * 6; + +// 100 Million SRI +pub const GENESIS_SRI: u64 = 100_000_000 * 10_u64.pow(8); + +/// This needs to be long enough for arbitrage to occur and make holding any fake price up +/// sufficiently unrealistic. +#[allow(clippy::cast_possible_truncation)] +pub const ARBITRAGE_TIME: u16 = (2 * HOURS) as u16; + +/// Since we use the median price, double the window length. +/// +/// We additionally +1 so there is a true median. +pub const MEDIAN_PRICE_WINDOW_LENGTH: u16 = (2 * ARBITRAGE_TIME) + 1; diff --git a/substrate/primitives/src/lib.rs b/substrate/primitives/src/lib.rs index 2af36e22d..d2c52219e 100644 --- a/substrate/primitives/src/lib.rs +++ b/substrate/primitives/src/lib.rs @@ -37,6 +37,9 @@ pub use balance::*; mod account; pub use account::*; +mod constants; +pub use constants::*; + pub type BlockNumber = u64; pub type Header = sp_runtime::generic::Header; diff --git a/substrate/runtime/src/abi.rs b/substrate/runtime/src/abi.rs index 146ce8aec..812cb58d8 100644 --- a/substrate/runtime/src/abi.rs +++ b/substrate/runtime/src/abi.rs @@ -30,10 +30,10 @@ impl From for RuntimeCall { } }, Call::LiquidityTokens(lt) => match lt { - serai_abi::coins::LiquidityTokensCall::transfer { to, balance } => { + serai_abi::liquidity_tokens::Call::transfer { to, balance } => { RuntimeCall::LiquidityTokens(coins::Call::transfer { to: to.into(), balance }) } - serai_abi::coins::LiquidityTokensCall::burn { balance } => { + serai_abi::liquidity_tokens::Call::burn { balance } => { RuntimeCall::LiquidityTokens(coins::Call::burn { balance }) } }, @@ -220,9 +220,9 @@ impl TryInto for RuntimeCall { }), RuntimeCall::LiquidityTokens(call) => Call::LiquidityTokens(match call { coins::Call::transfer { to, balance } => { - serai_abi::coins::LiquidityTokensCall::transfer { to: to.into(), balance } + serai_abi::liquidity_tokens::Call::transfer { to: to.into(), balance } } - coins::Call::burn { balance } => serai_abi::coins::LiquidityTokensCall::burn { balance }, + coins::Call::burn { balance } => serai_abi::liquidity_tokens::Call::burn { balance }, _ => Err(())?, }), RuntimeCall::Dex(call) => Call::Dex(match call { diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 3f034eed2..5301f0432 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -48,7 +48,11 @@ use sp_runtime::{ BoundedVec, Perbill, ApplyExtrinsicResult, }; -use primitives::{NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, NETWORKS}; +#[allow(unused_imports)] +use primitives::{ + NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, NETWORKS, MEDIAN_PRICE_WINDOW_LENGTH, + HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE, +}; use support::{ traits::{ConstU8, ConstU16, ConstU32, ConstU64, Contains}, @@ -115,28 +119,7 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } -// 1 MB -pub const BLOCK_SIZE: u32 = 1024 * 1024; -// 6 seconds -pub const TARGET_BLOCK_TIME: u64 = 6; - -/// Measured in blocks. -pub const MINUTES: BlockNumber = 60 / TARGET_BLOCK_TIME; -pub const HOURS: BlockNumber = MINUTES * 60; -pub const DAYS: BlockNumber = HOURS * 24; - pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); - -/// This needs to be long enough for arbitrage to occur and make holding any fake price up -/// sufficiently unrealistic. -#[allow(clippy::cast_possible_truncation)] -pub const ARBITRAGE_TIME: u16 = (2 * HOURS) as u16; - -/// Since we use the median price, double the window length. -/// -/// We additionally +1 so there is a true median. -pub const MEDIAN_PRICE_WINDOW_LENGTH: u16 = (2 * ARBITRAGE_TIME) + 1; - pub const BABE_GENESIS_EPOCH_CONFIG: sp_consensus_babe::BabeEpochConfiguration = sp_consensus_babe::BabeEpochConfiguration { c: PRIMARY_PROBABILITY, @@ -295,7 +278,7 @@ pub type ReportLongevity = ::EpochDuration; impl babe::Config for Runtime { #[cfg(feature = "fast-epoch")] - type EpochDuration = ConstU64<{ HOURS / 2 }>; // 30 minutes + type EpochDuration = ConstU64<{ MINUTES / 2 }>; // 30 seconds #[cfg(not(feature = "fast-epoch"))] type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>; From ef703843a66cfdba18160e098d53b4ef52bafbc0 Mon Sep 17 00:00:00 2001 From: akildemir Date: Fri, 28 Jun 2024 16:56:01 +0300 Subject: [PATCH 15/18] further bug fixes --- .../client/src/serai/genesis_liquidity.rs | 8 +- substrate/client/tests/genesis_liquidity.rs | 7 +- substrate/genesis-liquidity/pallet/src/lib.rs | 84 +++++++++++++------ 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/substrate/client/src/serai/genesis_liquidity.rs b/substrate/client/src/serai/genesis_liquidity.rs index 2a2072f09..23e942aa0 100644 --- a/substrate/client/src/serai/genesis_liquidity.rs +++ b/substrate/client/src/serai/genesis_liquidity.rs @@ -41,7 +41,11 @@ impl<'a> SeraiGenesisLiquidity<'a> { }) } - pub async fn liquidity(&self, address: &SeraiAddress, coin: Coin) -> Result { + pub async fn liquidity( + &self, + address: &SeraiAddress, + coin: Coin, + ) -> Result<(Amount, Amount), SeraiError> { Ok( self .0 @@ -51,7 +55,7 @@ impl<'a> SeraiGenesisLiquidity<'a> { (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), ) .await? - .unwrap_or(Amount(0)), + .unwrap_or((Amount(0), Amount(0))), ) } diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs index ca1a12750..0e5b787ff 100644 --- a/substrate/client/tests/genesis_liquidity.rs +++ b/substrate/client/tests/genesis_liquidity.rs @@ -147,7 +147,8 @@ async fn test_genesis_liquidity(serai: Serai) { // check each btc liq provider got liq tokens proportional to their value let btc_liq_supply = serai.genesis_liquidity().supply(Coin::Bitcoin).await.unwrap(); for (acc, amount) in btc_addresses { - let acc_liq_shares = serai.genesis_liquidity().liquidity(&acc, Coin::Bitcoin).await.unwrap().0; + let acc_liq_shares = + serai.genesis_liquidity().liquidity(&acc, Coin::Bitcoin).await.unwrap().0 .0; // since we can't test the ratios directly(due to integer division giving 0) // we test whether they give the same result when multiplied by another constant. @@ -160,7 +161,9 @@ async fn test_genesis_liquidity(serai: Serai) { // check each xmr liq provider got liq tokens proportional to their value let xmr_liq_supply = serai.genesis_liquidity().supply(Coin::Monero).await.unwrap(); for (acc, amount) in xmr_addresses { - let acc_liq_shares = serai.genesis_liquidity().liquidity(&acc, Coin::Monero).await.unwrap().0; + let acc_liq_shares = + serai.genesis_liquidity().liquidity(&acc, Coin::Monero).await.unwrap().0 .0; + let shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / xmr_liq_supply.0 .0; let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_xmr).unwrap(); assert_eq!(shares_ratio, amounts_ratio); diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 259b0d6ef..b5ee14f2e 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -54,13 +54,22 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(PhantomData); + /// Keeps shares and the amount of coins per account. #[pallet::storage] - pub(crate) type Liquidity = - StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, SubstrateAmount, OptionQuery>; + pub(crate) type Liquidity = StorageDoubleMap< + _, + Identity, + Coin, + Blake2_128Concat, + PublicKey, + (SubstrateAmount, SubstrateAmount), + OptionQuery, + >; /// Keeps the total shares and the total amount of coins per coin. #[pallet::storage] - pub(crate) type Supply = StorageMap<_, Identity, Coin, (u64, u64), OptionQuery>; + pub(crate) type Supply = + StorageMap<_, Identity, Coin, (SubstrateAmount, SubstrateAmount), OptionQuery>; #[pallet::storage] pub(crate) type EconomicSecurityReached = @@ -190,18 +199,20 @@ pub mod pallet { let shares = Self::mul_div(supply.0, balance.amount.0, supply.1)?; // get new shares for this account - let existing = Liquidity::::get(balance.coin, account).unwrap_or(0); - let new = existing.checked_add(shares).ok_or(Error::::AmountOverflowed)?; - + let existing = Liquidity::::get(balance.coin, account).unwrap_or((0, 0)); ( - new, + ( + existing.0.checked_add(shares).ok_or(Error::::AmountOverflowed)?, + existing.1.checked_add(balance.amount.0).ok_or(Error::::AmountOverflowed)?, + ), ( supply.0.checked_add(shares).ok_or(Error::::AmountOverflowed)?, supply.1.checked_add(balance.amount.0).ok_or(Error::::AmountOverflowed)?, ), ) } else { - (GENESIS_LP_SHARES, (GENESIS_LP_SHARES, balance.amount.0)) + let first_amounts = (GENESIS_LP_SHARES, balance.amount.0); + (first_amounts, first_amounts) }; // save @@ -266,15 +277,17 @@ pub mod pallet { pub fn remove_coin_liquidity(origin: OriginFor, balance: Balance) -> DispatchResult { let account = ensure_signed(origin)?; let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); + let supply = Supply::::get(balance.coin).ok_or(Error::::NotEnoughLiquidity)?; // check we are still in genesis period - if Self::genesis_ended() { + let (new_shares, new_supply) = if Self::genesis_ended() { // see how much liq tokens we have let total_liq_tokens = LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai).0; // get how much user wants to remove - let user_shares = Liquidity::::get(balance.coin, account).unwrap_or(0); + let (user_shares, user_coins) = + Liquidity::::get(balance.coin, account).unwrap_or((0, 0)); let total_shares = Supply::::get(balance.coin).unwrap_or((0, 0)).0; let user_liq_tokens = Self::mul_div(total_liq_tokens, user_shares, total_shares)?; let amount_to_remove = Self::mul_div(user_liq_tokens, balance.amount.0, GENESIS_LP_SHARES)?; @@ -318,30 +331,47 @@ pub mod pallet { Balance { coin: Coin::Serai, amount: Amount(sri) }, )?; - // save - let new_shares = - user_shares.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?; - if new_shares == 0 { - Liquidity::::set(balance.coin, account, None); - } else { - Liquidity::::set(balance.coin, account, Some(new_shares)); - } + // return new amounts + ( + ( + user_shares.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?, + user_coins.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, + ), + ( + supply.0.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?, + supply.1.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, + ), + ) } else { - let existing = Liquidity::::get(balance.coin, account).unwrap_or(0); - if balance.amount.0 > existing || balance.amount.0 == 0 { - Err(Error::::NotEnoughLiquidity)?; - } - if balance.amount.0 < existing { + if balance.amount.0 != GENESIS_LP_SHARES { Err(Error::::CanOnlyRemoveFullAmount)?; } + let existing = + Liquidity::::get(balance.coin, account).ok_or(Error::::NotEnoughLiquidity)?; - // TODO: do external transfer instead for making it easier for the user? - // or do we even want to make it easier? - Coins::::transfer(origin.into(), account, balance)?; + // transfer to the user + Coins::::transfer( + origin.into(), + account, + Balance { coin: balance.coin, amount: Amount(existing.1) }, + )?; + + ( + (0, 0), + ( + supply.0.checked_sub(existing.0).ok_or(Error::::AmountOverflowed)?, + supply.1.checked_sub(existing.1).ok_or(Error::::AmountOverflowed)?, + ), + ) + }; - // save + // save + if new_shares.0 == 0 { Liquidity::::set(balance.coin, account, None); + } else { + Liquidity::::set(balance.coin, account, Some(new_shares)); } + Supply::::set(balance.coin, Some(new_supply)); Self::deposit_event(Event::GenesisLiquidityRemoved { by: account.into(), balance }); Ok(()) From 1eee0a4d92dfbeb83435e9ddd0d9876d158b2393 Mon Sep 17 00:00:00 2001 From: akildemir Date: Thu, 4 Jul 2024 16:41:19 +0300 Subject: [PATCH 16/18] fix last pr comments --- Cargo.lock | 1 + substrate/abi/src/genesis_liquidity.rs | 2 +- .../client/src/serai/genesis_liquidity.rs | 4 +- substrate/client/tests/genesis_liquidity.rs | 19 +- substrate/genesis-liquidity/pallet/src/lib.rs | 171 ++++++++++-------- .../genesis-liquidity/primitives/src/lib.rs | 22 ++- substrate/in-instructions/pallet/Cargo.toml | 2 + substrate/in-instructions/pallet/src/lib.rs | 2 + substrate/runtime/src/abi.rs | 8 +- 9 files changed, 138 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d661d297..d4a18fe1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7998,6 +7998,7 @@ dependencies = [ "serai-coins-pallet", "serai-dex-pallet", "serai-genesis-liquidity-pallet", + "serai-genesis-liquidity-primitives", "serai-in-instructions-primitives", "serai-primitives", "serai-validator-sets-pallet", diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs index 2b0c208c8..2047bc2b9 100644 --- a/substrate/abi/src/genesis_liquidity.rs +++ b/substrate/abi/src/genesis_liquidity.rs @@ -7,7 +7,7 @@ use primitives::*; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Call { remove_coin_liquidity { balance: Balance }, - set_initial_price { prices: Prices, signature: Signature }, + oraclize_values { prices: Prices, signature: Signature }, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] diff --git a/substrate/client/src/serai/genesis_liquidity.rs b/substrate/client/src/serai/genesis_liquidity.rs index 23e942aa0..431ce90b0 100644 --- a/substrate/client/src/serai/genesis_liquidity.rs +++ b/substrate/client/src/serai/genesis_liquidity.rs @@ -29,9 +29,9 @@ impl<'a> SeraiGenesisLiquidity<'a> { .await } - pub fn set_initial_price(prices: Prices, signature: Signature) -> Transaction { + pub fn oraclize_values(prices: Prices, signature: Signature) -> Transaction { Serai::unsigned(serai_abi::Call::GenesisLiquidity( - serai_abi::genesis_liquidity::Call::set_initial_price { prices, signature }, + serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature }, )) } diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs index 0e5b787ff..ae0f27eb6 100644 --- a/substrate/client/tests/genesis_liquidity.rs +++ b/substrate/client/tests/genesis_liquidity.rs @@ -16,7 +16,7 @@ use serai_client::{ }; use serai_abi::{ - genesis_liquidity::primitives::{set_initial_price_message, Prices}, + genesis_liquidity::primitives::{oraclize_values_message, Prices}, primitives::COINS, }; @@ -101,7 +101,7 @@ async fn test_genesis_liquidity(serai: Serai) { .unwrap(); // set prices - let prices = Prices { bitcoin: 10u64.pow(8), monero: 184100, ethereum: 4785000, dai: 1500 }; + let prices = Prices { monero: 184100, ethereum: 4785000, dai: 1500 }; set_prices(&serai, &prices).await; // wait little bit.. @@ -127,7 +127,7 @@ async fn test_genesis_liquidity(serai: Serai) { let pool_btc = btc_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); let pool_xmr = xmr_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); - let pool_btc_value = (pool_btc * u128::from(prices.bitcoin)) / 10u128.pow(8); + let pool_btc_value = pool_btc; let pool_xmr_value = (pool_xmr * u128::from(prices.monero)) / 10u128.pow(12); let total_value = pool_btc_value + pool_xmr_value; @@ -176,7 +176,8 @@ async fn set_prices(serai: &Serai, prices: &Prices) { // prepare a Musig tx to set the initial prices let pair = insecure_pair_from_name("Alice"); let public = pair.public(); - let set = ValidatorSet { session: Session(0), network: NetworkId::Serai }; + // we publish the tx in set 2 + let set = ValidatorSet { session: Session(2), network: NetworkId::Serai }; let public_key = ::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap(); let secret_key = ::read_F::<&[u8]>( @@ -195,13 +196,11 @@ async fn set_prices(serai: &Serai, prices: &Prices) { &Schnorrkel::new(b"substrate"), &HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]), ), - &set_initial_price_message(&set, prices), + &oraclize_values_message(&set, prices), ); // set initial prices - let _ = publish_tx( - serai, - &SeraiGenesisLiquidity::set_initial_price(*prices, Signature(sig.to_bytes())), - ) - .await; + let _ = + publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*prices, Signature(sig.to_bytes()))) + .await; } diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index b5ee14f2e..86f2a9081 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -47,7 +47,7 @@ pub mod pallet { pub enum Event { GenesisLiquidityAdded { by: SeraiAddress, balance: Balance }, GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance }, - GenesisLiquidityAddedToPool { coin1: Balance, coin2: Balance }, + GenesisLiquidityAddedToPool { coin1: Balance, sri: Amount }, EconomicSecurityReached { network: NetworkId }, } @@ -56,20 +56,12 @@ pub mod pallet { /// Keeps shares and the amount of coins per account. #[pallet::storage] - pub(crate) type Liquidity = StorageDoubleMap< - _, - Identity, - Coin, - Blake2_128Concat, - PublicKey, - (SubstrateAmount, SubstrateAmount), - OptionQuery, - >; + pub(crate) type Liquidity = + StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, LiquidityAmount, OptionQuery>; /// Keeps the total shares and the total amount of coins per coin. #[pallet::storage] - pub(crate) type Supply = - StorageMap<_, Identity, Coin, (SubstrateAmount, SubstrateAmount), OptionQuery>; + pub(crate) type Supply = StorageMap<_, Identity, Coin, LiquidityAmount, OptionQuery>; #[pallet::storage] pub(crate) type EconomicSecurityReached = @@ -83,7 +75,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_finalize(n: BlockNumberFor) { + fn on_initialize(n: BlockNumberFor) -> Weight { #[cfg(feature = "fast-epoch")] let final_block = 10u64; @@ -115,9 +107,14 @@ pub mod pallet { continue; }; - let pool_amount = u128::from(Supply::::get(coin).unwrap_or((0, 0)).1); - let pool_value = pool_amount.saturating_mul(value.into()) / 10u128.pow(coin.decimals()); - total_value = total_value.saturating_add(pool_value); + let pool_amount = + u128::from(Supply::::get(coin).unwrap_or(LiquidityAmount::zero()).coins); + let pool_value = pool_amount + .checked_mul(value.into()) + .unwrap() + .checked_div(10u128.pow(coin.decimals())) + .unwrap(); + total_value = total_value.checked_add(pool_value).unwrap(); pool_values.push((coin, pool_amount, pool_value)); } @@ -127,11 +124,18 @@ pub mod pallet { for (i, (coin, pool_amount, pool_value)) in pool_values.into_iter().enumerate() { // whatever sri left for the last coin should be ~= it's ratio let sri_amount = if i == (pool_values_len - 1) { - GENESIS_SRI - total_sri_distributed + GENESIS_SRI.checked_sub(total_sri_distributed).unwrap() } else { - u64::try_from(u128::from(GENESIS_SRI).saturating_mul(pool_value) / total_value).unwrap() + u64::try_from( + u128::from(GENESIS_SRI) + .checked_mul(pool_value) + .unwrap() + .checked_div(total_value) + .unwrap(), + ) + .unwrap() }; - total_sri_distributed += sri_amount; + total_sri_distributed = total_sri_distributed.checked_add(sri_amount).unwrap(); // we can't add 0 liquidity if !(pool_amount > 0 && sri_amount > 0) { @@ -154,7 +158,7 @@ pub mod pallet { // let everyone know about the event Self::deposit_event(Event::GenesisLiquidityAddedToPool { coin1: Balance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) }, - coin2: Balance { coin: Coin::Serai, amount: Amount(sri_amount) }, + sri: Amount(sri_amount), }); } assert_eq!(total_sri_distributed, GENESIS_SRI); @@ -169,6 +173,7 @@ pub mod pallet { } // we accept we reached economic security once we can mint smallest amount of a network's coin + // TODO: move EconomicSecurity to a separate pallet for coin in COINS { let existing = EconomicSecurityReached::::get(coin.network()); if existing.is_none() && @@ -178,6 +183,8 @@ pub mod pallet { Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() }); } } + + Weight::zero() // TODO } } @@ -190,33 +197,37 @@ pub mod pallet { Err(Error::::GenesisPeriodEnded)?; } - // mint the coins - Coins::::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), balance)?; - // calculate new shares & supply - let (new_shares, new_supply) = if let Some(supply) = Supply::::get(balance.coin) { + let (new_liquidity, new_supply) = if let Some(supply) = Supply::::get(balance.coin) { // calculate amount of shares for this amount - let shares = Self::mul_div(supply.0, balance.amount.0, supply.1)?; + let shares = Self::mul_div(supply.shares, balance.amount.0, supply.coins)?; // get new shares for this account - let existing = Liquidity::::get(balance.coin, account).unwrap_or((0, 0)); + let existing = + Liquidity::::get(balance.coin, account).unwrap_or(LiquidityAmount::zero()); ( - ( - existing.0.checked_add(shares).ok_or(Error::::AmountOverflowed)?, - existing.1.checked_add(balance.amount.0).ok_or(Error::::AmountOverflowed)?, - ), - ( - supply.0.checked_add(shares).ok_or(Error::::AmountOverflowed)?, - supply.1.checked_add(balance.amount.0).ok_or(Error::::AmountOverflowed)?, - ), + LiquidityAmount { + shares: existing.shares.checked_add(shares).ok_or(Error::::AmountOverflowed)?, + coins: existing + .coins + .checked_add(balance.amount.0) + .ok_or(Error::::AmountOverflowed)?, + }, + LiquidityAmount { + shares: supply.shares.checked_add(shares).ok_or(Error::::AmountOverflowed)?, + coins: supply + .coins + .checked_add(balance.amount.0) + .ok_or(Error::::AmountOverflowed)?, + }, ) } else { - let first_amounts = (GENESIS_LP_SHARES, balance.amount.0); - (first_amounts, first_amounts) + let first_amount = LiquidityAmount { shares: GENESIS_LP_SHARES, coins: balance.amount.0 }; + (first_amount, first_amount) }; // save - Liquidity::::set(balance.coin, account, Some(new_shares)); + Liquidity::::set(balance.coin, account, Some(new_liquidity)); Supply::::set(balance.coin, Some(new_supply)); Self::deposit_event(Event::GenesisLiquidityAdded { by: account.into(), balance }); Ok(()) @@ -280,16 +291,16 @@ pub mod pallet { let supply = Supply::::get(balance.coin).ok_or(Error::::NotEnoughLiquidity)?; // check we are still in genesis period - let (new_shares, new_supply) = if Self::genesis_ended() { + let (new_liquidity, new_supply) = if Self::genesis_ended() { // see how much liq tokens we have let total_liq_tokens = LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai).0; // get how much user wants to remove - let (user_shares, user_coins) = - Liquidity::::get(balance.coin, account).unwrap_or((0, 0)); - let total_shares = Supply::::get(balance.coin).unwrap_or((0, 0)).0; - let user_liq_tokens = Self::mul_div(total_liq_tokens, user_shares, total_shares)?; + let LiquidityAmount { shares, coins } = + Liquidity::::get(balance.coin, account).unwrap_or(LiquidityAmount::zero()); + let total_shares = Supply::::get(balance.coin).unwrap_or(LiquidityAmount::zero()).shares; + let user_liq_tokens = Self::mul_div(total_liq_tokens, shares, total_shares)?; let amount_to_remove = Self::mul_div(user_liq_tokens, balance.amount.0, GENESIS_LP_SHARES)?; // remove liquidity from pool @@ -308,18 +319,25 @@ pub mod pallet { // burn the SRI if necessary // TODO: take into consideration movement between pools. - let mut sri = current_sri.0.saturating_sub(prev_sri.0); + let mut sri: u64 = current_sri.0.saturating_sub(prev_sri.0); let distance_to_full_pay = GENESIS_SRI_TRICKLE_FEED.saturating_sub(Self::blocks_since_ec_security().unwrap_or(0)); - let burn_sri_amount = sri.saturating_mul(distance_to_full_pay) / GENESIS_SRI_TRICKLE_FEED; + let burn_sri_amount = u64::try_from( + u128::from(sri) + .checked_mul(u128::from(distance_to_full_pay)) + .ok_or(Error::::AmountOverflowed)? + .checked_div(u128::from(GENESIS_SRI_TRICKLE_FEED)) + .ok_or(Error::::AmountOverflowed)?, + ) + .map_err(|_| Error::::AmountOverflowed)?; Coins::::burn( origin.clone().into(), Balance { coin: Coin::Serai, amount: Amount(burn_sri_amount) }, )?; - sri -= burn_sri_amount; + sri = sri.checked_sub(burn_sri_amount).ok_or(Error::::AmountOverflowed)?; // transfer to owner - let coin_out = current_coin.0 - prev_coin.0; + let coin_out = current_coin.0.saturating_sub(prev_coin.0); Coins::::transfer( origin.clone().into(), account, @@ -333,14 +351,17 @@ pub mod pallet { // return new amounts ( - ( - user_shares.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?, - user_coins.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, - ), - ( - supply.0.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?, - supply.1.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, - ), + LiquidityAmount { + shares: shares.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?, + coins: coins.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, + }, + LiquidityAmount { + shares: supply + .shares + .checked_sub(amount_to_remove) + .ok_or(Error::::AmountOverflowed)?, + coins: supply.coins.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, + }, ) } else { if balance.amount.0 != GENESIS_LP_SHARES { @@ -353,23 +374,26 @@ pub mod pallet { Coins::::transfer( origin.into(), account, - Balance { coin: balance.coin, amount: Amount(existing.1) }, + Balance { coin: balance.coin, amount: Amount(existing.coins) }, )?; ( - (0, 0), - ( - supply.0.checked_sub(existing.0).ok_or(Error::::AmountOverflowed)?, - supply.1.checked_sub(existing.1).ok_or(Error::::AmountOverflowed)?, - ), + LiquidityAmount::zero(), + LiquidityAmount { + shares: supply + .shares + .checked_sub(existing.shares) + .ok_or(Error::::AmountOverflowed)?, + coins: supply.coins.checked_sub(existing.coins).ok_or(Error::::AmountOverflowed)?, + }, ) }; // save - if new_shares.0 == 0 { + if new_liquidity == LiquidityAmount::zero() { Liquidity::::set(balance.coin, account, None); } else { - Liquidity::::set(balance.coin, account, Some(new_shares)); + Liquidity::::set(balance.coin, account, Some(new_liquidity)); } Supply::::set(balance.coin, Some(new_supply)); @@ -380,7 +404,7 @@ pub mod pallet { /// A call to submit the initial coin values in terms of BTC. #[pallet::call_index(1)] #[pallet::weight((0, DispatchClass::Operational))] // TODO - pub fn set_initial_price( + pub fn oraclize_values( origin: OriginFor, prices: Prices, _signature: Signature, @@ -388,7 +412,7 @@ pub mod pallet { ensure_none(origin)?; // set the prices - Oracle::::set(Coin::Bitcoin, Some(prices.bitcoin)); + Oracle::::set(Coin::Bitcoin, Some(10u64.pow(8))); Oracle::::set(Coin::Monero, Some(prices.monero)); Oracle::::set(Coin::Ether, Some(prices.ethereum)); Oracle::::set(Coin::Dai, Some(prices.dai)); @@ -402,11 +426,14 @@ pub mod pallet { fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { - Call::set_initial_price { ref prices, ref signature } => { - // TODO: if this is supposed to be called after a month, serai set won't still be - // in the session 0? Ideally this should pull the session from Vs pallet? - let set = ValidatorSet { network: NetworkId::Serai, session: Session(0) }; - let signers = ValidatorSets::::participants_for_latest_decided_set(NetworkId::Serai) + Call::oraclize_values { ref prices, ref signature } => { + let network = NetworkId::Serai; + let Some(session) = ValidatorSets::::session(network) else { + return Err(TransactionValidityError::from(InvalidTransaction::Custom(0))); + }; + + let set = ValidatorSet { network, session }; + let signers = ValidatorSets::::participants_for_latest_decided_set(network) .expect("no participant in the current set") .into_iter() .map(|(p, _)| p) @@ -414,17 +441,17 @@ pub mod pallet { // check this didn't get called before if Self::oraclization_is_done() { - Err(InvalidTransaction::Custom(0))?; + Err(InvalidTransaction::Custom(1))?; } // make sure signers settings the price at the end of the genesis period. // we don't need this check for tests. #[cfg(not(feature = "fast-epoch"))] if >::block_number().saturated_into::() < MONTHS { - Err(InvalidTransaction::Custom(1))?; + Err(InvalidTransaction::Custom(2))?; } - if !musig_key(set, &signers).verify(&set_initial_price_message(&set, prices), signature) { + if !musig_key(set, &signers).verify(&oraclize_values_message(&set, prices), signature) { Err(InvalidTransaction::BadProof)?; } diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index 7053e3f5b..0468fdca0 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -28,13 +28,27 @@ pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liq #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Prices { - pub bitcoin: u64, pub monero: u64, pub ethereum: u64, pub dai: u64, } -/// The message for the set_initial_price signature. -pub fn set_initial_price_message(set: &ValidatorSet, prices: &Prices) -> Vec { - (b"GenesisLiquidity-set_initial_price", set, prices).encode() +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Zeroize))] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct LiquidityAmount { + pub shares: u64, + pub coins: u64, +} + +impl LiquidityAmount { + pub fn zero() -> Self { + LiquidityAmount { shares: 0, coins: 0 } + } +} + +/// The message for the oraclize_values signature. +pub fn oraclize_values_message(set: &ValidatorSet, prices: &Prices) -> Vec { + (b"GenesisLiquidity-oraclize_values", set, prices).encode() } diff --git a/substrate/in-instructions/pallet/Cargo.toml b/substrate/in-instructions/pallet/Cargo.toml index f684cbaad..4eafd199e 100644 --- a/substrate/in-instructions/pallet/Cargo.toml +++ b/substrate/in-instructions/pallet/Cargo.toml @@ -33,6 +33,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur serai-primitives = { path = "../../primitives", default-features = false } in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false } +genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../../genesis-liquidity/primitives", default-features = false } coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } @@ -55,6 +56,7 @@ std = [ "serai-primitives/std", "in-instructions-primitives/std", + "genesis-liquidity-primitives/std", "coins-pallet/std", "dex-pallet/std", diff --git a/substrate/in-instructions/pallet/src/lib.rs b/substrate/in-instructions/pallet/src/lib.rs index 007147d6e..2667b13df 100644 --- a/substrate/in-instructions/pallet/src/lib.rs +++ b/substrate/in-instructions/pallet/src/lib.rs @@ -19,6 +19,7 @@ pub mod pallet { use sp_core::sr25519::Public; use serai_primitives::{Coin, Amount, Balance}; + use genesis_liquidity_primitives::GENESIS_LIQUIDITY_ACCOUNT; use frame_support::pallet_prelude::*; use frame_system::{pallet_prelude::*, RawOrigin}; @@ -205,6 +206,7 @@ pub mod pallet { } } InInstruction::GenesisLiquidity(address) => { + Coins::::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), instruction.balance)?; GenesisLiq::::add_coin_liquidity(address.into(), instruction.balance)?; } } diff --git a/substrate/runtime/src/abi.rs b/substrate/runtime/src/abi.rs index 812cb58d8..aa5f1a82e 100644 --- a/substrate/runtime/src/abi.rs +++ b/substrate/runtime/src/abi.rs @@ -93,8 +93,8 @@ impl From for RuntimeCall { serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } => { RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::remove_coin_liquidity { balance }) } - serai_abi::genesis_liquidity::Call::set_initial_price { prices, signature } => { - RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::set_initial_price { + serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature } => { + RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::oraclize_values { prices, signature, }) @@ -276,8 +276,8 @@ impl TryInto for RuntimeCall { genesis_liquidity::Call::remove_coin_liquidity { balance } => { serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } } - genesis_liquidity::Call::set_initial_price { prices, signature } => { - serai_abi::genesis_liquidity::Call::set_initial_price { prices, signature } + genesis_liquidity::Call::oraclize_values { prices, signature } => { + serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature } } _ => Err(())?, }), From 9d50fe7f9c86f668d7ddb12d2c03ee0a81486823 Mon Sep 17 00:00:00 2001 From: akildemir Date: Thu, 18 Jul 2024 16:35:12 +0300 Subject: [PATCH 17/18] tidy up --- substrate/abi/src/genesis_liquidity.rs | 2 +- .../client/src/serai/genesis_liquidity.rs | 14 +- substrate/client/tests/genesis_liquidity.rs | 210 +++++++++--------- substrate/genesis-liquidity/pallet/src/lib.rs | 41 ++-- .../genesis-liquidity/primitives/src/lib.rs | 10 +- substrate/runtime/src/abi.rs | 8 +- 6 files changed, 144 insertions(+), 141 deletions(-) diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs index 2047bc2b9..461284141 100644 --- a/substrate/abi/src/genesis_liquidity.rs +++ b/substrate/abi/src/genesis_liquidity.rs @@ -7,7 +7,7 @@ use primitives::*; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Call { remove_coin_liquidity { balance: Balance }, - oraclize_values { prices: Prices, signature: Signature }, + oraclize_values { values: Values, signature: Signature }, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] diff --git a/substrate/client/src/serai/genesis_liquidity.rs b/substrate/client/src/serai/genesis_liquidity.rs index 431ce90b0..04e80d745 100644 --- a/substrate/client/src/serai/genesis_liquidity.rs +++ b/substrate/client/src/serai/genesis_liquidity.rs @@ -1,5 +1,5 @@ pub use serai_abi::genesis_liquidity::primitives; -use primitives::Prices; +use primitives::{Values, LiquidityAmount}; use serai_abi::primitives::*; @@ -29,9 +29,9 @@ impl<'a> SeraiGenesisLiquidity<'a> { .await } - pub fn oraclize_values(prices: Prices, signature: Signature) -> Transaction { + pub fn oraclize_values(values: Values, signature: Signature) -> Transaction { Serai::unsigned(serai_abi::Call::GenesisLiquidity( - serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature }, + serai_abi::genesis_liquidity::Call::oraclize_values { values, signature }, )) } @@ -45,7 +45,7 @@ impl<'a> SeraiGenesisLiquidity<'a> { &self, address: &SeraiAddress, coin: Coin, - ) -> Result<(Amount, Amount), SeraiError> { + ) -> Result { Ok( self .0 @@ -55,11 +55,11 @@ impl<'a> SeraiGenesisLiquidity<'a> { (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), ) .await? - .unwrap_or((Amount(0), Amount(0))), + .unwrap_or(LiquidityAmount::zero()), ) } - pub async fn supply(&self, coin: Coin) -> Result<(Amount, Amount), SeraiError> { - Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or((Amount(0), Amount(0)))) + pub async fn supply(&self, coin: Coin) -> Result { + Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero())) } } diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs index ae0f27eb6..17f7ee904 100644 --- a/substrate/client/tests/genesis_liquidity.rs +++ b/substrate/client/tests/genesis_liquidity.rs @@ -9,14 +9,14 @@ use schnorrkel::Schnorrkel; use serai_client::{ genesis_liquidity::{ - primitives::{GENESIS_LIQUIDITY_ACCOUNT, GENESIS_LP_SHARES}, + primitives::{GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES}, SeraiGenesisLiquidity, }, validator_sets::primitives::{musig_context, Session, ValidatorSet}, }; use serai_abi::{ - genesis_liquidity::primitives::{oraclize_values_message, Prices}, + genesis_liquidity::primitives::{oraclize_values_message, Values}, primitives::COINS, }; @@ -40,59 +40,53 @@ serai_test_fast_epoch!( ); async fn test_genesis_liquidity(serai: Serai) { - // amounts - let amounts = vec![ - Amount(5_53246991), - Amount(3_14562819), - Amount(9_33648912), - Amount(150_873639000000), - Amount(248_665228000000), - Amount(451_765529000000), - ]; - - // addresses - let mut btc_addresses = vec![]; - let mut xmr_addresses = vec![]; - let addr_count = amounts.len(); - for (i, amount) in amounts.into_iter().enumerate() { - let mut address = SeraiAddress::new([0; 32]); - OsRng.fill_bytes(&mut address.0); - if i < addr_count / 2 { - btc_addresses.push((address, amount)); - } else { - xmr_addresses.push((address, amount)); + // all coins except the native + let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::>(); + + // make accounts with amounts + let mut accounts = HashMap::new(); + for coin in coins.clone() { + // make 5 accounts per coin + let mut values = vec![]; + for _ in 0 .. 5 { + let mut address = SeraiAddress::new([0; 32]); + OsRng.fill_bytes(&mut address.0); + values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals())))); } + accounts.insert(coin, values); } - // btc batch - let mut block_hash = BlockHash([0; 32]); - OsRng.fill_bytes(&mut block_hash.0); - let btc_ins = btc_addresses - .iter() - .map(|(addr, amount)| InInstructionWithBalance { - instruction: InInstruction::GenesisLiquidity(*addr), - balance: Balance { coin: Coin::Bitcoin, amount: *amount }, - }) - .collect::>(); - let batch = - Batch { network: NetworkId::Bitcoin, id: 0, block: block_hash, instructions: btc_ins }; - provide_batch(&serai, batch).await; - - // xmr batch - let mut block_hash = BlockHash([0; 32]); - OsRng.fill_bytes(&mut block_hash.0); - let xmr_ins = xmr_addresses - .iter() - .map(|(addr, amount)| InInstructionWithBalance { - instruction: InInstruction::GenesisLiquidity(*addr), - balance: Balance { coin: Coin::Monero, amount: *amount }, - }) - .collect::>(); - let batch = Batch { network: NetworkId::Monero, id: 0, block: block_hash, instructions: xmr_ins }; - provide_batch(&serai, batch).await; + // send a batch per coin + let mut batch_ids: HashMap = HashMap::new(); + for coin in coins.clone() { + // set up instructions + let instructions = accounts[&coin] + .iter() + .map(|(addr, amount)| InInstructionWithBalance { + instruction: InInstruction::GenesisLiquidity(*addr), + balance: Balance { coin, amount: *amount }, + }) + .collect::>(); + + // set up bloch hash + let mut block = BlockHash([0; 32]); + OsRng.fill_bytes(&mut block.0); + + // set up batch id + batch_ids + .entry(coin.network()) + .and_modify(|v| { + *v += 1; + }) + .or_insert(0); + + let batch = + Batch { network: coin.network(), id: batch_ids[&coin.network()], block, instructions }; + provide_batch(&serai, batch).await; + } // wait until genesis ends.. - tokio::time::timeout(tokio::time::Duration::from_secs(300), async { + tokio::time::timeout(tokio::time::Duration::from_secs(3 * 10 * 6), async { while serai.latest_finalized_block().await.unwrap().number() < 10 { tokio::time::sleep(Duration::from_secs(6)).await; } @@ -101,18 +95,22 @@ async fn test_genesis_liquidity(serai: Serai) { .unwrap(); // set prices - let prices = Prices { monero: 184100, ethereum: 4785000, dai: 1500 }; - set_prices(&serai, &prices).await; - - // wait little bit.. + let values = Values { monero: 184100, ether: 4785000, dai: 1500 }; + set_values(&serai, &values).await; + let values_map = HashMap::from([ + (Coin::Monero, values.monero), + (Coin::Ether, values.ether), + (Coin::Dai, values.dai), + ]); + + // wait a little bit.. tokio::time::sleep(Duration::from_secs(12)).await; // check total SRI supply is +100M - let last_block = serai.latest_finalized_block().await.unwrap().hash(); - let serai = serai.as_of(last_block); - let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap(); // there are 6 endowed accounts in dev-net. Take this into consideration when checking // for the total sri minted at this time. + let serai = serai.as_of_latest_finalized_block().await.unwrap(); + let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap(); let endowed_amount: u64 = 1 << 60; let total_sri = (6 * endowed_amount) + GENESIS_SRI; assert_eq!(sri, Amount(total_sri)); @@ -124,60 +122,66 @@ async fn test_genesis_liquidity(serai: Serai) { } // check pools has proper liquidity - let pool_btc = btc_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); - let pool_xmr = xmr_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); - - let pool_btc_value = pool_btc; - let pool_xmr_value = (pool_xmr * u128::from(prices.monero)) / 10u128.pow(12); - let total_value = pool_btc_value + pool_xmr_value; - - // calculated distributed SRI. We know that xmr is at the end of COINS array - // so it will be approximated to roof instead of floor after integer division. - let btc_sri = (pool_btc_value * u128::from(GENESIS_SRI)) / total_value; - let xmr_sri = ((pool_xmr_value * u128::from(GENESIS_SRI)) / total_value) + 1; - - let btc_reserves = serai.dex().get_reserves(Coin::Bitcoin).await.unwrap().unwrap(); - assert_eq!(u128::from(btc_reserves.0 .0), pool_btc); - assert_eq!(u128::from(btc_reserves.1 .0), btc_sri); - - let xmr_reserves = serai.dex().get_reserves(Coin::Monero).await.unwrap().unwrap(); - assert_eq!(u128::from(xmr_reserves.0 .0), pool_xmr); - assert_eq!(u128::from(xmr_reserves.1 .0), xmr_sri); - - // check each btc liq provider got liq tokens proportional to their value - let btc_liq_supply = serai.genesis_liquidity().supply(Coin::Bitcoin).await.unwrap(); - for (acc, amount) in btc_addresses { - let acc_liq_shares = - serai.genesis_liquidity().liquidity(&acc, Coin::Bitcoin).await.unwrap().0 .0; - - // since we can't test the ratios directly(due to integer division giving 0) - // we test whether they give the same result when multiplied by another constant. - // Following test ensures the account in fact has the right amount of shares. - let shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / btc_liq_supply.0 .0; - let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_btc).unwrap(); - assert_eq!(shares_ratio, amounts_ratio); + let mut pool_amounts = HashMap::new(); + let mut total_value = 0u128; + for coin in coins.clone() { + let total_coin = accounts[&coin].iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); + let value = if coin != Coin::Bitcoin { + (total_coin * u128::from(values_map[&coin])) / 10u128.pow(coin.decimals()) + } else { + total_coin + }; + + total_value += value; + pool_amounts.insert(coin, (total_coin, value)); } - // check each xmr liq provider got liq tokens proportional to their value - let xmr_liq_supply = serai.genesis_liquidity().supply(Coin::Monero).await.unwrap(); - for (acc, amount) in xmr_addresses { - let acc_liq_shares = - serai.genesis_liquidity().liquidity(&acc, Coin::Monero).await.unwrap().0 .0; + // check distributed SRI per pool + let mut total_sri_distributed = 0u128; + for coin in coins.clone() { + let sri = if coin == *COINS.last().unwrap() { + u128::from(GENESIS_SRI).checked_sub(total_sri_distributed).unwrap() + } else { + (pool_amounts[&coin].1 * u128::from(GENESIS_SRI)) / total_value + }; + total_sri_distributed += sri; - let shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / xmr_liq_supply.0 .0; - let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_xmr).unwrap(); - assert_eq!(shares_ratio, amounts_ratio); + let reserves = serai.dex().get_reserves(coin).await.unwrap().unwrap(); + assert_eq!(u128::from(reserves.0 .0), pool_amounts[&coin].0); // coin side + assert_eq!(u128::from(reserves.1 .0), sri); // SRI side + } + + // check each liquidity provider got liquidity tokens proportional to their value + for coin in coins { + let liq_supply = serai.genesis_liquidity().supply(coin).await.unwrap(); + for (acc, amount) in &accounts[&coin] { + let acc_liq_shares = serai.genesis_liquidity().liquidity(acc, coin).await.unwrap().shares; + + // since we can't test the ratios directly(due to integer division giving 0) + // we test whether they give the same result when multiplied by another constant. + // Following test ensures the account in fact has the right amount of shares. + let mut shares_ratio = (INITIAL_GENESIS_LP_SHARES * acc_liq_shares) / liq_supply.shares; + let amounts_ratio = + (INITIAL_GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_amounts[&coin].0).unwrap(); + + // we can tolerate 1 unit diff between them due to integer division. + if shares_ratio.abs_diff(amounts_ratio) == 1 { + shares_ratio = amounts_ratio; + } + + assert_eq!(shares_ratio, amounts_ratio); + } } // TODO: test remove the liq before/after genesis ended. } -async fn set_prices(serai: &Serai, prices: &Prices) { +async fn set_values(serai: &Serai, values: &Values) { // prepare a Musig tx to set the initial prices let pair = insecure_pair_from_name("Alice"); let public = pair.public(); - // we publish the tx in set 2 - let set = ValidatorSet { session: Session(2), network: NetworkId::Serai }; + // we publish the tx in set 4 + let set = ValidatorSet { session: Session(4), network: NetworkId::Serai }; let public_key = ::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap(); let secret_key = ::read_F::<&[u8]>( @@ -196,11 +200,11 @@ async fn set_prices(serai: &Serai, prices: &Prices) { &Schnorrkel::new(b"substrate"), &HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]), ), - &oraclize_values_message(&set, prices), + &oraclize_values_message(&set, values), ); - // set initial prices + // oraclize values let _ = - publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*prices, Signature(sig.to_bytes()))) + publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*values, Signature(sig.to_bytes()))) .await; } diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 86f2a9081..8c052a451 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -16,10 +16,11 @@ pub mod pallet { use validator_sets_pallet::{Config as VsConfig, Pallet as ValidatorSets}; use serai_primitives::{Coin, COINS, *}; - use validator_sets_primitives::{ValidatorSet, Session, musig_key}; + use validator_sets_primitives::{ValidatorSet, musig_key}; pub use genesis_liquidity_primitives as primitives; use primitives::*; + // TODO: Have a more robust way of accessing LiquidityTokens pallet. /// LiquidityTokens Pallet as an instance of coins pallet. pub type LiquidityTokens = coins_pallet::Pallet; @@ -71,7 +72,7 @@ pub mod pallet { pub(crate) type Oracle = StorageMap<_, Identity, Coin, u64, OptionQuery>; #[pallet::storage] - pub(crate) type GenesisComplete = StorageValue<_, bool, OptionQuery>; + pub(crate) type GenesisComplete = StorageValue<_, (), OptionQuery>; #[pallet::hooks] impl Hooks> for Pallet { @@ -137,14 +138,9 @@ pub mod pallet { }; total_sri_distributed = total_sri_distributed.checked_add(sri_amount).unwrap(); - // we can't add 0 liquidity - if !(pool_amount > 0 && sri_amount > 0) { - continue; - } - // actually add the liquidity to dex let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); - Dex::::add_liquidity( + let Ok(()) = Dex::::add_liquidity( origin.into(), coin, u64::try_from(pool_amount).unwrap(), @@ -152,8 +148,9 @@ pub mod pallet { u64::try_from(pool_amount).unwrap(), sri_amount, GENESIS_LIQUIDITY_ACCOUNT.into(), - ) - .unwrap(); + ) else { + continue; + }; // let everyone know about the event Self::deposit_event(Event::GenesisLiquidityAddedToPool { @@ -169,7 +166,7 @@ pub mod pallet { assert_eq!(Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin), Amount(0)); } - GenesisComplete::::set(Some(true)); + GenesisComplete::::set(Some(())); } // we accept we reached economic security once we can mint smallest amount of a network's coin @@ -222,7 +219,8 @@ pub mod pallet { }, ) } else { - let first_amount = LiquidityAmount { shares: GENESIS_LP_SHARES, coins: balance.amount.0 }; + let first_amount = + LiquidityAmount { shares: INITIAL_GENESIS_LP_SHARES, coins: balance.amount.0 }; (first_amount, first_amount) }; @@ -301,7 +299,8 @@ pub mod pallet { Liquidity::::get(balance.coin, account).unwrap_or(LiquidityAmount::zero()); let total_shares = Supply::::get(balance.coin).unwrap_or(LiquidityAmount::zero()).shares; let user_liq_tokens = Self::mul_div(total_liq_tokens, shares, total_shares)?; - let amount_to_remove = Self::mul_div(user_liq_tokens, balance.amount.0, GENESIS_LP_SHARES)?; + let amount_to_remove = + Self::mul_div(user_liq_tokens, balance.amount.0, INITIAL_GENESIS_LP_SHARES)?; // remove liquidity from pool let prev_sri = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai); @@ -364,7 +363,7 @@ pub mod pallet { }, ) } else { - if balance.amount.0 != GENESIS_LP_SHARES { + if balance.amount.0 != INITIAL_GENESIS_LP_SHARES { Err(Error::::CanOnlyRemoveFullAmount)?; } let existing = @@ -406,16 +405,16 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Operational))] // TODO pub fn oraclize_values( origin: OriginFor, - prices: Prices, + values: Values, _signature: Signature, ) -> DispatchResult { ensure_none(origin)?; // set the prices Oracle::::set(Coin::Bitcoin, Some(10u64.pow(8))); - Oracle::::set(Coin::Monero, Some(prices.monero)); - Oracle::::set(Coin::Ether, Some(prices.ethereum)); - Oracle::::set(Coin::Dai, Some(prices.dai)); + Oracle::::set(Coin::Monero, Some(values.monero)); + Oracle::::set(Coin::Ether, Some(values.ether)); + Oracle::::set(Coin::Dai, Some(values.dai)); Ok(()) } } @@ -426,7 +425,7 @@ pub mod pallet { fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { - Call::oraclize_values { ref prices, ref signature } => { + Call::oraclize_values { ref values, ref signature } => { let network = NetworkId::Serai; let Some(session) = ValidatorSets::::session(network) else { return Err(TransactionValidityError::from(InvalidTransaction::Custom(0))); @@ -444,14 +443,14 @@ pub mod pallet { Err(InvalidTransaction::Custom(1))?; } - // make sure signers settings the price at the end of the genesis period. + // make sure signers settings the value at the end of the genesis period. // we don't need this check for tests. #[cfg(not(feature = "fast-epoch"))] if >::block_number().saturated_into::() < MONTHS { Err(InvalidTransaction::Custom(2))?; } - if !musig_key(set, &signers).verify(&oraclize_values_message(&set, prices), signature) { + if !musig_key(set, &signers).verify(&oraclize_values_message(&set, values), signature) { Err(InvalidTransaction::BadProof)?; } diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index 0468fdca0..4f07dccef 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -18,7 +18,7 @@ use scale_info::TypeInfo; use serai_primitives::*; use validator_sets_primitives::ValidatorSet; -pub const GENESIS_LP_SHARES: u64 = 10_000; +pub const INITIAL_GENESIS_LP_SHARES: u64 = 10_000; // This is the account to hold and manage the genesis liquidity. pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); @@ -27,9 +27,9 @@ pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liq #[cfg_attr(feature = "std", derive(Zeroize))] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Prices { +pub struct Values { pub monero: u64, - pub ethereum: u64, + pub ether: u64, pub dai: u64, } @@ -49,6 +49,6 @@ impl LiquidityAmount { } /// The message for the oraclize_values signature. -pub fn oraclize_values_message(set: &ValidatorSet, prices: &Prices) -> Vec { - (b"GenesisLiquidity-oraclize_values", set, prices).encode() +pub fn oraclize_values_message(set: &ValidatorSet, values: &Values) -> Vec { + (b"GenesisLiquidity-oraclize_values", set, values).encode() } diff --git a/substrate/runtime/src/abi.rs b/substrate/runtime/src/abi.rs index aa5f1a82e..b479036d2 100644 --- a/substrate/runtime/src/abi.rs +++ b/substrate/runtime/src/abi.rs @@ -93,9 +93,9 @@ impl From for RuntimeCall { serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } => { RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::remove_coin_liquidity { balance }) } - serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature } => { + serai_abi::genesis_liquidity::Call::oraclize_values { values, signature } => { RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::oraclize_values { - prices, + values, signature, }) } @@ -276,8 +276,8 @@ impl TryInto for RuntimeCall { genesis_liquidity::Call::remove_coin_liquidity { balance } => { serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } } - genesis_liquidity::Call::oraclize_values { prices, signature } => { - serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature } + genesis_liquidity::Call::oraclize_values { values, signature } => { + serai_abi::genesis_liquidity::Call::oraclize_values { values, signature } } _ => Err(())?, }), From b040dce76aee82edfada6dc778f8cc355518123c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 18 Jul 2024 15:52:41 -0400 Subject: [PATCH 18/18] Misc --- substrate/client/tests/genesis_liquidity.rs | 22 ++++++++++++------- substrate/genesis-liquidity/pallet/Cargo.toml | 1 - substrate/genesis-liquidity/pallet/src/lib.rs | 6 ++--- .../genesis-liquidity/primitives/src/lib.rs | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs index 17f7ee904..8edc87d3d 100644 --- a/substrate/client/tests/genesis_liquidity.rs +++ b/substrate/client/tests/genesis_liquidity.rs @@ -85,16 +85,22 @@ async fn test_genesis_liquidity(serai: Serai) { provide_batch(&serai, batch).await; } - // wait until genesis ends.. - tokio::time::timeout(tokio::time::Duration::from_secs(3 * 10 * 6), async { - while serai.latest_finalized_block().await.unwrap().number() < 10 { - tokio::time::sleep(Duration::from_secs(6)).await; - } - }) + // wait until genesis ends + let genesis_blocks = 10; // TODO + let block_time = 6; // TODO + tokio::time::timeout( + tokio::time::Duration::from_secs(3 * (genesis_blocks * block_time)), + async { + while serai.latest_finalized_block().await.unwrap().number() < 10 { + tokio::time::sleep(Duration::from_secs(6)).await; + } + }, + ) .await .unwrap(); - // set prices + // set values relative to each other + // TODO: Random values here let values = Values { monero: 184100, ether: 4785000, dai: 1500 }; set_values(&serai, &values).await; let values_map = HashMap::from([ @@ -177,7 +183,7 @@ async fn test_genesis_liquidity(serai: Serai) { } async fn set_values(serai: &Serai, values: &Values) { - // prepare a Musig tx to set the initial prices + // prepare a Musig tx to oraclize the relative values let pair = insecure_pair_from_name("Alice"); let public = pair.public(); // we publish the tx in set 4 diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml index befeb29bc..99b71c4ce 100644 --- a/substrate/genesis-liquidity/pallet/Cargo.toml +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -18,7 +18,6 @@ ignored = ["scale", "scale-info"] [lints] workspace = true - [dependencies] scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2", default-features = false, features = ["derive"] } diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 8c052a451..ad371ae41 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -84,7 +84,7 @@ pub mod pallet { let final_block = MONTHS; // Distribute the genesis sri to pools after a month - if n.saturated_into::() >= final_block && + if (n.saturated_into::() >= final_block) && Self::oraclization_is_done() && GenesisComplete::::get().is_none() { @@ -410,8 +410,8 @@ pub mod pallet { ) -> DispatchResult { ensure_none(origin)?; - // set the prices - Oracle::::set(Coin::Bitcoin, Some(10u64.pow(8))); + // set their relative values + Oracle::::set(Coin::Bitcoin, Some(10u64.pow(Coin::Bitcoin.decimals()))); Oracle::::set(Coin::Monero, Some(values.monero)); Oracle::::set(Coin::Ether, Some(values.ether)); Oracle::::set(Coin::Dai, Some(values.dai)); diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index 4f07dccef..4e4c277ca 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -21,7 +21,7 @@ use validator_sets_primitives::ValidatorSet; pub const INITIAL_GENESIS_LP_SHARES: u64 = 10_000; // This is the account to hold and manage the genesis liquidity. -pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); +pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"GenesisLiquidity-account"); #[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize))]