use std::time::Duration;
use num_bigint::BigInt;
use crate::Client;
use crate::postage::PostageBatch;
use crate::swarm::{BatchId, Error, Network, Size};
#[derive(Clone, Debug, Default)]
pub struct StorageOptions {
pub network: Network,
pub label: Option<String>,
pub immutable: Option<bool>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StorageCost {
pub depth: u8,
pub amount_per_chunk: BigInt,
pub total_cost: BigInt,
pub blocks: u64,
}
pub async fn get_storage_cost(
client: &Client,
size: Size,
duration: Duration,
network: Network,
) -> Result<StorageCost, Error> {
let chain = client.debug().chain_state().await?;
let blocks = network.seconds_to_blocks(duration.as_secs());
let depth_i32 = crate::postage::get_depth_for_size(size.to_bytes());
let depth: u8 = depth_i32
.try_into()
.map_err(|_| Error::argument(format!("computed depth {depth_i32} out of u8 range")))?;
let amount_per_chunk = &chain.current_price * BigInt::from(blocks);
let total_cost = crate::postage::get_stamp_cost(depth_i32, &amount_per_chunk);
Ok(StorageCost {
depth,
amount_per_chunk,
total_cost,
blocks,
})
}
pub async fn buy_storage(
client: &Client,
size: Size,
duration: Duration,
opts: &StorageOptions,
) -> Result<BatchId, Error> {
let cost = get_storage_cost(client, size, duration, opts.network).await?;
client
.postage()
.create_postage_batch(&cost.amount_per_chunk, cost.depth, opts.label.as_deref())
.await
}
pub async fn extend_storage_duration(
client: &Client,
batch_id: &BatchId,
duration: Duration,
network: Network,
) -> Result<(), Error> {
let chain = client.debug().chain_state().await?;
let blocks = network.seconds_to_blocks(duration.as_secs());
let amount = &chain.current_price * BigInt::from(blocks);
client.postage().top_up_batch(batch_id, &amount).await
}
pub async fn extend_storage_size(
client: &Client,
batch_id: &BatchId,
new_size: Size,
) -> Result<(), Error> {
let batch: PostageBatch = client.postage().get_postage_batch(batch_id).await?;
let target_depth_i32 = crate::postage::get_depth_for_size(new_size.to_bytes());
let target: u8 = target_depth_i32.try_into().map_err(|_| {
Error::argument(format!("computed depth {target_depth_i32} out of u8 range"))
})?;
if target <= batch.depth {
return Ok(());
}
client.postage().dilute_batch(batch_id, target).await
}
pub async fn get_duration_extension_cost(
client: &Client,
batch_id: &BatchId,
duration: Duration,
network: Network,
) -> Result<BigInt, Error> {
let batch = client.postage().get_postage_batch(batch_id).await?;
let chain = client.debug().chain_state().await?;
let blocks = network.seconds_to_blocks(duration.as_secs());
let amount_per_chunk = &chain.current_price * BigInt::from(blocks);
Ok(crate::postage::get_stamp_cost(
batch.depth as i32,
&amount_per_chunk,
))
}
pub async fn get_size_extension_cost(
client: &Client,
batch_id: &BatchId,
new_size: Size,
) -> Result<BigInt, Error> {
let batch = client.postage().get_postage_batch(batch_id).await?;
let target_depth_i32 = crate::postage::get_depth_for_size(new_size.to_bytes());
let target: u8 = target_depth_i32.try_into().map_err(|_| {
Error::argument(format!("computed depth {target_depth_i32} out of u8 range"))
})?;
if target <= batch.depth {
return Ok(BigInt::from(0));
}
let amount = batch
.amount
.as_ref()
.ok_or_else(|| Error::argument("batch missing amount"))?;
let scale = BigInt::from(2u32).pow(target as u32) - BigInt::from(2u32).pow(batch.depth as u32);
Ok(scale * amount)
}
pub async fn calculate_top_up_for_bzz(
client: &Client,
batch_id: &BatchId,
target_bzz: &BigInt,
) -> Result<BigInt, Error> {
let batch = client.postage().get_postage_batch(batch_id).await?;
let current = batch
.amount
.as_ref()
.ok_or_else(|| Error::argument("batch missing amount"))?;
if target_bzz <= current {
return Ok(BigInt::from(0));
}
Ok(target_bzz - current)
}