diff --git a/crates/walrus-sui/src/client.rs b/crates/walrus-sui/src/client.rs index 5e196a3a6..9f6f77217 100644 --- a/crates/walrus-sui/src/client.rs +++ b/crates/walrus-sui/src/client.rs @@ -28,12 +28,7 @@ use sui_sdk::{ }, types::base_types::ObjectID, }; -use sui_types::{ - TypeTag, - base_types::SuiAddress, - event::EventID, - transaction::{Argument, TransactionData}, -}; +use sui_types::{TypeTag, base_types::SuiAddress, event::EventID, transaction::TransactionData}; use tokio::sync::Mutex; use tokio_stream::Stream; use tracing::Level; @@ -1471,14 +1466,34 @@ impl SuiContractClientInner { return Ok(vec![]); } + let subsidies_package_id = self.read_client.get_subsidies_package_id(); + let expected_num_blobs = blob_metadata_and_storage.len(); tracing::debug!(num_blobs = expected_num_blobs, "starting to register blobs"); let mut pt_builder = self.transaction_builder()?; // Build a ptb to include all register blob commands for all blobs. for (blob_metadata, storage) in blob_metadata_and_storage.into_iter() { - pt_builder - .register_blob(storage.id.into(), blob_metadata, persistence) - .await?; + match subsidies_package_id { + Some(pkg_id) => { + pt_builder + .register_blob_with_subsidies( + storage.id.into(), + blob_metadata, + persistence, + pkg_id, + ) + .await?; + } + None => { + pt_builder + .register_blob_without_subsidies( + storage.id.into(), + blob_metadata, + persistence, + ) + .await?; + } + }; } let transaction = pt_builder.build_transaction_data(self.gas_budget).await?; let res = self @@ -1515,11 +1530,12 @@ impl SuiContractClientInner { match subsidies_package_id { Some(pkg_id) => { match self - .reserve_and_register_blobs_with_subsidies( + .reserve_and_register_blobs_inner( epochs_ahead, blob_metadata_list.clone(), persistence, pkg_id, + true, ) .await { @@ -1531,10 +1547,12 @@ impl SuiContractClientInner { "Walrus package version mismatch in subsidies call, \ falling back to direct contract call" ); - self.reserve_and_register_blobs_without_subsidies( + self.reserve_and_register_blobs_inner( epochs_ahead, blob_metadata_list.clone(), persistence, + ObjectID::random(), + false, ) .await } @@ -1542,79 +1560,27 @@ impl SuiContractClientInner { } } None => { - self.reserve_and_register_blobs_without_subsidies( + self.reserve_and_register_blobs_inner( epochs_ahead, blob_metadata_list, persistence, + ObjectID::random(), + false, ) .await } } } - /// reserve and register blobs with subsidies - pub async fn reserve_and_register_blobs_with_subsidies( + /// reserve and register blobs inner + pub async fn reserve_and_register_blobs_inner( &mut self, epochs_ahead: EpochCount, blob_metadata_list: Vec, persistence: BlobPersistence, subsidies_package_id: ObjectID, + with_subsidies: bool, ) -> SuiClientResult> { - // Use helper for implementing with the subsidies approach - self.reserve_and_register_blobs_impl( - epochs_ahead, - blob_metadata_list, - persistence, - |builder, encoded_size, epochs| { - Box::pin(async move { - builder - .reserve_space_with_subsidies(encoded_size, epochs, subsidies_package_id) - .await - }) as BoxFuture<'_, SuiClientResult> - }, - ) - .await - } - - /// reserve and register blobs without subsidies - pub async fn reserve_and_register_blobs_without_subsidies( - &mut self, - epochs_ahead: EpochCount, - blob_metadata_list: Vec, - persistence: BlobPersistence, - ) -> SuiClientResult> { - // Use helper for implementing with the non-subsidies approach - self.reserve_and_register_blobs_impl( - epochs_ahead, - blob_metadata_list, - persistence, - |builder, encoded_size, epochs| { - Box::pin(async move { - builder - .reserve_space_without_subsidies(encoded_size, epochs) - .await - }) as BoxFuture<'_, SuiClientResult> - }, - ) - .await - } - - /// Common implementation for reserving and registering blobs - async fn reserve_and_register_blobs_impl( - &mut self, - epochs_ahead: EpochCount, - blob_metadata_list: Vec, - persistence: BlobPersistence, - reserve_space_fn: F, - ) -> SuiClientResult> - where - F: for<'a> Fn( - &'a mut WalrusPtbBuilder, - u64, - EpochCount, - ) -> BoxFuture<'a, SuiClientResult> - + Send, - { if blob_metadata_list.is_empty() { tracing::debug!("no blobs to register"); return Ok(vec![]); @@ -1632,8 +1598,23 @@ impl SuiContractClientInner { let mut main_storage_arg_size = blob_metadata_list .iter() .fold(0, |acc, metadata| acc + metadata.encoded_size); - let main_storage_arg = - reserve_space_fn(&mut pt_builder, main_storage_arg_size, epochs_ahead).await?; + + let main_storage_arg = match with_subsidies { + true => { + pt_builder + .reserve_space_with_subsidies( + main_storage_arg_size, + epochs_ahead, + subsidies_package_id, + ) + .await? + } + false => { + pt_builder + .reserve_space_without_subsidies(main_storage_arg_size, epochs_ahead) + .await? + } + }; for blob_metadata in blob_metadata_list.into_iter() { // Split off a storage resource, unless the remainder is equal to the required size. @@ -1645,14 +1626,33 @@ impl SuiContractClientInner { } else { main_storage_arg }; - pt_builder - .register_blob(storage_arg.into(), blob_metadata, persistence) - .await?; + + match with_subsidies { + true => { + pt_builder + .register_blob_with_subsidies( + storage_arg.into(), + blob_metadata, + persistence, + subsidies_package_id, + ) + .await? + } + false => { + pt_builder + .register_blob_without_subsidies( + storage_arg.into(), + blob_metadata, + persistence, + ) + .await? + } + }; } let transaction = pt_builder.build_transaction_data(self.gas_budget).await?; let res = self - .sign_and_send_transaction(transaction, "reserve_and_register_blobs_impl") + .sign_and_send_transaction(transaction, "reserve_and_register_blobs_inner") .await?; let blob_obj_ids = get_created_sui_object_ids_by_type( &res, diff --git a/crates/walrus-sui/src/client/read_client.rs b/crates/walrus-sui/src/client/read_client.rs index da29c08ec..333238bc0 100644 --- a/crates/walrus-sui/src/client/read_client.rs +++ b/crates/walrus-sui/src/client/read_client.rs @@ -319,8 +319,9 @@ impl SuiReadClient { let wal_type = sui_client.wal_type_from_package(walrus_package_id).await?; let subsidies = if let Some(subsidies_object_id) = contract_config.subsidies_object { let subsidies_package_id = sui_client - .get_subsidies_package_id_from_subsidies_object(subsidies_object_id) - .await?; + .get_subsidies_object(subsidies_object_id) + .await? + .package_id; Some(Subsidies { package_id: subsidies_package_id, object_id: subsidies_object_id, @@ -804,8 +805,9 @@ impl SuiReadClient { pub async fn set_subsidies_object(&self, subsidies_object_id: ObjectID) -> SuiClientResult<()> { let subsidies_package_id = self .sui_client - .get_subsidies_package_id_from_subsidies_object(subsidies_object_id) - .await?; + .get_subsidies_object(subsidies_object_id) + .await? + .package_id; *self.subsidies_mut() = Some(Subsidies { package_id: subsidies_package_id, object_id: subsidies_object_id, @@ -1170,8 +1172,9 @@ impl ReadClient for SuiReadClient { if let Some(subsidies) = subsidies { let new_package_id = self .sui_client - .get_subsidies_package_id_from_subsidies_object(subsidies.object_id) - .await?; + .get_subsidies_object(subsidies.object_id) + .await? + .package_id; // Update the package_id if it has changed if new_package_id != subsidies.package_id { diff --git a/crates/walrus-sui/src/client/retry_client/retriable_sui_client.rs b/crates/walrus-sui/src/client/retry_client/retriable_sui_client.rs index b1daf2b82..de03c2b37 100644 --- a/crates/walrus-sui/src/client/retry_client/retriable_sui_client.rs +++ b/crates/walrus-sui/src/client/retry_client/retriable_sui_client.rs @@ -887,16 +887,11 @@ impl RetriableSuiClient { } /// Checks if the Walrus subsidies object exist on chain and returns the subsidies package ID. - pub(crate) async fn get_subsidies_package_id_from_subsidies_object( + pub(crate) async fn get_subsidies_object( &self, subsidies_object_id: ObjectID, - ) -> SuiClientResult { - let subsidies_object = self - .get_sui_object::(subsidies_object_id) - .await?; - - let pkg_id = subsidies_object.package_id; - Ok(pkg_id) + ) -> SuiClientResult { + self.get_sui_object::(subsidies_object_id).await } /// Returns the package ID from the type of the given object. diff --git a/crates/walrus-sui/src/client/transaction_builder.rs b/crates/walrus-sui/src/client/transaction_builder.rs index 058b41689..c5de3f35b 100644 --- a/crates/walrus-sui/src/client/transaction_builder.rs +++ b/crates/walrus-sui/src/client/transaction_builder.rs @@ -61,7 +61,7 @@ use crate::{ UpdatePublicKeyParams, move_structs::{Authorized, BlobAttribute, EmergencyUpgradeCap, NodeMetadata, WalExchange}, }, - utils::{price_for_encoded_length, write_price_for_encoded_length}, + utils::{MAX_BUYER_SUBSIDY_RATE, price_for_encoded_length, write_price_for_encoded_length}, }; const CLOCK_OBJECT_ARG: ObjectArg = ObjectArg::SharedObject { @@ -267,7 +267,7 @@ impl WalrusPtbBuilder { epochs_ahead: EpochCount, ) -> SuiClientResult { let price = self - .storage_price_for_encoded_length(encoded_size, epochs_ahead) + .storage_price_for_encoded_length(encoded_size, epochs_ahead, false) .await?; self.fill_wal_balance(price).await?; @@ -292,7 +292,7 @@ impl WalrusPtbBuilder { subsidies_package_id: ObjectID, ) -> SuiClientResult { let price = self - .storage_price_for_encoded_length(encoded_size, epochs_ahead) + .storage_price_for_encoded_length(encoded_size, epochs_ahead, true) .await?; self.fill_wal_balance(price).await?; let reserve_arguments = vec![ @@ -332,15 +332,16 @@ impl WalrusPtbBuilder { Ok(result_arg) } - /// Adds a call to `register_blob` to the `pt_builder` and returns the result [`Argument`]. - pub async fn register_blob( + /// Adds a call to `register_blob` to the `pt_builder` and returns the result [`Argument`] + /// without subsidies. + pub async fn register_blob_without_subsidies( &mut self, storage_resource: ArgumentOrOwnedObject, blob_metadata: BlobObjectMetadata, persistence: BlobPersistence, ) -> SuiClientResult { let price = self - .write_price_for_encoded_length(blob_metadata.encoded_size) + .write_price_for_encoded_length(blob_metadata.encoded_size, false) .await?; self.fill_wal_balance(price).await?; @@ -365,6 +366,46 @@ impl WalrusPtbBuilder { Ok(result_arg) } + /// Adds a call to `register_blob` to the `pt_builder` and returns the result [`Argument`]. + /// with subsidies. + pub async fn register_blob_with_subsidies( + &mut self, + storage_resource: ArgumentOrOwnedObject, + blob_metadata: BlobObjectMetadata, + persistence: BlobPersistence, + subsidies_package_id: ObjectID, + ) -> SuiClientResult { + let price = self + .write_price_for_encoded_length(blob_metadata.encoded_size, true) + .await?; + self.fill_wal_balance(price).await?; + + let storage_resource_arg = self.argument_from_arg_or_obj(storage_resource).await?; + + let register_arguments = vec![ + self.subsidies_arg(Mutability::Mutable).await?, + self.system_arg(Mutability::Mutable).await?, + storage_resource_arg, + self.pt_builder.pure(blob_metadata.blob_id)?, + self.pt_builder.pure(blob_metadata.root_hash.bytes())?, + self.pt_builder.pure(blob_metadata.unencoded_size)?, + self.pt_builder + .pure(u8::from(blob_metadata.encoding_type))?, + self.pt_builder.pure(persistence.is_deletable())?, + self.wal_coin_arg()?, + ]; + let result_arg = self.move_call( + subsidies_package_id, + contracts::subsidies::register_blob, + register_arguments, + )?; + + self.reduce_wal_balance(price)?; + self.mark_arg_as_consumed(&storage_resource_arg); + self.add_result_to_be_consumed(result_arg); + Ok(result_arg) + } + /// Adds a call to `certify_blob` to the `pt_builder`. pub async fn certify_blob( &mut self, @@ -631,7 +672,7 @@ impl WalrusPtbBuilder { encoded_size: u64, ) -> SuiClientResult<()> { let price = self - .storage_price_for_encoded_length(encoded_size, epochs_extended) + .storage_price_for_encoded_length(encoded_size, epochs_extended, false) .await?; self.fill_wal_balance(price).await?; @@ -656,7 +697,7 @@ impl WalrusPtbBuilder { subsidies_package_id: ObjectID, ) -> SuiClientResult<()> { let price = self - .storage_price_for_encoded_length(encoded_size, epochs_ahead) + .storage_price_for_encoded_length(encoded_size, epochs_ahead, true) .await?; self.fill_wal_balance(price).await?; @@ -1537,19 +1578,60 @@ impl WalrusPtbBuilder { &self, encoded_size: u64, epochs_ahead: EpochCount, + with_subsidies: bool, ) -> SuiClientResult { - Ok(price_for_encoded_length( + let full_price = price_for_encoded_length( encoded_size, self.read_client.storage_price_per_unit_size().await?, epochs_ahead, - )) + ); + let buyer_pays = match self.read_client.get_subsidies_object_id() { + Some(subsidies_object_id) if with_subsidies => { + let subsidies_object = self + .read_client + .sui_client() + .get_subsidies_object(subsidies_object_id) + .await?; + (full_price + * (MAX_BUYER_SUBSIDY_RATE - u64::from(subsidies_object.buyer_subsidy_rate))) + .div_ceil(MAX_BUYER_SUBSIDY_RATE) + } + Some(_) => full_price, + None => full_price, + }; + + Ok(buyer_pays) } - async fn write_price_for_encoded_length(&self, encoded_size: u64) -> SuiClientResult { - Ok(write_price_for_encoded_length( + async fn write_price_for_encoded_length( + &self, + encoded_size: u64, + with_subsidies: bool, + ) -> SuiClientResult { + let full_price = write_price_for_encoded_length( encoded_size, self.read_client.write_price_per_unit_size().await?, - )) + ); + let buyer_pays = match self.read_client.get_subsidies_object_id() { + Some(subsidies_object_id) if with_subsidies => { + let buyer_subsidy_rate = self + .read_client + .sui_client() + .get_subsidies_object(subsidies_object_id) + .await? + .buyer_subsidy_rate; + // Hack until new subsidy is integrated with system contract + if u64::from(buyer_subsidy_rate) == MAX_BUYER_SUBSIDY_RATE { + 0 + } else { + full_price + } + } + Some(_) => full_price, + None => full_price, + }; + + Ok(buyer_pays) } async fn argument_from_arg_or_obj( diff --git a/crates/walrus-sui/src/utils.rs b/crates/walrus-sui/src/utils.rs index df75437d6..dbd9397a5 100644 --- a/crates/walrus-sui/src/utils.rs +++ b/crates/walrus-sui/src/utils.rs @@ -45,6 +45,9 @@ use crate::{ wallet::Wallet, }; +/// Max subsidy rate is 100%. +pub const MAX_BUYER_SUBSIDY_RATE: u64 = 10_000; + // Keep in sync with the same constant in `contracts/walrus/sources/system/system_state_inner.move`. // The storage unit is used in doc comments for CLI arguments in the files // `crates/walrus-service/bin/deploy.rs` and `crates/walrus-service/bin/node.rs`.