mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-14 06:59:24 +00:00
Merge branch 'next' into serai-rpc
This commit is contained in:
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["serai"]
|
||||
edition = "2021"
|
||||
rust-version = "1.74"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -18,10 +18,13 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
zeroize = "^1.5"
|
||||
thiserror = { version = "1", optional = true }
|
||||
thiserror = { version = "2", default-features = false, optional = true }
|
||||
|
||||
bitvec = { version = "1", default-features = false, features = ["alloc", "serde"] }
|
||||
|
||||
hex = "0.4"
|
||||
scale = { package = "parity-scale-codec", version = "3" }
|
||||
borsh = { version = "1", features = ["derive"] }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
|
||||
@@ -39,7 +42,7 @@ simple-request = { path = "../../common/request", version = "0.1", optional = tr
|
||||
bitcoin = { version = "0.32", optional = true }
|
||||
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", version = "0.4", optional = true }
|
||||
monero-wallet = { path = "../../networks/monero/wallet", version = "0.1.0", default-features = false, features = ["std"], optional = true }
|
||||
monero-address = { path = "../../networks/monero/wallet/address", version = "0.1.0", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_core = "0.6"
|
||||
@@ -57,12 +60,13 @@ dockertest = "0.5"
|
||||
serai-docker-tests = { path = "../../tests/docker" }
|
||||
|
||||
[features]
|
||||
serai = ["thiserror", "serde", "serde_json", "serai-abi/serde", "multiaddr", "sp-core", "sp-runtime", "frame-system", "simple-request"]
|
||||
serai = ["thiserror/std", "serde", "serde_json", "serai-abi/serde", "multiaddr", "sp-core", "sp-runtime", "frame-system", "simple-request"]
|
||||
borsh = ["serai-abi/borsh"]
|
||||
|
||||
networks = []
|
||||
bitcoin = ["networks", "dep:bitcoin"]
|
||||
monero = ["networks", "ciphersuite/ed25519", "monero-wallet"]
|
||||
ethereum = ["networks"]
|
||||
monero = ["networks", "ciphersuite/ed25519", "monero-address"]
|
||||
|
||||
# Assumes the default usage is to use Serai as a DEX, which doesn't actually
|
||||
# require connecting to a Serai node
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use core::{str::FromStr, fmt};
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use bitcoin::{
|
||||
hashes::{Hash as HashTrait, hash160::Hash},
|
||||
@@ -10,47 +11,10 @@ use bitcoin::{
|
||||
address::{AddressType, NetworkChecked, Address as BAddress},
|
||||
};
|
||||
|
||||
#[derive(Clone, Eq, Debug)]
|
||||
pub struct Address(ScriptBuf);
|
||||
use crate::primitives::ExternalAddress;
|
||||
|
||||
impl PartialEq for Address {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Since Serai defines the Bitcoin-address specification as a variant of the script alone,
|
||||
// define equivalency as the script alone
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for ScriptBuf {
|
||||
fn from(addr: Address) -> ScriptBuf {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Address {
|
||||
type Err = ();
|
||||
fn from_str(str: &str) -> Result<Address, ()> {
|
||||
Address::new(
|
||||
BAddress::from_str(str)
|
||||
.map_err(|_| ())?
|
||||
.require_network(Network::Bitcoin)
|
||||
.map_err(|_| ())?
|
||||
.script_pubkey(),
|
||||
)
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
BAddress::<NetworkChecked>::from_script(&self.0, Network::Bitcoin)
|
||||
.map_err(|_| fmt::Error)?
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
// SCALE-encoded variant of Monero addresses.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
// SCALE-encodable representation of Bitcoin addresses, used internally.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, BorshSerialize, BorshDeserialize)]
|
||||
enum EncodedAddress {
|
||||
P2PKH([u8; 20]),
|
||||
P2SH([u8; 20]),
|
||||
@@ -59,34 +23,13 @@ enum EncodedAddress {
|
||||
P2TR([u8; 32]),
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Address {
|
||||
impl TryFrom<&ScriptBuf> for EncodedAddress {
|
||||
type Error = ();
|
||||
fn try_from(data: Vec<u8>) -> Result<Address, ()> {
|
||||
Ok(Address(match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? {
|
||||
EncodedAddress::P2PKH(hash) => {
|
||||
ScriptBuf::new_p2pkh(&PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2SH(hash) => {
|
||||
ScriptBuf::new_p2sh(&ScriptHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2WPKH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2WSH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2TR(key) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V1, &key).unwrap())
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_to_vec(addr: &Address) -> Result<Vec<u8>, ()> {
|
||||
let parsed_addr =
|
||||
BAddress::<NetworkChecked>::from_script(&addr.0, Network::Bitcoin).map_err(|_| ())?;
|
||||
Ok(
|
||||
(match parsed_addr.address_type() {
|
||||
fn try_from(script_buf: &ScriptBuf) -> Result<Self, ()> {
|
||||
// This uses mainnet as our encodings don't specify a network.
|
||||
let parsed_addr =
|
||||
BAddress::<NetworkChecked>::from_script(script_buf, Network::Bitcoin).map_err(|_| ())?;
|
||||
Ok(match parsed_addr.address_type() {
|
||||
Some(AddressType::P2pkh) => {
|
||||
EncodedAddress::P2PKH(*parsed_addr.pubkey_hash().unwrap().as_raw_hash().as_byte_array())
|
||||
}
|
||||
@@ -110,23 +53,119 @@ fn try_to_vec(addr: &Address) -> Result<Vec<u8>, ()> {
|
||||
}
|
||||
_ => Err(())?,
|
||||
})
|
||||
.encode(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for Vec<u8> {
|
||||
fn from(addr: Address) -> Vec<u8> {
|
||||
impl From<EncodedAddress> for ScriptBuf {
|
||||
fn from(encoded: EncodedAddress) -> Self {
|
||||
match encoded {
|
||||
EncodedAddress::P2PKH(hash) => {
|
||||
ScriptBuf::new_p2pkh(&PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2SH(hash) => {
|
||||
ScriptBuf::new_p2sh(&ScriptHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2WPKH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2WSH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2TR(key) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V1, &key).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Bitcoin address usable with Serai.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Address(ScriptBuf);
|
||||
|
||||
// Support consuming into the underlying ScriptBuf.
|
||||
impl From<Address> for ScriptBuf {
|
||||
fn from(addr: Address) -> ScriptBuf {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Address> for BAddress {
|
||||
fn from(addr: &Address) -> BAddress {
|
||||
// This fails if the script doesn't have an address representation, yet all our representable
|
||||
// addresses' scripts do
|
||||
BAddress::<NetworkChecked>::from_script(&addr.0, Network::Bitcoin).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Support converting a string into an address.
|
||||
impl FromStr for Address {
|
||||
type Err = ();
|
||||
fn from_str(str: &str) -> Result<Address, ()> {
|
||||
Address::new(
|
||||
BAddress::from_str(str)
|
||||
.map_err(|_| ())?
|
||||
.require_network(Network::Bitcoin)
|
||||
.map_err(|_| ())?
|
||||
.script_pubkey(),
|
||||
)
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
// Support converting an address into a string.
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
BAddress::from(self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExternalAddress> for Address {
|
||||
type Error = ();
|
||||
fn try_from(data: ExternalAddress) -> Result<Address, ()> {
|
||||
// Decode as an EncodedAddress, then map to a ScriptBuf
|
||||
let mut data = data.as_ref();
|
||||
let encoded = EncodedAddress::decode(&mut data).map_err(|_| ())?;
|
||||
if !data.is_empty() {
|
||||
Err(())?
|
||||
}
|
||||
Ok(Address(ScriptBuf::from(encoded)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for EncodedAddress {
|
||||
fn from(addr: Address) -> EncodedAddress {
|
||||
// Safe since only encodable addresses can be created
|
||||
try_to_vec(&addr).unwrap()
|
||||
EncodedAddress::try_from(&addr.0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for ExternalAddress {
|
||||
fn from(addr: Address) -> ExternalAddress {
|
||||
// Safe since all variants are fixed-length and fit into MAX_ADDRESS_LEN
|
||||
ExternalAddress::new(EncodedAddress::from(addr).encode()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl BorshSerialize for Address {
|
||||
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
|
||||
EncodedAddress::from(self.clone()).serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorshDeserialize for Address {
|
||||
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
|
||||
Ok(Self(ScriptBuf::from(EncodedAddress::deserialize_reader(reader)?)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Address {
|
||||
pub fn new(address: ScriptBuf) -> Option<Self> {
|
||||
let res = Self(address);
|
||||
if try_to_vec(&res).is_ok() {
|
||||
return Some(res);
|
||||
/// Create a new Address from a ScriptBuf.
|
||||
pub fn new(script_buf: ScriptBuf) -> Option<Self> {
|
||||
// If we can represent this Script, it's an acceptable address
|
||||
if EncodedAddress::try_from(&script_buf).is_ok() {
|
||||
return Some(Self(script_buf));
|
||||
}
|
||||
// Else, it isn't acceptable
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
129
substrate/client/src/networks/ethereum.rs
Normal file
129
substrate/client/src/networks/ethereum.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use core::str::FromStr;
|
||||
use std::io::Read;
|
||||
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use crate::primitives::{MAX_ADDRESS_LEN, ExternalAddress};
|
||||
|
||||
/// THe maximum amount of gas an address is allowed to specify as its gas limit.
|
||||
///
|
||||
/// Payments to an address with a gas limit which exceed this value will be dropped entirely.
|
||||
pub const ADDRESS_GAS_LIMIT: u32 = 950_000;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub struct ContractDeployment {
|
||||
/// The gas limit to use for this contract's execution.
|
||||
///
|
||||
/// This MUST be less than the Serai gas limit. The cost of it, and the associated costs with
|
||||
/// making this transaction, will be deducted from the amount transferred.
|
||||
gas_limit: u32,
|
||||
/// The initialization code of the contract to deploy.
|
||||
///
|
||||
/// This contract will be deployed (executing the initialization code). No further calls will
|
||||
/// be made.
|
||||
code: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A contract to deploy, enabling executing arbitrary code.
|
||||
impl ContractDeployment {
|
||||
pub fn new(gas_limit: u32, code: Vec<u8>) -> Option<Self> {
|
||||
// Check the gas limit is less the address gas limit
|
||||
if gas_limit > ADDRESS_GAS_LIMIT {
|
||||
None?;
|
||||
}
|
||||
|
||||
// The max address length, minus the type byte, minus the size of the gas
|
||||
const MAX_CODE_LEN: usize = (MAX_ADDRESS_LEN as usize) - (1 + core::mem::size_of::<u32>());
|
||||
if code.len() > MAX_CODE_LEN {
|
||||
None?;
|
||||
}
|
||||
|
||||
Some(Self { gas_limit, code })
|
||||
}
|
||||
|
||||
pub fn gas_limit(&self) -> u32 {
|
||||
self.gas_limit
|
||||
}
|
||||
pub fn code(&self) -> &[u8] {
|
||||
&self.code
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of an Ethereum address.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Address {
|
||||
/// A traditional address.
|
||||
Address([u8; 20]),
|
||||
/// A contract to deploy, enabling executing arbitrary code.
|
||||
Contract(ContractDeployment),
|
||||
}
|
||||
|
||||
impl From<[u8; 20]> for Address {
|
||||
fn from(address: [u8; 20]) -> Self {
|
||||
Address::Address(address)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExternalAddress> for Address {
|
||||
type Error = ();
|
||||
fn try_from(data: ExternalAddress) -> Result<Address, ()> {
|
||||
let mut kind = [0xff];
|
||||
let mut reader: &[u8] = data.as_ref();
|
||||
reader.read_exact(&mut kind).map_err(|_| ())?;
|
||||
Ok(match kind[0] {
|
||||
0 => {
|
||||
let mut address = [0xff; 20];
|
||||
reader.read_exact(&mut address).map_err(|_| ())?;
|
||||
Address::Address(address)
|
||||
}
|
||||
1 => {
|
||||
let mut gas_limit = [0xff; 4];
|
||||
reader.read_exact(&mut gas_limit).map_err(|_| ())?;
|
||||
Address::Contract(ContractDeployment {
|
||||
gas_limit: {
|
||||
let gas_limit = u32::from_le_bytes(gas_limit);
|
||||
if gas_limit > ADDRESS_GAS_LIMIT {
|
||||
Err(())?;
|
||||
}
|
||||
gas_limit
|
||||
},
|
||||
// The code is whatever's left since the ExternalAddress is a delimited container of
|
||||
// appropriately bounded length
|
||||
code: reader.to_vec(),
|
||||
})
|
||||
}
|
||||
_ => Err(())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<Address> for ExternalAddress {
|
||||
fn from(address: Address) -> ExternalAddress {
|
||||
let mut res = Vec::with_capacity(1 + 20);
|
||||
match address {
|
||||
Address::Address(address) => {
|
||||
res.push(0);
|
||||
res.extend(&address);
|
||||
}
|
||||
Address::Contract(ContractDeployment { gas_limit, code }) => {
|
||||
res.push(1);
|
||||
res.extend(&gas_limit.to_le_bytes());
|
||||
res.extend(&code);
|
||||
}
|
||||
}
|
||||
// We only construct addresses whose code is small enough this can safely be constructed
|
||||
ExternalAddress::new(res).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Address {
|
||||
type Err = ();
|
||||
fn from_str(str: &str) -> Result<Address, ()> {
|
||||
let Some(address) = str.strip_prefix("0x") else { Err(())? };
|
||||
if address.len() != 40 {
|
||||
Err(())?
|
||||
};
|
||||
Ok(Address::Address(
|
||||
hex::decode(address.to_lowercase()).map_err(|_| ())?.try_into().map_err(|_| ())?,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
#[cfg(feature = "bitcoin")]
|
||||
pub mod bitcoin;
|
||||
|
||||
#[cfg(feature = "ethereum")]
|
||||
pub mod ethereum;
|
||||
|
||||
#[cfg(feature = "monero")]
|
||||
pub mod monero;
|
||||
|
||||
@@ -1,102 +1,141 @@
|
||||
use core::{str::FromStr, fmt};
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
|
||||
use ciphersuite::{Ciphersuite, Ed25519};
|
||||
|
||||
use monero_wallet::address::{AddressError, Network, AddressType, MoneroAddress};
|
||||
use monero_address::{Network, AddressType as MoneroAddressType, MoneroAddress};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Address(MoneroAddress);
|
||||
impl Address {
|
||||
pub fn new(address: MoneroAddress) -> Option<Address> {
|
||||
if address.payment_id().is_some() {
|
||||
return None;
|
||||
}
|
||||
Some(Address(address))
|
||||
}
|
||||
}
|
||||
use crate::primitives::ExternalAddress;
|
||||
|
||||
impl FromStr for Address {
|
||||
type Err = AddressError;
|
||||
fn from_str(str: &str) -> Result<Address, AddressError> {
|
||||
MoneroAddress::from_str(Network::Mainnet, str).map(Address)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
// SCALE-encoded variant of Monero addresses.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
enum EncodedAddressType {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum AddressType {
|
||||
Legacy,
|
||||
Subaddress,
|
||||
Featured(u8),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
struct EncodedAddress {
|
||||
kind: EncodedAddressType,
|
||||
spend: [u8; 32],
|
||||
view: [u8; 32],
|
||||
/// A representation of a Monero address.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Address {
|
||||
kind: AddressType,
|
||||
spend: <Ed25519 as Ciphersuite>::G,
|
||||
view: <Ed25519 as Ciphersuite>::G,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Address {
|
||||
type Error = ();
|
||||
fn try_from(data: Vec<u8>) -> Result<Address, ()> {
|
||||
// Decode as SCALE
|
||||
let addr = EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())?;
|
||||
// Convert over
|
||||
Ok(Address(MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
match addr.kind {
|
||||
EncodedAddressType::Legacy => AddressType::Legacy,
|
||||
EncodedAddressType::Subaddress => AddressType::Subaddress,
|
||||
EncodedAddressType::Featured(flags) => {
|
||||
let subaddress = (flags & 1) != 0;
|
||||
let integrated = (flags & (1 << 1)) != 0;
|
||||
let guaranteed = (flags & (1 << 2)) != 0;
|
||||
if integrated {
|
||||
Err(())?;
|
||||
}
|
||||
AddressType::Featured { subaddress, payment_id: None, guaranteed }
|
||||
}
|
||||
},
|
||||
Ed25519::read_G::<&[u8]>(&mut addr.spend.as_ref()).map_err(|_| ())?.0,
|
||||
Ed25519::read_G::<&[u8]>(&mut addr.view.as_ref()).map_err(|_| ())?.0,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<MoneroAddress> for Address {
|
||||
fn into(self) -> MoneroAddress {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<Vec<u8>> for Address {
|
||||
fn into(self) -> Vec<u8> {
|
||||
EncodedAddress {
|
||||
kind: match self.0.kind() {
|
||||
AddressType::Legacy => EncodedAddressType::Legacy,
|
||||
AddressType::LegacyIntegrated(_) => {
|
||||
panic!("integrated address became Serai Monero address")
|
||||
}
|
||||
AddressType::Subaddress => EncodedAddressType::Subaddress,
|
||||
AddressType::Featured { subaddress, payment_id, guaranteed } => {
|
||||
debug_assert!(payment_id.is_none());
|
||||
EncodedAddressType::Featured(u8::from(*subaddress) + (u8::from(*guaranteed) << 2))
|
||||
}
|
||||
},
|
||||
spend: self.0.spend().compress().0,
|
||||
view: self.0.view().compress().0,
|
||||
fn byte_for_kind(kind: AddressType) -> u8 {
|
||||
// We use the second and third highest bits for the type
|
||||
// This leaves the top bit open for interpretation as a VarInt later
|
||||
match kind {
|
||||
AddressType::Legacy => 0,
|
||||
AddressType::Subaddress => 1 << 5,
|
||||
AddressType::Featured(flags) => {
|
||||
// The flags only take up the low three bits
|
||||
debug_assert!(flags <= 0b111);
|
||||
(2 << 5) | flags
|
||||
}
|
||||
.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl borsh::BorshSerialize for Address {
|
||||
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
|
||||
writer.write_all(&[byte_for_kind(self.kind)])?;
|
||||
writer.write_all(&self.spend.compress().to_bytes())?;
|
||||
writer.write_all(&self.view.compress().to_bytes())
|
||||
}
|
||||
}
|
||||
impl borsh::BorshDeserialize for Address {
|
||||
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
|
||||
let mut kind_byte = [0xff];
|
||||
reader.read_exact(&mut kind_byte)?;
|
||||
let kind_byte = kind_byte[0];
|
||||
let kind = match kind_byte >> 5 {
|
||||
0 => AddressType::Legacy,
|
||||
1 => AddressType::Subaddress,
|
||||
2 => AddressType::Featured(kind_byte & 0b111),
|
||||
_ => Err(borsh::io::Error::other("unrecognized type"))?,
|
||||
};
|
||||
// Check this wasn't malleated
|
||||
if byte_for_kind(kind) != kind_byte {
|
||||
Err(borsh::io::Error::other("malleated type byte"))?;
|
||||
}
|
||||
let spend = Ed25519::read_G(reader)?;
|
||||
let view = Ed25519::read_G(reader)?;
|
||||
Ok(Self { kind, spend, view })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MoneroAddress> for Address {
|
||||
type Error = ();
|
||||
fn try_from(address: MoneroAddress) -> Result<Self, ()> {
|
||||
let spend = address.spend().compress().to_bytes();
|
||||
let view = address.view().compress().to_bytes();
|
||||
let kind = match address.kind() {
|
||||
MoneroAddressType::Legacy => AddressType::Legacy,
|
||||
MoneroAddressType::LegacyIntegrated(_) => Err(())?,
|
||||
MoneroAddressType::Subaddress => AddressType::Subaddress,
|
||||
MoneroAddressType::Featured { subaddress, payment_id, guaranteed } => {
|
||||
if payment_id.is_some() {
|
||||
Err(())?
|
||||
}
|
||||
// This maintains the same bit layout as featured addresses use
|
||||
AddressType::Featured(u8::from(*subaddress) + (u8::from(*guaranteed) << 2))
|
||||
}
|
||||
};
|
||||
Ok(Address {
|
||||
kind,
|
||||
spend: Ed25519::read_G(&mut spend.as_slice()).map_err(|_| ())?,
|
||||
view: Ed25519::read_G(&mut view.as_slice()).map_err(|_| ())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for MoneroAddress {
|
||||
fn from(address: Address) -> MoneroAddress {
|
||||
let kind = match address.kind {
|
||||
AddressType::Legacy => MoneroAddressType::Legacy,
|
||||
AddressType::Subaddress => MoneroAddressType::Subaddress,
|
||||
AddressType::Featured(features) => {
|
||||
debug_assert!(features <= 0b111);
|
||||
let subaddress = (features & 1) != 0;
|
||||
let integrated = (features & (1 << 1)) != 0;
|
||||
debug_assert!(!integrated);
|
||||
let guaranteed = (features & (1 << 2)) != 0;
|
||||
MoneroAddressType::Featured { subaddress, payment_id: None, guaranteed }
|
||||
}
|
||||
};
|
||||
MoneroAddress::new(Network::Mainnet, kind, address.spend.0, address.view.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExternalAddress> for Address {
|
||||
type Error = ();
|
||||
fn try_from(data: ExternalAddress) -> Result<Address, ()> {
|
||||
// Decode as an Address
|
||||
let mut data = data.as_ref();
|
||||
let address =
|
||||
<Address as borsh::BorshDeserialize>::deserialize_reader(&mut data).map_err(|_| ())?;
|
||||
if !data.is_empty() {
|
||||
Err(())?
|
||||
}
|
||||
Ok(address)
|
||||
}
|
||||
}
|
||||
impl From<Address> for ExternalAddress {
|
||||
fn from(address: Address) -> ExternalAddress {
|
||||
// This is 65 bytes which is less than MAX_ADDRESS_LEN
|
||||
ExternalAddress::new(borsh::to_vec(&address).unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Address {
|
||||
type Err = ();
|
||||
fn from_str(str: &str) -> Result<Address, ()> {
|
||||
let Ok(address) = MoneroAddress::from_str(Network::Mainnet, str) else { Err(())? };
|
||||
Address::try_from(address)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
MoneroAddress::from(*self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub type CoinsEvent = serai_abi::coins::Event;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiCoins<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiCoins<'a> {
|
||||
impl SeraiCoins<'_> {
|
||||
pub async fn mint_events(&self) -> Result<Vec<CoinsEvent>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
|
||||
@@ -9,7 +9,7 @@ const PALLET: &str = "Dex";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiDex<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiDex<'a> {
|
||||
impl SeraiDex<'_> {
|
||||
pub async fn events(&self) -> Result<Vec<DexEvent>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
|
||||
@@ -15,7 +15,7 @@ const PALLET: &str = "GenesisLiquidity";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiGenesisLiquidity<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiGenesisLiquidity<'a> {
|
||||
impl SeraiGenesisLiquidity<'_> {
|
||||
pub async fn events(&self) -> Result<Vec<GenesisLiquidityEvent>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
pub use serai_abi::in_instructions::primitives;
|
||||
use primitives::SignedBatch;
|
||||
|
||||
use crate::{
|
||||
primitives::{BlockHash, ExternalNetworkId},
|
||||
Transaction, SeraiError, Serai, TemporalSerai,
|
||||
};
|
||||
use crate::{primitives::ExternalNetworkId, Transaction, SeraiError, Serai, TemporalSerai};
|
||||
|
||||
pub type InInstructionsEvent = serai_abi::in_instructions::Event;
|
||||
|
||||
@@ -12,14 +9,7 @@ const PALLET: &str = "InInstructions";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiInInstructions<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiInInstructions<'a> {
|
||||
pub async fn latest_block_for_network(
|
||||
&self,
|
||||
network: ExternalNetworkId,
|
||||
) -> Result<Option<BlockHash>, SeraiError> {
|
||||
self.0.storage(PALLET, "LatestNetworkBlock", network).await
|
||||
}
|
||||
|
||||
impl SeraiInInstructions<'_> {
|
||||
pub async fn last_batch_for_network(
|
||||
&self,
|
||||
network: ExternalNetworkId,
|
||||
|
||||
@@ -8,7 +8,7 @@ const PALLET: &str = "LiquidityTokens";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiLiquidityTokens<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiLiquidityTokens<'a> {
|
||||
impl SeraiLiquidityTokens<'_> {
|
||||
pub async fn token_supply(&self, coin: ExternalCoin) -> Result<Amount, SeraiError> {
|
||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(Amount(0)))
|
||||
}
|
||||
|
||||
@@ -46,17 +46,17 @@ impl Block {
|
||||
}
|
||||
|
||||
/// Returns the time of this block, set by its producer, in milliseconds since the epoch.
|
||||
pub fn time(&self) -> Result<u64, SeraiError> {
|
||||
pub fn time(&self) -> Option<u64> {
|
||||
for transaction in &self.transactions {
|
||||
if let Call::Timestamp(timestamp::Call::set { now }) = transaction.call() {
|
||||
return Ok(*now);
|
||||
return Some(*now);
|
||||
}
|
||||
}
|
||||
Err(SeraiError::InvalidNode("no time was present in block".to_string()))
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SeraiError {
|
||||
#[error("failed to communicate with serai")]
|
||||
ConnectionError,
|
||||
@@ -81,7 +81,7 @@ pub struct TemporalSerai<'a> {
|
||||
block: [u8; 32],
|
||||
events: RwLock<Option<EventsInBlock>>,
|
||||
}
|
||||
impl<'a> Clone for TemporalSerai<'a> {
|
||||
impl Clone for TemporalSerai<'_> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { serai: self.serai, block: self.block, events: RwLock::new(None) }
|
||||
}
|
||||
@@ -314,7 +314,7 @@ impl Serai {
|
||||
/// Return the P2P Multiaddrs for the validators of the specified network.
|
||||
pub async fn p2p_validators(
|
||||
&self,
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
) -> Result<Vec<multiaddr::Multiaddr>, SeraiError> {
|
||||
self.call("p2p_validators", network).await
|
||||
}
|
||||
@@ -338,7 +338,7 @@ impl Serai {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TemporalSerai<'a> {
|
||||
impl TemporalSerai<'_> {
|
||||
async fn events<E>(
|
||||
&self,
|
||||
filter_map: impl Fn(&Event) -> Option<E>,
|
||||
@@ -408,27 +408,27 @@ impl<'a> TemporalSerai<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn coins(&'a self) -> SeraiCoins<'a> {
|
||||
pub fn coins(&self) -> SeraiCoins<'_> {
|
||||
SeraiCoins(self)
|
||||
}
|
||||
|
||||
pub fn dex(&'a self) -> SeraiDex<'a> {
|
||||
pub fn dex(&self) -> SeraiDex<'_> {
|
||||
SeraiDex(self)
|
||||
}
|
||||
|
||||
pub fn in_instructions(&'a self) -> SeraiInInstructions<'a> {
|
||||
pub fn in_instructions(&self) -> SeraiInInstructions<'_> {
|
||||
SeraiInInstructions(self)
|
||||
}
|
||||
|
||||
pub fn validator_sets(&'a self) -> SeraiValidatorSets<'a> {
|
||||
pub fn validator_sets(&self) -> SeraiValidatorSets<'_> {
|
||||
SeraiValidatorSets(self)
|
||||
}
|
||||
|
||||
pub fn genesis_liquidity(&'a self) -> SeraiGenesisLiquidity {
|
||||
pub fn genesis_liquidity(&self) -> SeraiGenesisLiquidity {
|
||||
SeraiGenesisLiquidity(self)
|
||||
}
|
||||
|
||||
pub fn liquidity_tokens(&'a self) -> SeraiLiquidityTokens {
|
||||
pub fn liquidity_tokens(&self) -> SeraiLiquidityTokens {
|
||||
SeraiLiquidityTokens(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use scale::Encode;
|
||||
|
||||
use sp_core::sr25519::{Public, Signature};
|
||||
use sp_runtime::BoundedVec;
|
||||
|
||||
use serai_abi::{primitives::Amount, validator_sets::primitives::ExternalValidatorSet};
|
||||
pub use serai_abi::validator_sets::primitives;
|
||||
use primitives::{Session, KeyPair};
|
||||
use primitives::{MAX_KEY_LEN, Session, KeyPair, SlashReport};
|
||||
|
||||
use crate::{
|
||||
primitives::{NetworkId, ExternalNetworkId, SeraiAddress},
|
||||
primitives::{NetworkId, ExternalNetworkId, EmbeddedEllipticCurve},
|
||||
Transaction, Serai, TemporalSerai, SeraiError,
|
||||
};
|
||||
|
||||
@@ -17,7 +18,7 @@ pub type ValidatorSetsEvent = serai_abi::validator_sets::Event;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiValidatorSets<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiValidatorSets<'a> {
|
||||
impl SeraiValidatorSets<'_> {
|
||||
pub async fn new_set_events(&self) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
@@ -107,6 +108,21 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
self.0.storage(PALLET, "CurrentSession", network).await
|
||||
}
|
||||
|
||||
pub async fn embedded_elliptic_curve_key(
|
||||
&self,
|
||||
validator: Public,
|
||||
embedded_elliptic_curve: EmbeddedEllipticCurve,
|
||||
) -> Result<Option<Vec<u8>>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
.storage(
|
||||
PALLET,
|
||||
"EmbeddedEllipticCurveKeys",
|
||||
(sp_core::hashing::blake2_128(&validator.encode()), validator, embedded_elliptic_curve),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn participants(
|
||||
&self,
|
||||
network: NetworkId,
|
||||
@@ -188,21 +204,30 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
|
||||
pub fn set_keys(
|
||||
network: ExternalNetworkId,
|
||||
removed_participants: sp_runtime::BoundedVec<
|
||||
SeraiAddress,
|
||||
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>,
|
||||
>,
|
||||
key_pair: KeyPair,
|
||||
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||
signature: Signature,
|
||||
) -> Transaction {
|
||||
Serai::unsigned(serai_abi::Call::ValidatorSets(serai_abi::validator_sets::Call::set_keys {
|
||||
network,
|
||||
removed_participants,
|
||||
key_pair,
|
||||
signature_participants,
|
||||
signature,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn set_embedded_elliptic_curve_key(
|
||||
embedded_elliptic_curve: EmbeddedEllipticCurve,
|
||||
key: BoundedVec<u8, sp_core::ConstU32<{ MAX_KEY_LEN }>>,
|
||||
) -> serai_abi::Call {
|
||||
serai_abi::Call::ValidatorSets(
|
||||
serai_abi::validator_sets::Call::set_embedded_elliptic_curve_key {
|
||||
embedded_elliptic_curve,
|
||||
key,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allocate(network: NetworkId, amount: Amount) -> serai_abi::Call {
|
||||
serai_abi::Call::ValidatorSets(serai_abi::validator_sets::Call::allocate { network, amount })
|
||||
}
|
||||
@@ -213,10 +238,7 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
|
||||
pub fn report_slashes(
|
||||
network: ExternalNetworkId,
|
||||
slashes: sp_runtime::BoundedVec<
|
||||
(SeraiAddress, u32),
|
||||
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>,
|
||||
>,
|
||||
slashes: SlashReport,
|
||||
signature: Signature,
|
||||
) -> Transaction {
|
||||
Serai::unsigned(serai_abi::Call::ValidatorSets(
|
||||
|
||||
@@ -8,12 +8,13 @@ use blake2::{
|
||||
use scale::Encode;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{Amount, BlockHash, ExternalBalance, ExternalCoin, SeraiAddress},
|
||||
primitives::{BlockHash, ExternalCoin, Amount, ExternalBalance, SeraiAddress},
|
||||
coins::CoinsEvent,
|
||||
validator_sets::primitives::Session,
|
||||
in_instructions::{
|
||||
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||
InInstructionsEvent,
|
||||
},
|
||||
coins::CoinsEvent,
|
||||
Serai,
|
||||
};
|
||||
|
||||
@@ -23,8 +24,6 @@ use common::in_instructions::provide_batch;
|
||||
serai_test!(
|
||||
publish_batch: (|serai: Serai| async move {
|
||||
let id = 0;
|
||||
let mut block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
|
||||
let mut address = SeraiAddress::new([0; 32]);
|
||||
OsRng.fill_bytes(&mut address.0);
|
||||
@@ -34,10 +33,13 @@ serai_test!(
|
||||
let amount = Amount(OsRng.next_u64().saturating_add(1));
|
||||
let balance = ExternalBalance { coin, amount };
|
||||
|
||||
let mut external_network_block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut external_network_block_hash.0);
|
||||
|
||||
let batch = Batch {
|
||||
network,
|
||||
id,
|
||||
block: block_hash,
|
||||
external_network_block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Transfer(address),
|
||||
balance,
|
||||
@@ -49,16 +51,16 @@ serai_test!(
|
||||
let serai = serai.as_of(block);
|
||||
{
|
||||
let serai = serai.in_instructions();
|
||||
let latest_finalized = serai.latest_block_for_network(network).await.unwrap();
|
||||
assert_eq!(latest_finalized, Some(block_hash));
|
||||
let batches = serai.batch_events().await.unwrap();
|
||||
assert_eq!(
|
||||
batches,
|
||||
vec![InInstructionsEvent::Batch {
|
||||
network,
|
||||
publishing_session: Session(0),
|
||||
id,
|
||||
block: block_hash,
|
||||
instructions_hash: Blake2b::<U32>::digest(batch.instructions.encode()).into(),
|
||||
external_network_block_hash,
|
||||
in_instructions_hash: Blake2b::<U32>::digest(batch.instructions.encode()).into(),
|
||||
in_instruction_results: bitvec::bitvec![u8, bitvec::order::Lsb0; 1; 1],
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,19 +7,22 @@ use blake2::{
|
||||
|
||||
use scale::Encode;
|
||||
|
||||
use serai_abi::coins::primitives::OutInstructionWithBalance;
|
||||
use sp_core::Pair;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{
|
||||
Amount, ExternalCoin, ExternalBalance, BlockHash, SeraiAddress, Data, ExternalAddress,
|
||||
BlockHash, ExternalCoin, Amount, ExternalBalance, SeraiAddress, ExternalAddress,
|
||||
insecure_pair_from_name,
|
||||
},
|
||||
coins::{
|
||||
primitives::{OutInstruction, OutInstructionWithBalance},
|
||||
CoinsEvent,
|
||||
},
|
||||
validator_sets::primitives::Session,
|
||||
in_instructions::{
|
||||
InInstructionsEvent,
|
||||
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||
},
|
||||
coins::{primitives::OutInstruction, CoinsEvent},
|
||||
Serai, SeraiCoins,
|
||||
};
|
||||
|
||||
@@ -44,7 +47,7 @@ serai_test!(
|
||||
let batch = Batch {
|
||||
network,
|
||||
id,
|
||||
block: block_hash,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Transfer(address),
|
||||
balance,
|
||||
@@ -54,17 +57,19 @@ serai_test!(
|
||||
let block = provide_batch(&serai, batch.clone()).await;
|
||||
|
||||
let instruction = {
|
||||
let serai = serai.as_of(block);
|
||||
let batches = serai.in_instructions().batch_events().await.unwrap();
|
||||
assert_eq!(
|
||||
batches,
|
||||
vec![InInstructionsEvent::Batch {
|
||||
network,
|
||||
id,
|
||||
block: block_hash,
|
||||
instructions_hash: Blake2b::<U32>::digest(batch.instructions.encode()).into(),
|
||||
}]
|
||||
);
|
||||
let serai = serai.as_of(block);
|
||||
let batches = serai.in_instructions().batch_events().await.unwrap();
|
||||
assert_eq!(
|
||||
batches,
|
||||
vec![InInstructionsEvent::Batch {
|
||||
network,
|
||||
publishing_session: Session(0),
|
||||
id,
|
||||
external_network_block_hash: block_hash,
|
||||
in_instructions_hash: Blake2b::<U32>::digest(batch.instructions.encode()).into(),
|
||||
in_instruction_results: bitvec::bitvec![u8, bitvec::order::Lsb0; 1; 1],
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serai.coins().mint_events().await.unwrap(),
|
||||
@@ -73,20 +78,16 @@ serai_test!(
|
||||
assert_eq!(serai.coins().coin_supply(coin.into()).await.unwrap(), amount);
|
||||
assert_eq!(serai.coins().coin_balance(coin.into(), address).await.unwrap(), amount);
|
||||
|
||||
// Now burn it
|
||||
let mut rand_bytes = vec![0; 32];
|
||||
OsRng.fill_bytes(&mut rand_bytes);
|
||||
let external_address = ExternalAddress::new(rand_bytes).unwrap();
|
||||
// Now burn it
|
||||
let mut rand_bytes = vec![0; 32];
|
||||
OsRng.fill_bytes(&mut rand_bytes);
|
||||
let external_address = ExternalAddress::new(rand_bytes).unwrap();
|
||||
|
||||
let mut rand_bytes = vec![0; 32];
|
||||
OsRng.fill_bytes(&mut rand_bytes);
|
||||
let data = Data::new(rand_bytes).unwrap();
|
||||
|
||||
OutInstructionWithBalance {
|
||||
balance,
|
||||
instruction: OutInstruction { address: external_address, data: Some(data) },
|
||||
}
|
||||
};
|
||||
OutInstructionWithBalance {
|
||||
balance,
|
||||
instruction: OutInstruction { address: external_address },
|
||||
}
|
||||
};
|
||||
|
||||
let block = publish_tx(
|
||||
&serai,
|
||||
|
||||
@@ -10,13 +10,13 @@ use schnorrkel::Schnorrkel;
|
||||
use sp_core::{sr25519::Signature, Pair as PairTrait};
|
||||
|
||||
use serai_abi::{
|
||||
genesis_liquidity::primitives::{oraclize_values_message, Values},
|
||||
in_instructions::primitives::{Batch, InInstruction, InInstructionWithBalance},
|
||||
primitives::{
|
||||
insecure_pair_from_name, Amount, ExternalBalance, BlockHash, ExternalCoin, ExternalNetworkId,
|
||||
NetworkId, SeraiAddress, EXTERNAL_COINS,
|
||||
EXTERNAL_COINS, BlockHash, ExternalNetworkId, NetworkId, ExternalCoin, Amount, ExternalBalance,
|
||||
SeraiAddress, insecure_pair_from_name,
|
||||
},
|
||||
validator_sets::primitives::{musig_context, Session, ValidatorSet},
|
||||
validator_sets::primitives::{Session, ValidatorSet, musig_context},
|
||||
genesis_liquidity::primitives::{Values, oraclize_values_message},
|
||||
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||
};
|
||||
|
||||
use serai_client::{Serai, SeraiGenesisLiquidity};
|
||||
@@ -53,7 +53,7 @@ pub async fn set_up_genesis(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// set up bloch hash
|
||||
// set up block hash
|
||||
let mut block = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block.0);
|
||||
|
||||
@@ -65,8 +65,12 @@ pub async fn set_up_genesis(
|
||||
})
|
||||
.or_insert(0);
|
||||
|
||||
let batch =
|
||||
Batch { network: coin.network(), id: batch_ids[&coin.network()], block, instructions };
|
||||
let batch = Batch {
|
||||
network: coin.network(),
|
||||
external_network_block_hash: block,
|
||||
id: batch_ids[&coin.network()],
|
||||
instructions,
|
||||
};
|
||||
provide_batch(serai, batch).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use scale::Encode;
|
||||
use sp_core::Pair;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{insecure_pair_from_name, BlockHash, ExternalBalance, SeraiAddress},
|
||||
primitives::{BlockHash, ExternalBalance, SeraiAddress, insecure_pair_from_name},
|
||||
validator_sets::primitives::{ExternalValidatorSet, KeyPair},
|
||||
in_instructions::{
|
||||
primitives::{Batch, SignedBatch, batch_message, InInstruction, InInstructionWithBalance},
|
||||
@@ -45,17 +45,29 @@ pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
|
||||
)
|
||||
.await;
|
||||
|
||||
let batches = serai.as_of(block).in_instructions().batch_events().await.unwrap();
|
||||
// TODO: impl From<Batch> for BatchEvent?
|
||||
assert_eq!(
|
||||
batches,
|
||||
vec![InInstructionsEvent::Batch {
|
||||
network: batch.network,
|
||||
id: batch.id,
|
||||
block: batch.block,
|
||||
instructions_hash: Blake2b::<U32>::digest(batch.instructions.encode()).into(),
|
||||
}],
|
||||
);
|
||||
{
|
||||
let mut batches = serai.as_of(block).in_instructions().batch_events().await.unwrap();
|
||||
assert_eq!(batches.len(), 1);
|
||||
let InInstructionsEvent::Batch {
|
||||
network,
|
||||
publishing_session,
|
||||
id,
|
||||
external_network_block_hash,
|
||||
in_instructions_hash,
|
||||
in_instruction_results: _,
|
||||
} = batches.swap_remove(0)
|
||||
else {
|
||||
panic!("Batch event wasn't Batch event")
|
||||
};
|
||||
assert_eq!(network, batch.network);
|
||||
assert_eq!(publishing_session, session);
|
||||
assert_eq!(id, batch.id);
|
||||
assert_eq!(external_network_block_hash, batch.external_network_block_hash);
|
||||
assert_eq!(
|
||||
in_instructions_hash,
|
||||
<[u8; 32]>::from(Blake2b::<U32>::digest(batch.instructions.encode()))
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Check the tokens events
|
||||
|
||||
@@ -75,7 +87,7 @@ pub async fn mint_coin(
|
||||
let batch = Batch {
|
||||
network: balance.coin.network(),
|
||||
id: batch_id,
|
||||
block: block_hash,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Transfer(address),
|
||||
balance,
|
||||
|
||||
@@ -5,6 +5,8 @@ use zeroize::Zeroizing;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use sp_core::{
|
||||
ConstU32,
|
||||
bounded_vec::BoundedVec,
|
||||
sr25519::{Pair, Signature},
|
||||
Pair as PairTrait,
|
||||
};
|
||||
@@ -14,11 +16,12 @@ use frost::dkg::musig::musig;
|
||||
use schnorrkel::Schnorrkel;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{EmbeddedEllipticCurve, Amount},
|
||||
validator_sets::{
|
||||
primitives::{ExternalValidatorSet, KeyPair, musig_context, set_keys_message},
|
||||
primitives::{MAX_KEY_LEN, ExternalValidatorSet, KeyPair, musig_context, set_keys_message},
|
||||
ValidatorSetsEvent,
|
||||
},
|
||||
Amount, Serai, SeraiValidatorSets,
|
||||
SeraiValidatorSets, Serai,
|
||||
};
|
||||
|
||||
use crate::common::tx::publish_tx;
|
||||
@@ -59,7 +62,7 @@ pub async fn set_keys(
|
||||
let sig = frost::tests::sign_without_caching(
|
||||
&mut OsRng,
|
||||
frost::tests::algorithm_machines(&mut OsRng, &Schnorrkel::new(b"substrate"), &musig_keys),
|
||||
&set_keys_message(&set, &[], &key_pair),
|
||||
&set_keys_message(&set, &key_pair),
|
||||
);
|
||||
|
||||
// Set the key pair
|
||||
@@ -67,8 +70,8 @@ pub async fn set_keys(
|
||||
serai,
|
||||
&SeraiValidatorSets::set_keys(
|
||||
set.network,
|
||||
vec![].try_into().unwrap(),
|
||||
key_pair.clone(),
|
||||
bitvec::bitvec!(u8, bitvec::prelude::Lsb0; 1; musig_keys.len()),
|
||||
Signature(sig.to_bytes()),
|
||||
),
|
||||
)
|
||||
@@ -83,6 +86,24 @@ pub async fn set_keys(
|
||||
block
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn set_embedded_elliptic_curve_key(
|
||||
serai: &Serai,
|
||||
pair: &Pair,
|
||||
embedded_elliptic_curve: EmbeddedEllipticCurve,
|
||||
key: BoundedVec<u8, ConstU32<{ MAX_KEY_LEN }>>,
|
||||
nonce: u32,
|
||||
) -> [u8; 32] {
|
||||
// get the call
|
||||
let tx = serai.sign(
|
||||
pair,
|
||||
SeraiValidatorSets::set_embedded_elliptic_curve_key(embedded_elliptic_curve, key),
|
||||
nonce,
|
||||
0,
|
||||
);
|
||||
publish_tx(serai, &tx).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn allocate_stake(
|
||||
serai: &Serai,
|
||||
|
||||
@@ -6,8 +6,8 @@ use serai_abi::in_instructions::primitives::DexCall;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{
|
||||
Amount, Coin, Balance, BlockHash, insecure_pair_from_name, ExternalAddress, SeraiAddress,
|
||||
ExternalCoin, ExternalBalance,
|
||||
BlockHash, ExternalCoin, Coin, Amount, ExternalBalance, Balance, SeraiAddress, ExternalAddress,
|
||||
insecure_pair_from_name,
|
||||
},
|
||||
in_instructions::primitives::{
|
||||
InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR, OutAddress,
|
||||
@@ -247,7 +247,7 @@ serai_test!(
|
||||
let batch = Batch {
|
||||
network: coin.network(),
|
||||
id: batch_id,
|
||||
block: block_hash,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(pair.public().into())),
|
||||
balance: ExternalBalance { coin, amount: Amount(20_000_000_000_000) },
|
||||
@@ -329,7 +329,7 @@ serai_test!(
|
||||
let batch = Batch {
|
||||
network: coin1.network(),
|
||||
id: coin1_batch_id,
|
||||
block: block_hash,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address)),
|
||||
balance: ExternalBalance { coin: coin1, amount: Amount(200_000_000_000_000) },
|
||||
@@ -369,7 +369,7 @@ serai_test!(
|
||||
let batch = Batch {
|
||||
network: coin2.network(),
|
||||
id: coin2_batch_id,
|
||||
block: block_hash,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||
balance: ExternalBalance { coin: coin2, amount: Amount(200_000_000_000) },
|
||||
@@ -407,7 +407,7 @@ serai_test!(
|
||||
let batch = Batch {
|
||||
network: coin1.network(),
|
||||
id: coin1_batch_id,
|
||||
block: block_hash,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||
balance: ExternalBalance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||
|
||||
@@ -44,7 +44,7 @@ async fn dht() {
|
||||
assert!(!Serai::new(serai_rpc.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.p2p_validators(ExternalNetworkId::Bitcoin.into())
|
||||
.p2p_validators(ExternalNetworkId::Bitcoin)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
|
||||
@@ -4,13 +4,13 @@ use rand_core::{RngCore, OsRng};
|
||||
use serai_client::TemporalSerai;
|
||||
|
||||
use serai_abi::{
|
||||
emissions::primitives::{INITIAL_REWARD_PER_BLOCK, SECURE_BY},
|
||||
in_instructions::primitives::Batch,
|
||||
primitives::{
|
||||
BlockHash, ExternalBalance, ExternalCoin, ExternalNetworkId, EXTERNAL_NETWORKS,
|
||||
FAST_EPOCH_DURATION, FAST_EPOCH_INITIAL_PERIOD, NETWORKS, TARGET_BLOCK_TIME, Amount, NetworkId,
|
||||
EXTERNAL_NETWORKS, NETWORKS, TARGET_BLOCK_TIME, FAST_EPOCH_DURATION, FAST_EPOCH_INITIAL_PERIOD,
|
||||
BlockHash, ExternalNetworkId, NetworkId, ExternalCoin, Amount, ExternalBalance,
|
||||
},
|
||||
validator_sets::primitives::Session,
|
||||
emissions::primitives::{INITIAL_REWARD_PER_BLOCK, SECURE_BY},
|
||||
in_instructions::primitives::Batch,
|
||||
};
|
||||
|
||||
use serai_client::Serai;
|
||||
@@ -38,7 +38,16 @@ async fn send_batches(serai: &Serai, ids: &mut HashMap<ExternalNetworkId, u32>)
|
||||
let mut block = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block.0);
|
||||
|
||||
provide_batch(serai, Batch { network, id: ids[&network], block, instructions: vec![] }).await;
|
||||
provide_batch(
|
||||
serai,
|
||||
Batch {
|
||||
network,
|
||||
id: ids[&network],
|
||||
external_network_block_hash: block,
|
||||
instructions: vec![],
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ use sp_core::{
|
||||
|
||||
use serai_client::{
|
||||
primitives::{
|
||||
NETWORKS, NetworkId, BlockHash, insecure_pair_from_name, FAST_EPOCH_DURATION,
|
||||
TARGET_BLOCK_TIME, ExternalNetworkId, Amount,
|
||||
FAST_EPOCH_DURATION, TARGET_BLOCK_TIME, NETWORKS, BlockHash, ExternalNetworkId, NetworkId,
|
||||
EmbeddedEllipticCurve, Amount, insecure_pair_from_name,
|
||||
},
|
||||
validator_sets::{
|
||||
primitives::{Session, ValidatorSet, ExternalValidatorSet, KeyPair},
|
||||
primitives::{Session, ExternalValidatorSet, ValidatorSet, KeyPair},
|
||||
ValidatorSetsEvent,
|
||||
},
|
||||
in_instructions::{
|
||||
@@ -24,7 +24,7 @@ use serai_client::{
|
||||
mod common;
|
||||
use common::{
|
||||
tx::publish_tx,
|
||||
validator_sets::{allocate_stake, deallocate_stake, set_keys},
|
||||
validator_sets::{set_embedded_elliptic_curve_key, allocate_stake, deallocate_stake, set_keys},
|
||||
};
|
||||
|
||||
fn get_random_key_pair() -> KeyPair {
|
||||
@@ -224,12 +224,39 @@ async fn validator_set_rotation() {
|
||||
|
||||
// add 1 participant
|
||||
let last_participant = accounts[4].clone();
|
||||
|
||||
// If this is the first iteration, set embedded elliptic curve keys
|
||||
if i == 0 {
|
||||
for (i, embedded_elliptic_curve) in
|
||||
[EmbeddedEllipticCurve::Embedwards25519, EmbeddedEllipticCurve::Secq256k1]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
set_embedded_elliptic_curve_key(
|
||||
&serai,
|
||||
&last_participant,
|
||||
embedded_elliptic_curve,
|
||||
vec![
|
||||
0;
|
||||
match embedded_elliptic_curve {
|
||||
EmbeddedEllipticCurve::Embedwards25519 => 32,
|
||||
EmbeddedEllipticCurve::Secq256k1 => 33,
|
||||
}
|
||||
]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
i.try_into().unwrap(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
let hash = allocate_stake(
|
||||
&serai,
|
||||
network,
|
||||
key_shares[&network],
|
||||
&last_participant,
|
||||
i.try_into().unwrap(),
|
||||
(2 + i).try_into().unwrap(),
|
||||
)
|
||||
.await;
|
||||
participants.push(last_participant.public());
|
||||
@@ -289,7 +316,7 @@ async fn validator_set_rotation() {
|
||||
let batch = Batch {
|
||||
network: network.try_into().unwrap(),
|
||||
id: 0,
|
||||
block: block_hash,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![],
|
||||
};
|
||||
publish_tx(
|
||||
|
||||
Reference in New Issue
Block a user