1use std::time::Duration;
11
12use num_bigint::BigInt;
13
14use crate::Client;
15use crate::postage::PostageBatch;
16use crate::swarm::{BatchId, Error, Network, Size};
17
18#[derive(Clone, Debug, Default)]
22pub struct StorageOptions {
23 pub network: Network,
25 pub label: Option<String>,
27 pub immutable: Option<bool>,
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct StorageCost {
34 pub depth: u8,
36 pub amount_per_chunk: BigInt,
38 pub total_cost: BigInt,
40 pub blocks: u64,
42}
43
44pub async fn get_storage_cost(
48 client: &Client,
49 size: Size,
50 duration: Duration,
51 network: Network,
52) -> Result<StorageCost, Error> {
53 let chain = client.debug().chain_state().await?;
54 let blocks = network.seconds_to_blocks(duration.as_secs());
55 let depth_i32 = crate::postage::get_depth_for_size(size.to_bytes());
56 let depth: u8 = depth_i32
57 .try_into()
58 .map_err(|_| Error::argument(format!("computed depth {depth_i32} out of u8 range")))?;
59 let amount_per_chunk = &chain.current_price * BigInt::from(blocks);
60 let total_cost = crate::postage::get_stamp_cost(depth_i32, &amount_per_chunk);
61 Ok(StorageCost {
62 depth,
63 amount_per_chunk,
64 total_cost,
65 blocks,
66 })
67}
68
69pub async fn buy_storage(
74 client: &Client,
75 size: Size,
76 duration: Duration,
77 opts: &StorageOptions,
78) -> Result<BatchId, Error> {
79 let cost = get_storage_cost(client, size, duration, opts.network).await?;
80 client
81 .postage()
82 .create_postage_batch(&cost.amount_per_chunk, cost.depth, opts.label.as_deref())
83 .await
84}
85
86pub async fn extend_storage_duration(
90 client: &Client,
91 batch_id: &BatchId,
92 duration: Duration,
93 network: Network,
94) -> Result<(), Error> {
95 let chain = client.debug().chain_state().await?;
96 let blocks = network.seconds_to_blocks(duration.as_secs());
97 let amount = &chain.current_price * BigInt::from(blocks);
98 client.postage().top_up_batch(batch_id, &amount).await
99}
100
101pub async fn extend_storage_size(
105 client: &Client,
106 batch_id: &BatchId,
107 new_size: Size,
108) -> Result<(), Error> {
109 let batch: PostageBatch = client.postage().get_postage_batch(batch_id).await?;
110 let target_depth_i32 = crate::postage::get_depth_for_size(new_size.to_bytes());
111 let target: u8 = target_depth_i32.try_into().map_err(|_| {
112 Error::argument(format!("computed depth {target_depth_i32} out of u8 range"))
113 })?;
114 if target <= batch.depth {
115 return Ok(());
116 }
117 client.postage().dilute_batch(batch_id, target).await
118}
119
120pub async fn get_duration_extension_cost(
124 client: &Client,
125 batch_id: &BatchId,
126 duration: Duration,
127 network: Network,
128) -> Result<BigInt, Error> {
129 let batch = client.postage().get_postage_batch(batch_id).await?;
130 let chain = client.debug().chain_state().await?;
131 let blocks = network.seconds_to_blocks(duration.as_secs());
132 let amount_per_chunk = &chain.current_price * BigInt::from(blocks);
133 Ok(crate::postage::get_stamp_cost(
134 batch.depth as i32,
135 &amount_per_chunk,
136 ))
137}
138
139pub async fn get_size_extension_cost(
144 client: &Client,
145 batch_id: &BatchId,
146 new_size: Size,
147) -> Result<BigInt, Error> {
148 let batch = client.postage().get_postage_batch(batch_id).await?;
149 let target_depth_i32 = crate::postage::get_depth_for_size(new_size.to_bytes());
150 let target: u8 = target_depth_i32.try_into().map_err(|_| {
151 Error::argument(format!("computed depth {target_depth_i32} out of u8 range"))
152 })?;
153 if target <= batch.depth {
154 return Ok(BigInt::from(0));
155 }
156 let amount = batch
157 .amount
158 .as_ref()
159 .ok_or_else(|| Error::argument("batch missing amount"))?;
160 let scale = BigInt::from(2u32).pow(target as u32) - BigInt::from(2u32).pow(batch.depth as u32);
161 Ok(scale * amount)
162}
163
164pub async fn calculate_top_up_for_bzz(
168 client: &Client,
169 batch_id: &BatchId,
170 target_bzz: &BigInt,
171) -> Result<BigInt, Error> {
172 let batch = client.postage().get_postage_batch(batch_id).await?;
173 let current = batch
174 .amount
175 .as_ref()
176 .ok_or_else(|| Error::argument("batch missing amount"))?;
177 if target_bzz <= current {
178 return Ok(BigInt::from(0));
179 }
180 Ok(target_bzz - current)
181}