Skip to content

Commit cccc1fc

Browse files
akildemirakildemirkayabaNerve
authored
Implement block emissions (#551)
* add genesis liquidity implementation * add missing deposit event * fix CI issues * minor fixes * make math safer * fix fmt * implement block emissions * make remove liquidity an authorized call * implement setting initial values for coins * add genesis liquidity test & misc fixes * updato develop latest * fix rotation test * fix licencing * add fast-epoch feature * only create the pool when adding liquidity first time * add initial reward era test * test whole pre ec security emissions * fix clippy * add swap-to-staked-sri feature * rebase changes * fix tests * Remove accidentally commited ETH ABI files * fix some pr comments * Finish up fixing pr comments * exclude SRI from is_allowed check * Misc changes --------- Co-authored-by: akildemir <aeg_asd@hotmail.com> Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
1 parent bf1c493 commit cccc1fc

File tree

36 files changed

+1280
-303
lines changed

36 files changed

+1280
-303
lines changed

Cargo.lock

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deny.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ exceptions = [
5454
{ allow = ["AGPL-3.0"], name = "serai-dex-pallet" },
5555

5656
{ allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" },
57+
{ allow = ["AGPL-3.0"], name = "serai-emissions-pallet" },
5758

5859
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
5960

substrate/abi/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ serai-primitives = { path = "../primitives", version = "0.1", default-features =
3434
serai-coins-primitives = { path = "../coins/primitives", version = "0.1", default-features = false }
3535
serai-validator-sets-primitives = { path = "../validator-sets/primitives", version = "0.1", default-features = false }
3636
serai-genesis-liquidity-primitives = { path = "../genesis-liquidity/primitives", version = "0.1", default-features = false }
37+
serai-emissions-primitives = { path = "../emissions/primitives", version = "0.1", default-features = false }
3738
serai-in-instructions-primitives = { path = "../in-instructions/primitives", version = "0.1", default-features = false }
3839
serai-signals-primitives = { path = "../signals/primitives", version = "0.1", default-features = false }
3940

@@ -57,6 +58,7 @@ std = [
5758
"serai-coins-primitives/std",
5859
"serai-validator-sets-primitives/std",
5960
"serai-genesis-liquidity-primitives/std",
61+
"serai-emissions-primitives/std",
6062
"serai-in-instructions-primitives/std",
6163
"serai-signals-primitives/std",
6264
]

substrate/abi/src/emissions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub use serai_emissions_primitives as primitives;

substrate/abi/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ pub mod liquidity_tokens;
1616
pub mod dex;
1717

1818
pub mod validator_sets;
19-
pub mod in_instructions;
20-
pub mod signals;
2119

2220
pub mod genesis_liquidity;
21+
pub mod emissions;
22+
23+
pub mod in_instructions;
24+
25+
pub mod signals;
2326

2427
pub mod babe;
2528
pub mod grandpa;
@@ -32,8 +35,8 @@ pub enum Call {
3235
Coins(coins::Call),
3336
LiquidityTokens(liquidity_tokens::Call),
3437
Dex(dex::Call),
35-
GenesisLiquidity(genesis_liquidity::Call),
3638
ValidatorSets(validator_sets::Call),
39+
GenesisLiquidity(genesis_liquidity::Call),
3740
InInstructions(in_instructions::Call),
3841
Signals(signals::Call),
3942
Babe(babe::Call),
@@ -54,8 +57,9 @@ pub enum Event {
5457
Coins(coins::Event),
5558
LiquidityTokens(liquidity_tokens::Event),
5659
Dex(dex::Event),
57-
GenesisLiquidity(genesis_liquidity::Event),
5860
ValidatorSets(validator_sets::Event),
61+
GenesisLiquidity(genesis_liquidity::Event),
62+
Emissions,
5963
InInstructions(in_instructions::Event),
6064
Signals(signals::Event),
6165
Babe,

substrate/client/src/serai/dex.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use sp_core::bounded_vec::BoundedVec;
22
use serai_abi::primitives::{SeraiAddress, Amount, Coin};
33

4-
use scale::{decode_from_bytes, Encode};
5-
6-
use crate::{Serai, SeraiError, TemporalSerai};
4+
use crate::{SeraiError, TemporalSerai};
75

86
pub type DexEvent = serai_abi::dex::Event;
97

8+
const PALLET: &str = "Dex";
9+
1010
#[derive(Clone, Copy)]
1111
pub struct SeraiDex<'a>(pub(crate) &'a TemporalSerai<'a>);
1212
impl<'a> SeraiDex<'a> {
@@ -62,17 +62,10 @@ impl<'a> SeraiDex<'a> {
6262

6363
/// Returns the reserves of `coin:SRI` pool.
6464
pub async fn get_reserves(&self, coin: Coin) -> Result<Option<(Amount, Amount)>, SeraiError> {
65-
let reserves = self
66-
.0
67-
.serai
68-
.call(
69-
"state_call",
70-
["DexApi_get_reserves".to_string(), hex::encode((coin, Coin::Serai).encode())],
71-
)
72-
.await?;
73-
let bytes = Serai::hex_decode(reserves)?;
74-
let result = decode_from_bytes::<Option<(u64, u64)>>(bytes.into())
75-
.map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?;
76-
Ok(result.map(|amounts| (Amount(amounts.0), Amount(amounts.1))))
65+
self.0.runtime_api("DexApi_get_reserves", (coin, Coin::Serai)).await
66+
}
67+
68+
pub async fn oracle_value(&self, coin: Coin) -> Result<Option<Amount>, SeraiError> {
69+
self.0.storage(PALLET, "SecurityOracleValue", coin).await
7770
}
7871
}

substrate/client/src/serai/genesis_liquidity.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,9 @@ impl<'a> SeraiGenesisLiquidity<'a> {
6262
pub async fn supply(&self, coin: Coin) -> Result<LiquidityAmount, SeraiError> {
6363
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
6464
}
65+
66+
pub async fn genesis_complete(&self) -> Result<bool, SeraiError> {
67+
let result: Option<()> = self.0.storage(PALLET, "GenesisComplete", ()).await?;
68+
Ok(result.is_some())
69+
}
6570
}

substrate/client/src/serai/mod.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -198,17 +198,6 @@ impl Serai {
198198
Ok(())
199199
}
200200

201-
// TODO: move this into substrate/client/src/validator_sets.rs
202-
async fn active_network_validators(&self, network: NetworkId) -> Result<Vec<Public>, SeraiError> {
203-
let validators: String = self
204-
.call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())])
205-
.await?;
206-
let bytes = Self::hex_decode(validators)?;
207-
let r = Vec::<Public>::decode(&mut bytes.as_slice())
208-
.map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?;
209-
Ok(r)
210-
}
211-
212201
pub async fn latest_finalized_block_hash(&self) -> Result<[u8; 32], SeraiError> {
213202
let hash: String = self.call("chain_getFinalizedHead", ()).await?;
214203
Self::hex_decode(hash)?.try_into().map_err(|_| {
@@ -378,6 +367,28 @@ impl<'a> TemporalSerai<'a> {
378367
})?))
379368
}
380369

370+
async fn runtime_api<P: Encode, R: Decode>(
371+
&self,
372+
method: &'static str,
373+
params: P,
374+
) -> Result<R, SeraiError> {
375+
let result: String = self
376+
.serai
377+
.call(
378+
"state_call",
379+
[method.to_string(), hex::encode(params.encode()), hex::encode(self.block)],
380+
)
381+
.await?;
382+
383+
let bytes = Serai::hex_decode(result.clone())?;
384+
R::decode(&mut bytes.as_slice()).map_err(|_| {
385+
SeraiError::InvalidRuntime(format!(
386+
"different type than what is expected to be returned, raw value: {}",
387+
hex::encode(result)
388+
))
389+
})
390+
}
391+
381392
pub fn coins(&'a self) -> SeraiCoins<'a> {
382393
SeraiCoins(self)
383394
}

substrate/client/src/serai/validator_sets.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ impl<'a> SeraiValidatorSets<'a> {
163163
&self,
164164
network: NetworkId,
165165
) -> Result<Vec<Public>, SeraiError> {
166-
self.0.serai.active_network_validators(network).await
166+
self.0.runtime_api("SeraiRuntimeApi_validators", network).await
167167
}
168168

169169
// TODO: Store these separately since we almost never need both at once?
@@ -178,6 +178,14 @@ impl<'a> SeraiValidatorSets<'a> {
178178
self.0.storage(PALLET, "PendingSlashReport", network).await
179179
}
180180

181+
pub async fn session_begin_block(
182+
&self,
183+
network: NetworkId,
184+
session: Session,
185+
) -> Result<Option<u64>, SeraiError> {
186+
self.0.storage(PALLET, "SessionBeginBlock", (network, session)).await
187+
}
188+
181189
pub fn set_keys(
182190
network: NetworkId,
183191
removed_participants: sp_runtime::BoundedVec<
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use std::collections::HashMap;
2+
3+
use rand_core::{RngCore, OsRng};
4+
use zeroize::Zeroizing;
5+
6+
use ciphersuite::{Ciphersuite, Ristretto};
7+
use frost::dkg::musig::musig;
8+
use schnorrkel::Schnorrkel;
9+
10+
use sp_core::{sr25519::Signature, Pair as PairTrait};
11+
12+
use serai_abi::{
13+
genesis_liquidity::primitives::{oraclize_values_message, Values},
14+
validator_sets::primitives::{musig_context, Session, ValidatorSet},
15+
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
16+
primitives::{
17+
Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name,
18+
},
19+
};
20+
21+
use serai_client::{Serai, SeraiGenesisLiquidity};
22+
23+
use crate::common::{in_instructions::provide_batch, tx::publish_tx};
24+
25+
#[allow(dead_code)]
26+
pub async fn set_up_genesis(
27+
serai: &Serai,
28+
coins: &[Coin],
29+
values: &HashMap<Coin, u64>,
30+
) -> (HashMap<Coin, Vec<(SeraiAddress, Amount)>>, HashMap<NetworkId, u32>) {
31+
// make accounts with amounts
32+
let mut accounts = HashMap::new();
33+
for coin in coins {
34+
// make 5 accounts per coin
35+
let mut values = vec![];
36+
for _ in 0 .. 5 {
37+
let mut address = SeraiAddress::new([0; 32]);
38+
OsRng.fill_bytes(&mut address.0);
39+
values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals()))));
40+
}
41+
accounts.insert(*coin, values);
42+
}
43+
44+
// send a batch per coin
45+
let mut batch_ids: HashMap<NetworkId, u32> = HashMap::new();
46+
for coin in coins {
47+
// set up instructions
48+
let instructions = accounts[coin]
49+
.iter()
50+
.map(|(addr, amount)| InInstructionWithBalance {
51+
instruction: InInstruction::GenesisLiquidity(*addr),
52+
balance: Balance { coin: *coin, amount: *amount },
53+
})
54+
.collect::<Vec<_>>();
55+
56+
// set up bloch hash
57+
let mut block = BlockHash([0; 32]);
58+
OsRng.fill_bytes(&mut block.0);
59+
60+
// set up batch id
61+
batch_ids
62+
.entry(coin.network())
63+
.and_modify(|v| {
64+
*v += 1;
65+
})
66+
.or_insert(0);
67+
68+
let batch =
69+
Batch { network: coin.network(), id: batch_ids[&coin.network()], block, instructions };
70+
provide_batch(serai, batch).await;
71+
}
72+
73+
// set values relative to each other. We can do that without checking for genesis period blocks
74+
// since we are running in test(fast-epoch) mode.
75+
// TODO: Random values here
76+
let values =
77+
Values { monero: values[&Coin::Monero], ether: values[&Coin::Ether], dai: values[&Coin::Dai] };
78+
set_values(serai, &values).await;
79+
80+
(accounts, batch_ids)
81+
}
82+
83+
#[allow(dead_code)]
84+
async fn set_values(serai: &Serai, values: &Values) {
85+
// prepare a Musig tx to oraclize the relative values
86+
let pair = insecure_pair_from_name("Alice");
87+
let public = pair.public();
88+
// we publish the tx in set 1
89+
let set = ValidatorSet { session: Session(1), network: NetworkId::Serai };
90+
91+
let public_key = <Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap();
92+
let secret_key = <Ristretto as Ciphersuite>::read_F::<&[u8]>(
93+
&mut pair.as_ref().secret.to_bytes()[.. 32].as_ref(),
94+
)
95+
.unwrap();
96+
97+
assert_eq!(Ristretto::generator() * secret_key, public_key);
98+
let threshold_keys =
99+
musig::<Ristretto>(&musig_context(set), &Zeroizing::new(secret_key), &[public_key]).unwrap();
100+
101+
let sig = frost::tests::sign_without_caching(
102+
&mut OsRng,
103+
frost::tests::algorithm_machines(
104+
&mut OsRng,
105+
&Schnorrkel::new(b"substrate"),
106+
&HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]),
107+
),
108+
&oraclize_values_message(&set, values),
109+
);
110+
111+
// oraclize values
112+
let _ =
113+
publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*values, Signature(sig.to_bytes())))
114+
.await;
115+
}

substrate/client/tests/common/in_instructions.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use sp_core::Pair;
1010

1111
use serai_client::{
1212
primitives::{insecure_pair_from_name, BlockHash, NetworkId, Balance, SeraiAddress},
13-
validator_sets::primitives::{Session, ValidatorSet, KeyPair},
13+
validator_sets::primitives::{ValidatorSet, KeyPair},
1414
in_instructions::{
1515
primitives::{Batch, SignedBatch, batch_message, InInstruction, InInstructionWithBalance},
1616
InInstructionsEvent,
@@ -22,12 +22,12 @@ use crate::common::{tx::publish_tx, validator_sets::set_keys};
2222

2323
#[allow(dead_code)]
2424
pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
25-
// TODO: Get the latest session
26-
let set = ValidatorSet { session: Session(0), network: batch.network };
25+
let serai_latest = serai.as_of_latest_finalized_block().await.unwrap();
26+
let session = serai_latest.validator_sets().session(batch.network).await.unwrap().unwrap();
27+
let set = ValidatorSet { session, network: batch.network };
28+
2729
let pair = insecure_pair_from_name(&format!("ValidatorSet {set:?}"));
28-
let keys = if let Some(keys) =
29-
serai.as_of_latest_finalized_block().await.unwrap().validator_sets().keys(set).await.unwrap()
30-
{
30+
let keys = if let Some(keys) = serai_latest.validator_sets().keys(set).await.unwrap() {
3131
keys
3232
} else {
3333
let keys = KeyPair(pair.public(), vec![].try_into().unwrap());

substrate/client/tests/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod tx;
22
pub mod validator_sets;
33
pub mod in_instructions;
44
pub mod dex;
5+
pub mod genesis_liquidity;
56

67
#[macro_export]
78
macro_rules! serai_test {

0 commit comments

Comments
 (0)