Merge branch 'develop' into next

This is an initial resolution of conflicts which does not work.
This commit is contained in:
Luke Parker
2025-01-30 00:56:29 -05:00
128 changed files with 1835 additions and 44261 deletions

View File

@@ -10,7 +10,7 @@ use ciphersuite::{
};
use dkg::evrf::*;
use serai_client::primitives::{NetworkId, insecure_arbitrary_key_from_name};
use serai_client::primitives::{ExternalNetworkId, insecure_arbitrary_key_from_name};
use messages::{ProcessorMessage, CoordinatorMessage};
use serai_message_queue::{Service, Metadata, client::MessageQueue};
@@ -36,7 +36,7 @@ pub struct EvrfPublicKeys {
pub fn processor_instance(
name: &str,
network: NetworkId,
network: ExternalNetworkId,
port: u32,
message_queue_key: <Ristretto as Ciphersuite>::F,
) -> (Vec<TestBodySpecification>, EvrfPublicKeys) {
@@ -65,10 +65,9 @@ pub fn processor_instance(
};
let network_str = match network {
NetworkId::Serai => panic!("starting a processor for Serai"),
NetworkId::Bitcoin => "bitcoin",
NetworkId::Ethereum => "ethereum",
NetworkId::Monero => "monero",
ExternalNetworkId::Bitcoin => "bitcoin",
ExternalNetworkId::Ethereum => "ethereum",
ExternalNetworkId::Monero => "monero",
};
let image = format!("{network_str}-processor");
serai_docker_tests::build(image.clone());
@@ -90,7 +89,7 @@ pub fn processor_instance(
.into(),
)];
if network == NetworkId::Ethereum {
if network == ExternalNetworkId::Ethereum {
serai_docker_tests::build("ethereum-relayer".to_string());
res.push(
TestBodySpecification::with_image(
@@ -119,7 +118,7 @@ pub struct ProcessorKeys {
pub type Handles = (String, String, String, String);
pub fn processor_stack(
name: &str,
network: NetworkId,
network: ExternalNetworkId,
network_hostname_override: Option<String>,
) -> (Handles, ProcessorKeys, Vec<TestBodySpecification>) {
let (network_composition, network_rpc_port) = network_instance(network);
@@ -145,10 +144,9 @@ pub fn processor_stack(
for (name, composition) in [
Some((
match network {
NetworkId::Serai => unreachable!(),
NetworkId::Bitcoin => "bitcoin",
NetworkId::Ethereum => "ethereum",
NetworkId::Monero => "monero",
ExternalNetworkId::Bitcoin => "bitcoin",
ExternalNetworkId::Ethereum => "ethereum",
ExternalNetworkId::Monero => "monero",
},
network_composition,
)),
@@ -200,7 +198,7 @@ pub fn processor_stack(
}
pub struct Coordinator {
network: NetworkId,
network: ExternalNetworkId,
network_handle: String,
#[allow(unused)]
@@ -218,7 +216,7 @@ pub struct Coordinator {
impl Coordinator {
pub fn new(
network: NetworkId,
network: ExternalNetworkId,
ops: &DockerOperations,
handles: Handles,
keys: ProcessorKeys,
@@ -256,7 +254,7 @@ impl Coordinator {
let mut iters = 0;
while iters < 60 {
match network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::rpc::Rpc;
// Bitcoin's Rpc::new will test the connection
@@ -264,7 +262,7 @@ impl Coordinator {
break;
}
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use std::sync::Arc;
use ethereum_serai::{
alloy::{
@@ -293,7 +291,7 @@ impl Coordinator {
provider
.raw_request::<_, ()>(
"anvil_setBalance".into(),
[signer.to_string(), (tx.gas_limit * tx.gas_price).to_string()],
[signer.to_string(), (u128::from(tx.gas_limit) * tx.gas_price).to_string()],
)
.await
.unwrap();
@@ -313,7 +311,7 @@ impl Coordinator {
break;
}
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
@@ -327,7 +325,6 @@ impl Coordinator {
break;
}
}
NetworkId::Serai => panic!("processor is booting with external network of Serai"),
}
println!("external network RPC has yet to boot, waiting 1 sec, attempt {iters}");
@@ -385,7 +382,7 @@ impl Coordinator {
pub async fn add_block(&self, ops: &DockerOperations) -> ([u8; 32], Vec<u8>) {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{
bitcoin::{consensus::Encodable, network::Network, Script, Address},
rpc::Rpc,
@@ -408,7 +405,7 @@ impl Coordinator {
block.consensus_encode(&mut block_buf).unwrap();
(hash, block_buf)
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_types::{BlockTransactionsKind, BlockNumberOrTag},
@@ -445,7 +442,7 @@ impl Coordinator {
.into_bytes();
(hash.into(), state)
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{rpc::Rpc, address::Network, ViewPair};
@@ -463,14 +460,13 @@ impl Coordinator {
let hash = rpc.get_block_hash(rpc.get_height().await.unwrap() - 1).await.unwrap();
(hash, rpc.get_block(hash).await.unwrap().serialize())
}
NetworkId::Serai => panic!("processor tests adding block to Serai"),
}
}
pub async fn sync(&self, ops: &DockerOperations, others: &[Coordinator]) {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{bitcoin::consensus::Encodable, rpc::Rpc};
let rpc = Rpc::new(rpc_url).await.expect("couldn't connect to the Bitcoin RPC");
@@ -500,7 +496,7 @@ impl Coordinator {
}
}
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_types::{BlockTransactionsKind, BlockNumberOrTag},
@@ -551,7 +547,7 @@ impl Coordinator {
//assert_eq!(expected_number, new_number);
}
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
@@ -582,14 +578,13 @@ impl Coordinator {
}
}
}
NetworkId::Serai => panic!("processors tests syncing Serai nodes"),
}
}
pub async fn publish_transaction(&self, ops: &DockerOperations, tx: &[u8]) {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{
bitcoin::{consensus::Decodable, Transaction},
rpc::Rpc,
@@ -599,7 +594,7 @@ impl Coordinator {
Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC");
rpc.send_raw_transaction(&Transaction::consensus_decode(&mut &*tx).unwrap()).await.unwrap();
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_client::ClientBuilder,
@@ -612,7 +607,7 @@ impl Coordinator {
);
let _ = provider.send_raw_transaction(tx).await.unwrap();
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{transaction::Transaction, rpc::Rpc};
@@ -621,15 +616,15 @@ impl Coordinator {
.expect("couldn't connect to the coordinator's Monero RPC");
rpc.publish_transaction(&Transaction::read(&mut &*tx).unwrap()).await.unwrap();
}
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
}
}
pub async fn publish_eventuality_completion(&self, ops: &DockerOperations, tx: &[u8]) {
match self.network {
NetworkId::Bitcoin | NetworkId::Monero => self.publish_transaction(ops, tx).await,
NetworkId::Ethereum => (),
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
ExternalNetworkId::Bitcoin | ExternalNetworkId::Monero => {
self.publish_transaction(ops, tx).await
}
ExternalNetworkId::Ethereum => (),
}
}
@@ -640,7 +635,7 @@ impl Coordinator {
) -> Option<Vec<u8>> {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{bitcoin::consensus::Encodable, rpc::Rpc};
let rpc =
@@ -662,7 +657,7 @@ impl Coordinator {
None
}
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
/*
let provider = RootProvider::<_, Ethereum>::new(
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
@@ -712,7 +707,7 @@ impl Coordinator {
None
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
@@ -727,7 +722,6 @@ impl Coordinator {
None
}
}
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
}
}
}

View File

@@ -4,9 +4,9 @@ use rand_core::{RngCore, OsRng};
use scale::Encode;
use serai_client::{
primitives::{Amount, NetworkId, Coin, Balance, ExternalAddress},
validator_sets::primitives::ExternalKey,
in_instructions::primitives::{InInstruction, RefundableInInstruction, Shorthand},
primitives::{Amount, ExternalAddress, ExternalBalance, ExternalCoin, ExternalNetworkId},
validator_sets::primitives::ExternalKey,
};
use dockertest::{PullPolicy, Image, StartPolicy, TestBodySpecification, DockerOperations};
@@ -52,37 +52,32 @@ pub fn monero_instance() -> (TestBodySpecification, u32) {
(composition, XMR_PORT)
}
pub fn network_instance(network: NetworkId) -> (TestBodySpecification, u32) {
pub fn network_instance(network: ExternalNetworkId) -> (TestBodySpecification, u32) {
match network {
NetworkId::Bitcoin => bitcoin_instance(),
NetworkId::Ethereum => ethereum_instance(),
NetworkId::Monero => monero_instance(),
NetworkId::Serai => {
panic!("Serai is not a valid network to spawn an instance of for a processor")
}
ExternalNetworkId::Bitcoin => bitcoin_instance(),
ExternalNetworkId::Ethereum => ethereum_instance(),
ExternalNetworkId::Monero => monero_instance(),
}
}
pub fn network_rpc(network: NetworkId, ops: &DockerOperations, handle: &str) -> String {
pub fn network_rpc(network: ExternalNetworkId, ops: &DockerOperations, handle: &str) -> String {
let (ip, port) = ops
.handle(handle)
.host_port(match network {
NetworkId::Bitcoin => BTC_PORT,
NetworkId::Ethereum => ETH_PORT,
NetworkId::Monero => XMR_PORT,
NetworkId::Serai => panic!("getting port for external network yet it was Serai"),
ExternalNetworkId::Bitcoin => BTC_PORT,
ExternalNetworkId::Ethereum => ETH_PORT,
ExternalNetworkId::Monero => XMR_PORT,
})
.unwrap();
format!("http://{RPC_USER}:{RPC_PASS}@{ip}:{port}")
}
pub fn confirmations(network: NetworkId) -> usize {
pub fn confirmations(network: ExternalNetworkId) -> usize {
use processor::networks::*;
match network {
NetworkId::Bitcoin => Bitcoin::CONFIRMATIONS,
NetworkId::Ethereum => Ethereum::<serai_db::MemDb>::CONFIRMATIONS,
NetworkId::Monero => Monero::CONFIRMATIONS,
NetworkId::Serai => panic!("getting confirmations required for Serai"),
ExternalNetworkId::Bitcoin => Bitcoin::CONFIRMATIONS,
ExternalNetworkId::Ethereum => Ethereum::<serai_db::MemDb>::CONFIRMATIONS,
ExternalNetworkId::Monero => Monero::CONFIRMATIONS,
}
}
@@ -108,11 +103,11 @@ pub enum Wallet {
// TODO: Merge these functions with the processor's tests, which offers very similar functionality
impl Wallet {
pub async fn new(network: NetworkId, ops: &DockerOperations, handle: String) -> Wallet {
pub async fn new(network: ExternalNetworkId, ops: &DockerOperations, handle: String) -> Wallet {
let rpc_url = network_rpc(network, ops, &handle);
match network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{
bitcoin::{
secp256k1::{SECP256K1, SecretKey},
@@ -153,7 +148,7 @@ impl Wallet {
Wallet::Bitcoin { private_key, public_key, input_tx: funds }
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ciphersuite::{group::ff::Field, Secp256k1};
use ethereum_serai::alloy::{
primitives::{U256, Address},
@@ -185,7 +180,7 @@ impl Wallet {
Wallet::Ethereum { rpc_url: rpc_url.clone(), key, nonce: 0 }
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{rpc::Rpc, address::Network, ViewPair};
@@ -210,7 +205,6 @@ impl Wallet {
last_tx: (height, block.miner_transaction.hash()),
}
}
NetworkId::Serai => panic!("creating a wallet for for Serai"),
}
}
@@ -219,7 +213,7 @@ impl Wallet {
ops: &DockerOperations,
to: &ExternalKey,
instruction: Option<InInstruction>,
) -> (Vec<u8>, Balance) {
) -> (Vec<u8>, ExternalBalance) {
match self {
Wallet::Bitcoin { private_key, public_key, ref mut input_tx } => {
use bitcoin_serai::bitcoin::{
@@ -298,14 +292,14 @@ impl Wallet {
let mut buf = vec![];
tx.consensus_encode(&mut buf).unwrap();
*input_tx = tx;
(buf, Balance { coin: Coin::Bitcoin, amount: Amount(AMOUNT) })
(buf, ExternalBalance { coin: ExternalCoin::Bitcoin, amount: Amount(AMOUNT) })
}
Wallet::Ethereum { rpc_url, key, ref mut nonce } => {
use std::sync::Arc;
use ethereum_serai::{
alloy::{
primitives::{U256, Signature, TxKind},
primitives::{U256, Parity, Signature, TxKind},
sol_types::SolCall,
simple_request_transport::SimpleRequest,
consensus::{TxLegacy, SignableTransaction},
@@ -349,13 +343,13 @@ impl Wallet {
provider
.raw_request::<_, ()>(
"anvil_setBalance".into(),
[signer.to_string(), (tx.gas_limit * tx.gas_price).to_string()],
[signer.to_string(), (u128::from(tx.gas_limit) * tx.gas_price).to_string()],
)
.await
.unwrap();
let mut bytes = vec![];
tx.encode_with_signature_fields(&Signature::from(sig), &mut bytes);
tx.encode_with_signature_fields(&sig, &mut bytes);
let _ = provider.send_raw_transaction(&bytes).await.unwrap();
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
@@ -370,7 +364,7 @@ impl Wallet {
chain_id: None,
nonce: *nonce,
gas_price: 1_000_000_000u128,
gas_limit: 200_000u128,
gas_limit: 200_000,
to: TxKind::Call(router_addr.into()),
// 1 ETH
value: one_eth,
@@ -395,12 +389,16 @@ impl Wallet {
.unwrap();
let mut bytes = vec![];
tx.encode_with_signature_fields(&Signature::from(sig), &mut bytes);
let parity = Parity::NonEip155(Parity::from(sig.1).y_parity());
tx.encode_with_signature_fields(&Signature::from(sig).with_parity(parity), &mut bytes);
// We drop the bottom 10 decimals
(
bytes,
Balance { coin: Coin::Ether, amount: Amount(u64::try_from(eight_decimals).unwrap()) },
ExternalBalance {
coin: ExternalCoin::Ether,
amount: Amount(u64::try_from(eight_decimals).unwrap()),
},
)
}
@@ -417,7 +415,7 @@ impl Wallet {
};
use processor::{additional_key, networks::Monero};
let rpc_url = network_rpc(NetworkId::Monero, ops, handle);
let rpc_url = network_rpc(ExternalNetworkId::Monero, ops, handle);
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
// Prepare inputs
@@ -485,7 +483,7 @@ impl Wallet {
last_tx.0 = current_height;
last_tx.1 = tx.hash();
(tx.serialize(), Balance { coin: Coin::Monero, amount: Amount(AMOUNT) })
(tx.serialize(), ExternalBalance { coin: ExternalCoin::Monero, amount: Amount(AMOUNT) })
}
}
}

View File

@@ -10,11 +10,12 @@ use dkg::{Participant, tests::clone_without};
use messages::{coordinator::*, SubstrateContext};
use serai_client::{
primitives::{
BlockHash, Amount, Balance, crypto::RuntimePublic, PublicKey, SeraiAddress, NetworkId,
},
in_instructions::primitives::{
InInstruction, InInstructionWithBalance, Batch, SignedBatch, batch_message,
batch_message, Batch, InInstruction, InInstructionWithBalance, SignedBatch,
},
primitives::{
crypto::RuntimePublic, Amount, BlockHash, ExternalBalance, ExternalNetworkId, PublicKey,
SeraiAddress, EXTERNAL_NETWORKS,
},
validator_sets::primitives::Session,
};
@@ -191,7 +192,7 @@ pub(crate) async fn substrate_block(
#[test]
fn batch_test() {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
for network in EXTERNAL_NETWORKS {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {
@@ -257,15 +258,14 @@ fn batch_test() {
instructions: if let Some(instruction) = &instruction {
vec![InInstructionWithBalance {
instruction: instruction.clone(),
balance: Balance {
balance: ExternalBalance {
coin: balance_sent.coin,
amount: Amount(
balance_sent.amount.0 -
(2 * match network {
NetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
NetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
NetworkId::Monero => Monero::COST_TO_AGGREGATE,
NetworkId::Serai => panic!("minted for Serai?"),
ExternalNetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
ExternalNetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
ExternalNetworkId::Monero => Monero::COST_TO_AGGREGATE,
}),
),
},
@@ -324,7 +324,9 @@ fn batch_test() {
},
)
.await;
if instruction.is_some() || (instruction.is_none() && (network == NetworkId::Monero)) {
if instruction.is_some() ||
(instruction.is_none() && (network == ExternalNetworkId::Monero))
{
assert!(plans.is_empty());
} else {
// If no instruction was used, and the processor csn presume the origin, it'd have
@@ -337,7 +339,7 @@ fn batch_test() {
// With the latter InInstruction not existing, we should've triggered a refund if the origin
// was detectable
// Check this is trying to sign a Plan
if network != NetworkId::Monero {
if network != ExternalNetworkId::Monero {
let mut refund_id = None;
for coordinator in &mut coordinators {
match coordinator.recv_message().await {

View File

@@ -3,8 +3,8 @@ use std::time::SystemTime;
use dkg::Participant;
use serai_client::{
primitives::{NetworkId, BlockHash, PublicKey},
validator_sets::primitives::{Session, KeyPair},
primitives::{BlockHash, PublicKey, EXTERNAL_NETWORKS},
validator_sets::primitives::{KeyPair, Session},
};
use messages::{SubstrateContext, CoordinatorMessage, ProcessorMessage};
@@ -122,7 +122,7 @@ pub(crate) async fn key_gen(coordinators: &mut [Coordinator]) -> KeyPair {
#[test]
fn key_gen_test() {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
for network in EXTERNAL_NETWORKS {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {

View File

@@ -15,7 +15,7 @@ mod send;
pub(crate) const COORDINATORS: usize = 4;
pub(crate) const THRESHOLD: usize = ((COORDINATORS * 2) / 3) + 1;
fn new_test(network: NetworkId) -> (Vec<(Handles, ProcessorKeys)>, DockerTest) {
fn new_test(network: ExternalNetworkId) -> (Vec<(Handles, ProcessorKeys)>, DockerTest) {
let mut coordinators = vec![];
let mut test = DockerTest::new().with_network(dockertest::Network::Isolated);
let mut eth_handle = None;
@@ -26,7 +26,7 @@ fn new_test(network: NetworkId) -> (Vec<(Handles, ProcessorKeys)>, DockerTest) {
processor_stack(&i.to_string(), network, eth_handle.clone());
// TODO: Remove this once https://github.com/foundry-rs/foundry/issues/7955
// This has all processors share an Ethereum node until we can sync controlled nodes
if network == NetworkId::Ethereum {
if network == ExternalNetworkId::Ethereum {
eth_handle = eth_handle.or_else(|| Some(handles.0.clone()));
}
coordinators.push((handles, keys));

View File

@@ -10,9 +10,9 @@ use dkg::{Participant, tests::clone_without};
use messages::{sign::SignId, SubstrateContext};
use serai_client::{
primitives::{BlockHash, NetworkId, Amount, Balance, SeraiAddress},
coins::primitives::{OutInstruction, OutInstructionWithBalance},
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
in_instructions::primitives::{Batch, InInstruction, InInstructionWithBalance},
primitives::{Amount, BlockHash, ExternalBalance, SeraiAddress, EXTERNAL_NETWORKS},
validator_sets::primitives::Session,
};
@@ -149,7 +149,7 @@ pub(crate) async fn sign_tx(
#[test]
fn send_test() {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
for network in EXTERNAL_NETWORKS {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {
@@ -204,10 +204,9 @@ fn send_test() {
let amount_minted = Amount(
balance_sent.amount.0 -
(2 * match network {
NetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
NetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
NetworkId::Monero => Monero::COST_TO_AGGREGATE,
NetworkId::Serai => panic!("minted for Serai?"),
ExternalNetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
ExternalNetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
ExternalNetworkId::Monero => Monero::COST_TO_AGGREGATE,
}),
);
@@ -217,7 +216,7 @@ fn send_test() {
block: BlockHash(block_with_tx.unwrap()),
instructions: vec![InInstructionWithBalance {
instruction,
balance: Balance { coin: balance_sent.coin, amount: amount_minted },
balance: ExternalBalance { coin: balance_sent.coin, amount: amount_minted },
}],
};
@@ -247,7 +246,7 @@ fn send_test() {
block: substrate_block_num,
burns: vec![OutInstructionWithBalance {
instruction: OutInstruction { address: wallet.address() },
balance: Balance { coin: balance_sent.coin, amount: amount_minted },
balance: ExternalBalance { coin: balance_sent.coin, amount: amount_minted },
}],
batches: vec![batch.batch.id],
},