1use crate::error::BucketError;
2#[cfg(not(feature = "library"))]
3use cosmwasm_std::entry_point;
4use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
5use cw2::set_contract_version;
6use cw_utils::nonpayable;
7
8use crate::crypto;
9use crate::error::ContractError;
10use crate::msg::{ExecuteMsg, InstantiateMsg, ObjectId, QueryMsg};
11use crate::state;
12use crate::state::{objects, pins, Bucket, Object, Pin, BUCKET, DATA};
13
14const CONTRACT_NAME: &str = concat!("crates.io:", env!("CARGO_PKG_NAME"));
16const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
17
18#[cfg_attr(not(feature = "library"), entry_point)]
19pub fn instantiate(
20 deps: DepsMut<'_>,
21 _env: Env,
22 info: MessageInfo,
23 msg: InstantiateMsg,
24) -> Result<Response, ContractError> {
25 nonpayable(&info)?;
26 let bucket = Bucket::try_new(
27 info.sender,
28 msg.bucket,
29 msg.config.try_into()?,
30 msg.limits.try_into()?,
31 msg.pagination.try_into()?,
32 )?;
33
34 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
35 BUCKET.save(deps.storage, &bucket)?;
36
37 Ok(Response::default())
38}
39
40#[cfg_attr(not(feature = "library"), entry_point)]
41pub fn execute(
42 deps: DepsMut<'_>,
43 _env: Env,
44 info: MessageInfo,
45 msg: ExecuteMsg,
46) -> Result<Response, ContractError> {
47 nonpayable(&info)?;
48
49 match msg {
50 ExecuteMsg::StoreObject {
51 data,
52 pin,
53 compression_algorithm,
54 } => execute::store_object(deps, info, data, pin, compression_algorithm),
55 ExecuteMsg::PinObject { id } => execute::pin_object(deps, info, id),
56 ExecuteMsg::UnpinObject { id } => execute::unpin_object(deps, info, id),
57 ExecuteMsg::ForgetObject { id } => execute::forget_object(deps, info, id),
58 }
59}
60
61pub mod execute {
62 use super::*;
63 use crate::compress::CompressionAlgorithm;
64 use crate::crypto::Hash;
65 use crate::msg;
66 use crate::state::BucketLimits;
67 use crate::ContractError::ObjectPinned;
68 use cosmwasm_std::{Addr, Order, Storage, Uint128};
69
70 pub fn store_object(
71 deps: DepsMut<'_>,
72 info: MessageInfo,
73 data: Binary,
74 pin: bool,
75 compression_algorithm: Option<msg::CompressionAlgorithm>,
76 ) -> Result<Response, ContractError> {
77 let size = (data.len() as u128).into();
78 let bucket = BUCKET.load(deps.storage)?;
79 let compressions = &bucket.config.accepted_compression_algorithms;
80 let compression: CompressionAlgorithm = compression_algorithm
81 .map(Into::into)
82 .or_else(|| compressions.first().cloned())
83 .unwrap_or(CompressionAlgorithm::Passthrough);
84
85 if let Some(limit) = bucket.limits.max_object_size {
87 if size > limit {
88 return Err(BucketError::MaxObjectSizeLimitExceeded(size, limit).into());
89 }
90 }
91 if let Some(limit) = bucket.limits.max_objects {
92 let value = bucket.stat.object_count + Uint128::one();
93 if value > limit {
94 return Err(BucketError::MaxObjectsLimitExceeded(value, limit).into());
95 }
96 }
97 if let Some(limit) = bucket.limits.max_object_pins {
98 if pin && limit.is_zero() {
99 return Err(BucketError::MaxObjectPinsLimitExceeded(Uint128::one(), limit).into());
100 }
101 }
102 if let Some(limit) = bucket.limits.max_total_size {
103 let value = bucket.stat.size + size;
104 if value > limit {
105 return Err(BucketError::MaxTotalSizeLimitExceeded(value, limit).into());
106 }
107 }
108 if !compressions.contains(&compression) {
109 return Err(BucketError::CompressionAlgorithmNotAccepted(
110 compression.into(),
111 bucket
112 .config
113 .accepted_compression_algorithms
114 .into_iter()
115 .map(Into::into)
116 .collect(),
117 )
118 .into());
119 }
120
121 let id = crypto::hash(&bucket.config.hash_algorithm.into(), &data.to_vec());
123 let mut res = Response::new()
124 .add_attribute("action", "store_object")
125 .add_attribute("id", id.to_string());
126
127 let data_path = DATA.key(id.clone());
128
129 let (old_obj, mut new_obj) = if !data_path.has(deps.storage) {
130 let compressed_data = compression.compress(&data)?;
131 data_path.save(deps.storage, &compressed_data)?;
132
133 let compressed_size = (compressed_data.len() as u128).into();
134
135 BUCKET.update(deps.storage, |mut bucket| -> Result<_, ContractError> {
137 let stat = &mut bucket.stat;
138 stat.size += size;
139 stat.object_count += Uint128::one();
140 stat.compressed_size += compressed_size;
141 Ok(bucket)
142 })?;
143
144 res = res
145 .add_attribute("size", size)
146 .add_attribute("compressed_size", compressed_size);
147
148 (
149 None,
150 Object {
151 id: id.clone(),
152 owner: info.sender.clone(),
153 size,
154 pin_count: Uint128::zero(),
155 compression,
156 compressed_size,
157 },
158 )
159 } else {
160 let old = objects().load(deps.storage, id.clone())?;
161 (Some(old.clone()), old)
162 };
163
164 let mut pinned = false;
165 if pin {
166 pinned = may_pin_object(deps.storage, info.sender, &mut new_obj)?;
167 }
168
169 objects().replace(deps.storage, id, Some(&new_obj), old_obj.as_ref())?;
170
171 Ok(res.add_attribute("pinned", pinned.to_string()))
172 }
173
174 pub fn pin_object(
175 deps: DepsMut<'_>,
176 info: MessageInfo,
177 object_id: ObjectId,
178 ) -> Result<Response, ContractError> {
179 let res = Response::new()
180 .add_attribute("action", "pin_object")
181 .add_attribute("id", object_id.clone());
182
183 let id: Hash = object_id.try_into()?;
184 let object = objects().load(deps.storage, id.clone())?;
185 let mut updated_object = object.clone();
186
187 if may_pin_object(deps.storage, info.sender, &mut updated_object)? {
188 objects().replace(deps.storage, id, Some(&updated_object), Some(&object))?;
189 }
190
191 Ok(res)
192 }
193
194 pub fn unpin_object(
195 deps: DepsMut<'_>,
196 info: MessageInfo,
197 object_id: ObjectId,
198 ) -> Result<Response, ContractError> {
199 let id: Hash = object_id.clone().try_into()?;
200 let object_path = objects().key(id.clone());
201 let mut object = object_path.load(deps.storage)?;
202
203 let res = Response::new()
204 .add_attribute("action", "unpin_object")
205 .add_attribute("id", object_id);
206
207 if !pins().has(deps.storage, (id.clone(), info.sender.clone())) {
208 return Ok(res);
209 }
210
211 object.pin_count -= Uint128::one();
212 object_path.save(deps.storage, &object)?;
213
214 pins().remove(deps.storage, (id, info.sender))?;
215
216 Ok(res)
217 }
218
219 pub fn forget_object(
220 deps: DepsMut<'_>,
221 info: MessageInfo,
222 object_id: ObjectId,
223 ) -> Result<Response, ContractError> {
224 let id: Hash = object_id.clone().try_into()?;
225 if pins().has(deps.storage, (id.clone(), info.sender.clone())) {
226 pins().remove(deps.storage, (id.clone(), info.sender))?;
227 }
228
229 if pins()
230 .idx
231 .object
232 .prefix(id.clone())
233 .keys_raw(deps.storage, None, None, Order::Ascending)
234 .next()
235 .is_some()
236 {
237 return Err(ObjectPinned {});
238 }
239 let object = query::object(deps.as_ref(), object_id.clone())?;
240 BUCKET.update(deps.storage, |mut b| -> Result<_, ContractError> {
241 b.stat.object_count -= Uint128::one();
242 b.stat.size -= object.size;
243 b.stat.compressed_size -= object.compressed_size;
244 Ok(b)
245 })?;
246
247 objects().remove(deps.storage, id.clone())?;
248 DATA.remove(deps.storage, id);
249
250 Ok(Response::new()
251 .add_attribute("action", "forget_object")
252 .add_attribute("id", object_id))
253 }
254
255 fn may_pin_object(
256 storage: &mut dyn Storage,
257 pinner: Addr,
258 target: &mut Object,
259 ) -> Result<bool, ContractError> {
260 if pins().has(storage, (target.id.clone(), pinner.clone())) {
261 return Ok(false);
262 }
263
264 target.pin_count += Uint128::one();
265
266 let bucket = BUCKET.load(storage)?;
267
268 match bucket.limits {
269 BucketLimits {
270 max_object_pins: Some(max),
271 ..
272 } if max < target.pin_count => {
273 Err(BucketError::MaxObjectPinsLimitExceeded(target.pin_count, max).into())
274 }
275 _ => {
276 pins().save(
277 storage,
278 (target.id.clone(), pinner.clone()),
279 &Pin {
280 id: target.id.clone(),
281 address: pinner,
282 },
283 )?;
284 Ok(true)
285 }
286 }
287 }
288}
289
290#[cfg_attr(not(feature = "library"), entry_point)]
291pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
292 match msg {
293 QueryMsg::Bucket {} => to_json_binary(&query::bucket(deps)?),
294 QueryMsg::Object { id } => to_json_binary(&query::object(deps, id)?),
295 QueryMsg::ObjectData { id } => to_json_binary(&query::data(deps, id)?),
296 QueryMsg::Objects {
297 address,
298 after,
299 first,
300 } => to_json_binary(&query::fetch_objects(deps, address, after, first)?),
301 QueryMsg::ObjectPins { id, after, first } => {
302 to_json_binary(&query::object_pins(deps, id, after, first)?)
303 }
304 }
305}
306
307pub mod query {
308 use super::*;
309 use crate::crypto::Hash;
310 use crate::cursor;
311 use crate::msg::{
312 BucketResponse, Cursor, ObjectPinsResponse, ObjectResponse, ObjectsResponse, PageInfo,
313 };
314 use crate::pagination::{PaginationHandler, QueryPage};
315 use cosmwasm_std::{Addr, Order, StdError};
316
317 pub fn bucket(deps: Deps<'_>) -> StdResult<BucketResponse> {
318 let bucket = BUCKET.load(deps.storage)?;
319
320 Ok(BucketResponse {
321 name: bucket.name,
322 config: bucket.config.into(),
323 limits: bucket.limits.into(),
324 pagination: bucket.pagination.into(),
325 stat: bucket.stat.into(),
326 })
327 }
328
329 pub fn object(deps: Deps<'_>, object_id: ObjectId) -> StdResult<ObjectResponse> {
330 let id: Hash = object_id.try_into()?;
331 let object = objects().load(deps.storage, id)?;
332 Ok((&object).into())
333 }
334
335 pub fn data(deps: Deps<'_>, object_id: ObjectId) -> StdResult<Binary> {
336 let id: Hash = object_id.try_into()?;
337 let compression = objects().load(deps.storage, id.clone())?.compression;
338 let data = DATA.load(deps.storage, id)?;
339
340 compression
341 .decompress(&data)
342 .map_err(|e| StdError::serialize_err(format!("{:?}", compression), e))
343 .map(Binary::from)
344 }
345
346 pub fn fetch_objects(
347 deps: Deps<'_>,
348 address: Option<String>,
349 after: Option<Cursor>,
350 first: Option<u32>,
351 ) -> StdResult<ObjectsResponse> {
352 let address = match address {
353 Some(raw) => Some(deps.api.addr_validate(&raw)?),
354 _ => None,
355 };
356
357 let handler: PaginationHandler<'_, Object, Hash> =
358 PaginationHandler::from(BUCKET.load(deps.storage)?.pagination);
359
360 let page: (Vec<Object>, PageInfo) = handler.query_page(
361 |min_bound| match address {
362 Some(addr) => objects().idx.owner.prefix(addr).range(
363 deps.storage,
364 min_bound,
365 None,
366 Order::Ascending,
367 ),
368 _ => objects().range(deps.storage, min_bound, None, Order::Ascending),
369 },
370 after,
371 first,
372 )?;
373
374 Ok(ObjectsResponse {
375 data: page.0.iter().map(Into::into).collect(),
376 page_info: page.1,
377 })
378 }
379
380 pub fn object_pins(
381 deps: Deps<'_>,
382 object_id: ObjectId,
383 after: Option<Cursor>,
384 first: Option<u32>,
385 ) -> StdResult<ObjectPinsResponse> {
386 let id: Hash = object_id.try_into()?;
387 objects().load(deps.storage, id.clone())?;
388
389 let handler: PaginationHandler<'_, Pin, (Hash, Addr)> =
390 PaginationHandler::from(BUCKET.load(deps.storage)?.pagination);
391
392 let page: (Vec<Pin>, PageInfo) = handler.query_page_cursor_fn(
393 |min_bound| {
394 pins().idx.object.prefix(id.clone()).range(
395 deps.storage,
396 min_bound,
397 None,
398 Order::Ascending,
399 )
400 },
401 |c| {
402 cursor::decode(c)
403 .and_then(|raw| deps.api.addr_validate(raw.as_str()))
404 .map(|addr| (id.clone(), addr))
405 },
406 |pin: &Pin| cursor::encode(pin.clone().address.into_string()),
407 after,
408 first,
409 )?;
410
411 Ok(ObjectPinsResponse {
412 data: page
413 .0
414 .iter()
415 .map(|pin: &Pin| pin.address.as_str().to_string())
416 .collect(),
417 page_info: page.1,
418 })
419 }
420}
421
422impl From<state::HashAlgorithm> for crypto::HashAlgorithm {
423 fn from(algorithm: state::HashAlgorithm) -> Self {
424 match algorithm {
425 state::HashAlgorithm::MD5 => crypto::HashAlgorithm::MD5,
426 state::HashAlgorithm::Sha224 => crypto::HashAlgorithm::Sha224,
427 state::HashAlgorithm::Sha256 => crypto::HashAlgorithm::Sha256,
428 state::HashAlgorithm::Sha384 => crypto::HashAlgorithm::Sha384,
429 state::HashAlgorithm::Sha512 => crypto::HashAlgorithm::Sha512,
430 }
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437 use crate::compress;
438 use crate::crypto::Hash;
439 use crate::error::BucketError;
440 use crate::msg::{
441 BucketConfig, BucketConfigBuilder, BucketLimitsBuilder, BucketResponse, BucketStat,
442 BucketStatBuilder, CompressionAlgorithm, HashAlgorithm, ObjectPinsResponse, ObjectResponse,
443 ObjectsResponse, PageInfo, PaginationConfigBuilder,
444 };
445 use base64::{engine::general_purpose, Engine as _};
446 use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env};
447 use cosmwasm_std::StdError::NotFound;
448 use cosmwasm_std::{coins, from_json, Addr, Attribute, Order, StdError, Uint128};
449 use cw_utils::PaymentError;
450
451 use crate::msg::CompressionAlgorithm::{Passthrough, Snappy};
452 use std::any::type_name;
453 use testing::addr::{addr, CREATOR, SENDER};
454 use testing::mock::mock_env_addr;
455
456 fn decode_hex(hex: &str) -> Vec<u8> {
457 base16ct::lower::decode_vec(hex).unwrap()
458 }
459
460 fn with_namespace(key: &[u8]) -> Vec<u8> {
461 let namespace = decode_hex("00064f424a454354");
462 let mut v = Vec::with_capacity(namespace.len() + key.len());
463 v.extend(namespace);
464 v.extend_from_slice(key);
465 v
466 }
467
468 fn not_found_object_info<T>(hex: &str) -> String {
469 let type_name = type_name::<T>();
470 let key = with_namespace(&decode_hex(hex));
471 format!("type: {type_name}; key: {:02X?}", key)
472 }
473
474 #[test]
475 fn proper_initialization() {
476 let mut deps = mock_dependencies();
477
478 let msg = InstantiateMsg {
479 bucket: "foo".to_string(),
480 config: Default::default(),
481 limits: Default::default(),
482 pagination: Default::default(),
483 };
484 let info = message_info(&addr(CREATOR), &[]);
485
486 let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
487 assert_eq!(0, res.messages.len());
488
489 let res = query(deps.as_ref(), mock_env(), QueryMsg::Bucket {}).unwrap();
490 let value: BucketResponse = from_json(&res).unwrap();
491 assert_eq!("foo", value.name);
492 assert_eq!(value.config, Default::default());
493 assert_eq!(value.limits, Default::default());
494 assert_eq!(value.pagination.max_page_size, 30);
495 assert_eq!(value.pagination.default_page_size, 10);
496
497 let bucket = BUCKET.load(&deps.storage).unwrap();
499 assert_eq!(addr(CREATOR), bucket.owner);
500 assert_eq!(Uint128::zero(), bucket.stat.size);
501 assert_eq!(Uint128::zero(), bucket.stat.object_count);
502 }
503
504 #[test]
505 fn proper_config_initialization() {
506 let mut deps = mock_dependencies();
507
508 let test_cases = vec![
510 (HashAlgorithm::MD5, HashAlgorithm::MD5),
511 (HashAlgorithm::Sha224, HashAlgorithm::Sha224),
512 (HashAlgorithm::Sha256, HashAlgorithm::Sha256),
513 (HashAlgorithm::Sha384, HashAlgorithm::Sha384),
514 (HashAlgorithm::Sha512, HashAlgorithm::Sha512),
515 ];
516
517 for (hash_algorithm, expected_hash_algorithm) in test_cases {
518 let msg = InstantiateMsg {
519 bucket: "bar".to_string(),
520 config: BucketConfig {
521 hash_algorithm,
522 ..Default::default()
523 },
524 limits: Default::default(),
525 pagination: Default::default(),
526 };
527 let info = message_info(&addr(CREATOR), &[]);
528
529 let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
530
531 let res = query(deps.as_ref(), mock_env(), QueryMsg::Bucket {}).unwrap();
532 let value: BucketResponse = from_json(&res).unwrap();
533
534 assert_eq!("bar", value.name);
535 assert_eq!(value.config.hash_algorithm, expected_hash_algorithm);
536 }
537 }
538
539 #[test]
540 fn proper_limits_initialization() {
541 let mut deps = mock_dependencies();
542
543 let msg = InstantiateMsg {
544 bucket: "bar".to_string(),
545 config: Default::default(),
546 limits: BucketLimitsBuilder::default()
547 .max_total_size(Uint128::new(20000))
548 .max_objects(Uint128::new(10))
549 .max_object_size(Uint128::new(2000))
550 .max_object_pins(Uint128::new(1))
551 .build()
552 .unwrap(),
553 pagination: PaginationConfigBuilder::default()
554 .max_page_size(50)
555 .default_page_size(30)
556 .build()
557 .unwrap(),
558 };
559 let info = message_info(&addr(CREATOR), &[]);
560 let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
561
562 let res = query(deps.as_ref(), mock_env(), QueryMsg::Bucket {}).unwrap();
563 let response: BucketResponse = from_json(&res).unwrap();
564 assert_eq!(response.name, "bar");
565 assert_eq!(
566 response.limits,
567 BucketLimitsBuilder::default()
568 .max_total_size(Uint128::new(20000))
569 .max_objects(Uint128::new(10))
570 .max_object_size(Uint128::new(2000))
571 .max_object_pins(Uint128::new(1))
572 .build()
573 .unwrap(),
574 );
575 assert_eq!(
576 response.pagination,
577 PaginationConfigBuilder::default()
578 .max_page_size(50)
579 .default_page_size(30)
580 .build()
581 .unwrap(),
582 );
583 assert_eq!(response.stat, BucketStat::default());
584 }
585
586 #[test]
587 fn proper_pagination_initialization() {
588 let mut deps = mock_dependencies();
589 let msg = InstantiateMsg {
590 bucket: "bar".to_string(),
591 config: Default::default(),
592 limits: Default::default(),
593 pagination: PaginationConfigBuilder::default()
594 .max_page_size(50)
595 .default_page_size(30)
596 .build()
597 .unwrap(),
598 };
599 instantiate(
600 deps.as_mut(),
601 mock_env(),
602 message_info(&addr(CREATOR), &[]),
603 msg,
604 )
605 .unwrap();
606
607 let res = query(deps.as_ref(), mock_env(), QueryMsg::Bucket {}).unwrap();
608 let value: BucketResponse = from_json(&res).unwrap();
609 assert_eq!(value.pagination.max_page_size, 50);
610 assert_eq!(value.pagination.default_page_size, 30);
611 }
612
613 #[test]
614 fn invalid_initialization() {
615 let cases = vec![
616 (
617 Default::default(),
618 Default::default(),
619 PaginationConfigBuilder::default()
620 .max_page_size(u32::MAX)
621 .build()
622 .unwrap(),
623 Some(StdError::generic_err(
624 "'max_page_size' cannot exceed 'u32::MAX - 1'",
625 )),
626 ),
627 (
628 Default::default(),
629 Default::default(),
630 PaginationConfigBuilder::default()
631 .default_page_size(31)
632 .build()
633 .unwrap(),
634 Some(StdError::generic_err(
635 "'default_page_size' cannot exceed 'max_page_size'",
636 )),
637 ),
638 (
639 Default::default(),
640 Default::default(),
641 PaginationConfigBuilder::default()
642 .default_page_size(701)
643 .max_page_size(700)
644 .build()
645 .unwrap(),
646 Some(StdError::generic_err(
647 "'default_page_size' cannot exceed 'max_page_size'",
648 )),
649 ),
650 (
651 Default::default(),
652 Default::default(),
653 PaginationConfigBuilder::default()
654 .default_page_size(20)
655 .max_page_size(20)
656 .build()
657 .unwrap(),
658 None,
659 ),
660 (
661 Default::default(),
662 Default::default(),
663 PaginationConfigBuilder::default()
664 .default_page_size(0)
665 .build()
666 .unwrap(),
667 Some(StdError::generic_err("'default_page_size' cannot be zero")),
668 ),
669 (
670 Default::default(),
671 BucketLimitsBuilder::default()
672 .max_objects(0u128)
673 .build()
674 .unwrap(),
675 Default::default(),
676 Some(StdError::generic_err("'max_objects' cannot be zero")),
677 ),
678 (
679 Default::default(),
680 BucketLimitsBuilder::default()
681 .max_object_size(0u128)
682 .build()
683 .unwrap(),
684 Default::default(),
685 Some(StdError::generic_err("'max_object_size' cannot be zero")),
686 ),
687 (
688 Default::default(),
689 BucketLimitsBuilder::default()
690 .max_total_size(0u128)
691 .build()
692 .unwrap(),
693 Default::default(),
694 Some(StdError::generic_err("'max_total_size' cannot be zero")),
695 ),
696 (
697 Default::default(),
698 BucketLimitsBuilder::default()
699 .max_total_size(10u128)
700 .max_object_size(20u128)
701 .build()
702 .unwrap(),
703 Default::default(),
704 Some(StdError::generic_err(
705 "'max_total_size' cannot be less than 'max_object_size'",
706 )),
707 ),
708 (
709 Default::default(),
710 BucketLimitsBuilder::default()
711 .max_total_size(20u128)
712 .max_object_size(20u128)
713 .build()
714 .unwrap(),
715 Default::default(),
716 None,
717 ),
718 (
719 BucketConfigBuilder::default()
720 .accepted_compression_algorithms(vec![CompressionAlgorithm::Passthrough])
721 .build()
722 .unwrap(),
723 Default::default(),
724 Default::default(),
725 None,
726 ),
727 (
728 BucketConfigBuilder::default()
729 .accepted_compression_algorithms(vec![])
730 .build()
731 .unwrap(),
732 Default::default(),
733 Default::default(),
734 Some(StdError::generic_err(
735 "'accepted_compression_algorithms' cannot be empty",
736 )),
737 ),
738 (
739 Default::default(),
740 Default::default(),
741 Default::default(),
742 None,
743 ),
744 ];
745 for case in cases {
746 let mut deps = mock_dependencies();
747 let msg = InstantiateMsg {
748 bucket: "bar".to_string(),
749 config: case.0,
750 limits: case.1,
751 pagination: case.2,
752 };
753 match instantiate(
754 deps.as_mut(),
755 mock_env(),
756 message_info(&addr(CREATOR), &[]),
757 msg,
758 ) {
759 Err(err) => {
760 assert!(case.3.is_some());
761 assert_eq!(err, ContractError::Std(case.3.unwrap()))
762 }
763 _ => assert!(case.3.is_none()),
764 }
765 }
766 }
767
768 #[test]
769 fn empty_name_initialization() {
770 let mut deps = mock_dependencies();
771
772 let msg = InstantiateMsg {
773 bucket: "".to_string(),
774 config: Default::default(),
775 limits: Default::default(),
776 pagination: Default::default(),
777 };
778 let info = message_info(&addr(CREATOR), &[]);
779
780 let err = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap_err();
781
782 assert_eq!(err, ContractError::Bucket(BucketError::EmptyName));
783 }
784
785 #[test]
786 fn whitespace_initialization() {
787 let mut deps = mock_dependencies();
788
789 let msg = InstantiateMsg {
790 bucket: "foo bar".to_string(),
791 config: Default::default(),
792 limits: Default::default(),
793 pagination: Default::default(),
794 };
795 let info = message_info(&addr(CREATOR), &[]);
796
797 let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
798 assert_eq!(0, res.messages.len());
799
800 let res = query(deps.as_ref(), mock_env(), QueryMsg::Bucket {}).unwrap();
801 let value: BucketResponse = from_json(&res).unwrap();
802 assert_eq!("foobar", value.name);
803 }
804
805 #[test]
806 fn funds_initialization() {
807 let mut deps = mock_dependencies();
808 let msg = InstantiateMsg {
809 bucket: "foo".to_string(),
810 config: Default::default(),
811 limits: Default::default(),
812 pagination: Default::default(),
813 };
814 let info = message_info(&addr(CREATOR), &coins(10, "uaxone"));
815
816 let res = instantiate(deps.as_mut(), mock_env(), info, msg);
817 assert!(res.is_err());
818 assert_eq!(
819 res.unwrap_err(),
820 ContractError::Payment(PaymentError::NonPayable {})
821 );
822 }
823
824 #[test]
825 fn execute_fail_with_funds() {
826 let mut deps = mock_dependencies();
827 let env = mock_env();
828 let info = message_info(&addr(SENDER), &coins(10, "uaxone"));
829
830 let messages = vec![
831 ExecuteMsg::StoreObject {
832 data: Binary::from("data".as_bytes()),
833 pin: false,
834 compression_algorithm: None,
835 },
836 ExecuteMsg::PinObject {
837 id: "object_id".to_string(),
838 },
839 ExecuteMsg::UnpinObject {
840 id: "object_id".to_string(),
841 },
842 ExecuteMsg::ForgetObject {
843 id: "object_id".to_string(),
844 },
845 ];
846
847 for msg in messages {
848 let result = execute(deps.as_mut(), env.clone(), info.clone(), msg);
849 assert!(result.is_err());
850 assert_eq!(
851 result.unwrap_err(),
852 ContractError::Payment(PaymentError::NonPayable {})
853 );
854 }
855 }
856
857 #[test]
858 fn store_object_without_limits() {
859 let obj1_content = &general_purpose::STANDARD.encode("hello");
860 let obj2_content = &general_purpose::STANDARD.encode("okp4");
861
862 let test_cases = vec![
863 (
864 HashAlgorithm::MD5,
865 vec![
866 (
867 obj1_content,
868 true,
869 "5d41402abc4b2a76b9719d911017c592"
870 .to_string(),
871 5,
872 vec![
873 Attribute::new("action", "store_object"),
874 Attribute::new("id", "5d41402abc4b2a76b9719d911017c592"
875 .to_string()),
876 Attribute::new("size", "5"),
877 Attribute::new("compressed_size", "5"),
878 Attribute::new("pinned", "true"),
879 ]
880 ),
881 (
882 obj2_content,
883 false,
884 "33f41d49353ad1a876e36918f64eac4d"
885 .to_string(),
886 4,
887 vec![
888 Attribute::new("action", "store_object"),
889 Attribute::new("id", "33f41d49353ad1a876e36918f64eac4d"
890 .to_string()),
891 Attribute::new("size", "4"),
892 Attribute::new("compressed_size", "4"),
893 Attribute::new("pinned", "false"),
894 ]
895 ),
896 ],
897 ),
898 (
899 HashAlgorithm::Sha224,
900 vec![
901 (
902 obj1_content,
903 true,
904 "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193"
905 .to_string(),
906 5,
907 vec![
908 Attribute::new("action", "store_object"),
909 Attribute::new("id", "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193"
910 .to_string()),
911 Attribute::new("size", "5"),
912 Attribute::new("compressed_size", "5"),
913 Attribute::new("pinned", "true"),
914 ]
915 ),
916 (
917 obj2_content,
918 false,
919 "fe798aa30e560c57d69c46982b2bb1320dc86813730bb7c6406ce84b"
920 .to_string(),
921 4,
922 vec![
923 Attribute::new("action", "store_object"),
924 Attribute::new("id", "fe798aa30e560c57d69c46982b2bb1320dc86813730bb7c6406ce84b"
925 .to_string()),
926 Attribute::new("size", "4"),
927 Attribute::new("compressed_size", "4"),
928 Attribute::new("pinned", "false"),
929 ]
930 ),
931 ],
932 ),
933 (
934 HashAlgorithm::Sha256,
935 vec![
936 (
937 obj1_content,
938 true,
939 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
940 .to_string(),
941 5,
942 vec![
943 Attribute::new("action", "store_object"),
944 Attribute::new("id", "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
945 .to_string()),
946 Attribute::new("size", "5"),
947 Attribute::new("compressed_size", "5"),
948 Attribute::new("pinned", "true"),
949 ]
950 ),
951 (
952 obj2_content,
953 false,
954 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6"
955 .to_string(),
956 4,
957 vec![
958 Attribute::new("action", "store_object"),
959 Attribute::new("id", "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6"
960 .to_string()),
961 Attribute::new("size", "4"),
962 Attribute::new("compressed_size", "4"),
963 Attribute::new("pinned", "false"),
964 ]
965 ),
966 ],
967 ),
968 (
969 HashAlgorithm::Sha384,
970 vec![
971 (
972 obj1_content,
973 true,
974 "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f"
975 .to_string(),
976 5,
977 vec![
978 Attribute::new("action", "store_object"),
979 Attribute::new("id", "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f"
980 .to_string()),
981 Attribute::new("size", "5"),
982 Attribute::new("compressed_size", "5"),
983 Attribute::new("pinned", "true"),
984 ]
985 ),
986 (
987 obj2_content,
988 false,
989 "e700b122a81f64ce34ab67c6a815987536a05b0590bbeb32cf5e88963edd8c6e69c9e43b0f957f242d984f09f91bcaf2"
990 .to_string(),
991 4,
992 vec![
993 Attribute::new("action", "store_object"),
994 Attribute::new("id", "e700b122a81f64ce34ab67c6a815987536a05b0590bbeb32cf5e88963edd8c6e69c9e43b0f957f242d984f09f91bcaf2"
995 .to_string()),
996 Attribute::new("size", "4"),
997 Attribute::new("compressed_size", "4"),
998 Attribute::new("pinned", "false"),
999 ]
1000 ),
1001 ],
1002 ),
1003 (
1004 HashAlgorithm::Sha512,
1005 vec![
1006 (
1007 obj1_content,
1008 true,
1009 "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
1010 .to_string(),
1011 5,
1012 vec![
1013 Attribute::new("action", "store_object"),
1014 Attribute::new("id", "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
1015 .to_string()),
1016 Attribute::new("size", "5"),
1017 Attribute::new("compressed_size", "5"),
1018 Attribute::new("pinned", "true"),
1019 ]
1020 ),
1021 (
1022 obj2_content,
1023 false,
1024 "e4f4025e1e28abb473c89bcae03ded972e91b4427e8970be87f645cc34b9b203d633c12760e32c97011439640cba159f60992e10aac8023fa2577cadc1be3b55"
1025 .to_string(),
1026 4,
1027 vec![
1028 Attribute::new("action", "store_object"),
1029 Attribute::new("id", "e4f4025e1e28abb473c89bcae03ded972e91b4427e8970be87f645cc34b9b203d633c12760e32c97011439640cba159f60992e10aac8023fa2577cadc1be3b55"
1030 .to_string()),
1031 Attribute::new("size", "4"),
1032 Attribute::new("compressed_size", "4"),
1033 Attribute::new("pinned", "false"),
1034 ]
1035 ),
1036 ],
1037 ),
1038 ];
1039
1040 for (hash_algorithm, objs) in test_cases {
1041 let mut deps = mock_dependencies();
1042 let info = message_info(&addr(CREATOR), &[]);
1043
1044 instantiate(
1045 deps.as_mut(),
1046 mock_env(),
1047 info.clone(),
1048 InstantiateMsg {
1049 bucket: "test".to_string(),
1050 config: BucketConfig {
1051 hash_algorithm,
1052 ..Default::default()
1053 },
1054 limits: Default::default(),
1055 pagination: Default::default(),
1056 },
1057 )
1058 .unwrap();
1059
1060 for (content, pin, expected_hash, expected_size, expected_attr) in &objs {
1061 let msg = ExecuteMsg::StoreObject {
1062 data: Binary::from_base64(content).unwrap(),
1063 pin: *pin,
1064 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1065 };
1066 let res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1067 assert_eq!(res.attributes, *expected_attr);
1068
1069 assert_eq!(
1070 Binary::from_base64(content).unwrap(),
1071 Binary::from(
1072 DATA.load(&deps.storage, decode_hex(&expected_hash).into())
1073 .unwrap()
1074 ),
1075 );
1076
1077 let created = objects()
1078 .load(&deps.storage, decode_hex(&expected_hash).into())
1079 .unwrap();
1080 assert_eq!(created.id, decode_hex(&expected_hash).into());
1081 assert_eq!(created.owner, info.sender.clone());
1082 assert_eq!(created.size.u128(), *expected_size);
1083 assert_eq!(
1084 created.pin_count,
1085 if *pin {
1086 Uint128::one()
1087 } else {
1088 Uint128::zero()
1089 }
1090 );
1091
1092 assert_eq!(
1093 pins().has(
1094 &deps.storage,
1095 (decode_hex(&expected_hash).into(), info.clone().sender),
1096 ),
1097 *pin,
1098 );
1099 }
1100
1101 let bucket = BUCKET.load(&deps.storage).unwrap();
1102 assert_eq!(
1103 bucket.stat.size.u128(),
1104 objs.iter().map(|x| x.3).sum::<u128>()
1105 );
1106 assert_eq!(
1107 bucket.stat.object_count.u128(),
1108 u128::try_from(objs.len()).unwrap()
1109 );
1110 assert_eq!(
1111 objects()
1112 .keys_raw(&deps.storage, None, None, Order::Ascending)
1113 .count(),
1114 2
1115 );
1116 assert_eq!(
1117 pins()
1118 .keys_raw(&deps.storage, None, None, Order::Ascending)
1119 .count(),
1120 1
1121 );
1122 }
1123 }
1124
1125 #[test]
1126 fn store_object_already_stored() {
1127 let mut deps = mock_dependencies();
1128 let info = message_info(&addr(CREATOR), &[]);
1129 let msg = InstantiateMsg {
1130 bucket: String::from("test"),
1131 config: Default::default(),
1132 limits: Default::default(),
1133 pagination: Default::default(),
1134 };
1135 instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1136
1137 let object = general_purpose::STANDARD.encode("already existing object");
1138 execute(
1139 deps.as_mut(),
1140 mock_env(),
1141 info.clone(),
1142 ExecuteMsg::StoreObject {
1143 data: Binary::from_base64(object.as_str()).unwrap(),
1144 pin: false,
1145 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1146 },
1147 )
1148 .unwrap();
1149
1150 let res = execute(
1151 deps.as_mut(),
1152 mock_env(),
1153 info.clone(),
1154 ExecuteMsg::StoreObject {
1155 data: Binary::from_base64(object.as_str()).unwrap(),
1156 pin: true,
1157 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1158 },
1159 );
1160
1161 assert!(res.is_ok());
1162 assert!(pins().has(
1163 &deps.storage,
1164 (
1165 decode_hex("46c4b2f687df251a98cc83cc35437e9893c16861899c2f9d183e1de57d3a2c0e")
1166 .into(),
1167 info.sender
1168 ),
1169 ));
1170 assert_eq!(
1171 objects()
1172 .load(
1173 &deps.storage,
1174 decode_hex("46c4b2f687df251a98cc83cc35437e9893c16861899c2f9d183e1de57d3a2c0e")
1175 .into()
1176 )
1177 .unwrap()
1178 .pin_count,
1179 Uint128::one()
1180 );
1181 }
1182
1183 #[test]
1184 fn store_object_limits() {
1185 let cases = vec![
1186 (
1187 BucketLimitsBuilder::default()
1188 .max_objects(2u128)
1189 .build()
1190 .unwrap(),
1191 None,
1192 ),
1193 (
1194 BucketLimitsBuilder::default()
1195 .max_object_size(5u128)
1196 .build()
1197 .unwrap(),
1198 None,
1199 ),
1200 (
1201 BucketLimitsBuilder::default()
1202 .max_total_size(9u128)
1203 .build()
1204 .unwrap(),
1205 None,
1206 ),
1207 (
1208 BucketLimitsBuilder::default()
1209 .max_object_pins(1u128)
1210 .build()
1211 .unwrap(),
1212 None,
1213 ),
1214 (
1215 BucketLimitsBuilder::default()
1216 .max_objects(1u128)
1217 .build()
1218 .unwrap(),
1219 Some(ContractError::Bucket(BucketError::MaxObjectsLimitExceeded(
1220 2u128.into(),
1221 1u128.into(),
1222 ))),
1223 ),
1224 (
1225 BucketLimitsBuilder::default()
1226 .max_object_size(4u128)
1227 .build()
1228 .unwrap(),
1229 Some(ContractError::Bucket(
1230 BucketError::MaxObjectSizeLimitExceeded(5u128.into(), 4u128.into()),
1231 )),
1232 ),
1233 (
1234 BucketLimitsBuilder::default()
1235 .max_total_size(8u128)
1236 .build()
1237 .unwrap(),
1238 Some(ContractError::Bucket(
1239 BucketError::MaxTotalSizeLimitExceeded(9u128.into(), 8u128.into()),
1240 )),
1241 ),
1242 (
1243 BucketLimitsBuilder::default()
1244 .max_object_pins(0u128)
1245 .build()
1246 .unwrap(),
1247 Some(ContractError::Bucket(
1248 BucketError::MaxObjectPinsLimitExceeded(1u128.into(), 0u128.into()),
1249 )),
1250 ),
1251 ];
1252
1253 let obj1 = general_purpose::STANDARD.encode("okp4");
1254 let obj2 = general_purpose::STANDARD.encode("hello");
1255
1256 for case in cases {
1257 let mut deps = mock_dependencies();
1258 let info = message_info(&addr(CREATOR), &[]);
1259 let msg = InstantiateMsg {
1260 bucket: String::from("test"),
1261 config: Default::default(),
1262 limits: case.0,
1263 pagination: Default::default(),
1264 };
1265 instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1266
1267 let msg = ExecuteMsg::StoreObject {
1268 data: Binary::from_base64(obj1.as_str()).unwrap(),
1269 pin: false,
1270 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1271 };
1272 execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1273 let msg = ExecuteMsg::StoreObject {
1274 data: Binary::from_base64(obj2.as_str()).unwrap(),
1275 pin: true,
1276 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1277 };
1278 let res = execute(deps.as_mut(), mock_env(), info.clone(), msg);
1279
1280 assert_eq!(res.err(), case.1);
1281 }
1282 }
1283
1284 #[test]
1285 fn store_object_compressed() {
1286 use either::Either;
1287
1288 struct ExpectedCompressionResult {
1289 compression_algorithm: CompressionAlgorithm,
1290 compressed_size: u128,
1291 }
1292 struct TC {
1293 accepted_compression_algorithms: Vec<CompressionAlgorithm>,
1294 compression_algorithm: Option<CompressionAlgorithm>,
1295 expected_result: Either<ContractError, ExpectedCompressionResult>,
1296 }
1297
1298 let cases: Vec<TC> = vec![
1299 TC {
1300 accepted_compression_algorithms: vec![
1301 CompressionAlgorithm::Passthrough,
1302 CompressionAlgorithm::Snappy,
1303 CompressionAlgorithm::Lzma,
1304 ],
1305 compression_algorithm: None,
1306 expected_result: Either::Right(ExpectedCompressionResult {
1307 compression_algorithm: CompressionAlgorithm::Passthrough,
1308 compressed_size: 466,
1309 }),
1310 },
1311 TC {
1312 accepted_compression_algorithms: vec![
1313 CompressionAlgorithm::Passthrough,
1314 CompressionAlgorithm::Snappy,
1315 CompressionAlgorithm::Lzma,
1316 ],
1317 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1318 expected_result: Either::Right(ExpectedCompressionResult {
1319 compression_algorithm: CompressionAlgorithm::Passthrough,
1320 compressed_size: 466,
1321 }),
1322 },
1323 TC {
1324 accepted_compression_algorithms: vec![
1325 CompressionAlgorithm::Passthrough,
1326 CompressionAlgorithm::Snappy,
1327 CompressionAlgorithm::Lzma,
1328 ],
1329 compression_algorithm: Some(CompressionAlgorithm::Snappy),
1330 expected_result: Either::Right(ExpectedCompressionResult {
1331 compression_algorithm: CompressionAlgorithm::Snappy,
1332 compressed_size: 414,
1333 }),
1334 },
1335 TC {
1336 accepted_compression_algorithms: vec![
1337 CompressionAlgorithm::Passthrough,
1338 CompressionAlgorithm::Snappy,
1339 CompressionAlgorithm::Lzma,
1340 ],
1341 compression_algorithm: Some(CompressionAlgorithm::Lzma),
1342 expected_result: Either::Right(ExpectedCompressionResult {
1343 compression_algorithm: CompressionAlgorithm::Lzma,
1344 compressed_size: 344,
1345 }),
1346 },
1347 TC {
1348 accepted_compression_algorithms: vec![CompressionAlgorithm::Passthrough],
1349 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1350 expected_result: Either::Right(ExpectedCompressionResult {
1351 compression_algorithm: CompressionAlgorithm::Passthrough,
1352 compressed_size: 466,
1353 }),
1354 },
1355 TC {
1356 accepted_compression_algorithms: vec![CompressionAlgorithm::Snappy],
1357 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1358 expected_result: Either::Left(ContractError::Bucket(
1359 BucketError::CompressionAlgorithmNotAccepted(
1360 CompressionAlgorithm::Passthrough,
1361 vec![CompressionAlgorithm::Snappy],
1362 ),
1363 )),
1364 },
1365 ];
1366 let data ="In a magical land, there were many realms, one of which was known as OKP4. Within \
1367 this realm, druid programmers possessed the power to create smart contracts. As the kingdom \
1368 grew, the druids used their skills to power decentralized systems, bringing prosperity and \
1369 wonder to all who sought their expertise. And so, the legend of the druid programmers and \
1370 their magical smart contracts lived on, inspiring future generations to unlock the power of \
1371 the digital realm.";
1372 let obj = general_purpose::STANDARD.encode(data);
1373 let obj_id = "25056da0c504e6beb9d8666f9e5919a4a02689f4bceeb4698a21c651f07d8e04";
1374
1375 for case in cases {
1376 let mut deps = mock_dependencies();
1378 let info = message_info(&addr(CREATOR), &[]);
1379 let msg = InstantiateMsg {
1380 bucket: String::from("test"),
1381 config: BucketConfig {
1382 accepted_compression_algorithms: case.accepted_compression_algorithms,
1383 ..Default::default()
1384 },
1385 limits: Default::default(),
1386 pagination: Default::default(),
1387 };
1388 instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1389
1390 let res = execute(
1392 deps.as_mut(),
1393 mock_env(),
1394 info.clone(),
1395 ExecuteMsg::StoreObject {
1396 data: Binary::from_base64(obj.as_str()).unwrap(),
1397 pin: false,
1398 compression_algorithm: case.compression_algorithm,
1399 },
1400 );
1401
1402 match case.expected_result {
1404 Either::Left(err) => assert_eq!(res.err(), Some(err)),
1405 Either::Right(expected) => {
1406 let _to_assert_if_we_want = res.unwrap();
1407 let res_object_info = query::object(deps.as_ref(), obj_id.to_string()).unwrap();
1408 let res_object_data = query::data(deps.as_ref(), obj_id.to_string()).unwrap();
1409
1410 assert_eq!(
1411 res_object_info,
1412 ObjectResponse {
1413 id: obj_id.to_string(),
1414 owner: addr(CREATOR).to_string(),
1415 is_pinned: false,
1416 size: Uint128::from(data.len() as u128),
1417 compressed_size: expected.compressed_size.into(),
1418 compression_algorithm: expected.compression_algorithm,
1419 }
1420 );
1421 assert_eq!(res_object_data, data.as_bytes().to_vec());
1422 }
1423 }
1424 }
1425 }
1426
1427 #[test]
1428 fn store_object_check_attributes() {
1429 let obj_content = &general_purpose::STANDARD.encode("hello");
1430 let obj_exist_content = &general_purpose::STANDARD.encode("axone");
1431 let obj_exist_pinned_content = &general_purpose::STANDARD.encode("protocol");
1432 let obj_large_content = &general_purpose::STANDARD.encode("In a world of interconnected systems, there were countless realms, one of which was known as AXONE. Within this realm, AI researchers harnessed the power to create collaborative AI models. As the realm expanded, the researchers used their expertise to power collaborative systems, bringing innovation and advancement to all who sought their knowledge. And so, the legend of the AI researchers and their collaborative AI models lived on, inspiring future generations to unlock the potential of the digital realm with AI.");
1433
1434 let test_cases = vec![
1435 (
1436 obj_content,
1437 true,
1438 Passthrough,
1439 vec![
1440 Attribute::new("action", "store_object"),
1441 Attribute::new(
1442 "id",
1443 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
1444 .to_string(),
1445 ),
1446 Attribute::new("size", "5"),
1447 Attribute::new("compressed_size", "5"),
1448 Attribute::new("pinned", "true"),
1449 ],
1450 ),
1451 (
1452 obj_content,
1453 false,
1454 Passthrough,
1455 vec![
1456 Attribute::new("action", "store_object"),
1457 Attribute::new(
1458 "id",
1459 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
1460 .to_string(),
1461 ),
1462 Attribute::new("size", "5"),
1463 Attribute::new("compressed_size", "5"),
1464 Attribute::new("pinned", "false"),
1465 ],
1466 ),
1467 (
1468 obj_large_content,
1469 true,
1470 Snappy,
1471 vec![
1472 Attribute::new("action", "store_object"),
1473 Attribute::new(
1474 "id",
1475 "afb9c7804a3515714a3ec2313c990df31d54000b890ae677dcaaa1060b437660"
1476 .to_string(),
1477 ),
1478 Attribute::new("size", "519"),
1479 Attribute::new("compressed_size", "453"),
1480 Attribute::new("pinned", "true"),
1481 ],
1482 ),
1483 (
1484 obj_exist_content,
1485 true,
1486 Passthrough,
1487 vec![
1488 Attribute::new("action", "store_object"),
1489 Attribute::new(
1490 "id",
1491 "45a8243ff863a08531c666569ce9997b63df94c2e2aeedaed3d32656ee1ae622"
1492 .to_string(),
1493 ),
1494 Attribute::new("pinned", "true"),
1495 ],
1496 ),
1497 (
1498 obj_exist_content,
1499 false,
1500 Passthrough,
1501 vec![
1502 Attribute::new("action", "store_object"),
1503 Attribute::new(
1504 "id",
1505 "45a8243ff863a08531c666569ce9997b63df94c2e2aeedaed3d32656ee1ae622"
1506 .to_string(),
1507 ),
1508 Attribute::new("pinned", "false"),
1509 ],
1510 ),
1511 (
1512 obj_exist_pinned_content,
1513 false,
1514 Passthrough,
1515 vec![
1516 Attribute::new("action", "store_object"),
1517 Attribute::new(
1518 "id",
1519 "2ea88c7a30351b12a4dcfc06cdce2af6eab18416176466c2500cb6ef74f745bf"
1520 .to_string(),
1521 ),
1522 Attribute::new("pinned", "false"),
1523 ],
1524 ),
1525 (
1526 obj_exist_pinned_content,
1527 true,
1528 Passthrough,
1529 vec![
1530 Attribute::new("action", "store_object"),
1531 Attribute::new(
1532 "id",
1533 "2ea88c7a30351b12a4dcfc06cdce2af6eab18416176466c2500cb6ef74f745bf"
1534 .to_string(),
1535 ),
1536 Attribute::new("pinned", "false"),
1537 ],
1538 ),
1539 ];
1540
1541 for (content, pin, compression_algorithm, expected_attr) in &test_cases {
1542 let mut deps = mock_dependencies();
1543 let info = message_info(&addr(CREATOR), &[]);
1544 let msg = InstantiateMsg {
1545 bucket: String::from("test"),
1546 config: Default::default(),
1547 limits: Default::default(),
1548 pagination: Default::default(),
1549 };
1550 instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1551
1552 _ = execute(
1553 deps.as_mut(),
1554 mock_env(),
1555 info.clone(),
1556 ExecuteMsg::StoreObject {
1557 data: Binary::from_base64(obj_exist_content).unwrap(),
1558 pin: false,
1559 compression_algorithm: Some(Passthrough),
1560 },
1561 );
1562
1563 _ = execute(
1564 deps.as_mut(),
1565 mock_env(),
1566 info.clone(),
1567 ExecuteMsg::StoreObject {
1568 data: Binary::from_base64(obj_exist_pinned_content).unwrap(),
1569 pin: true,
1570 compression_algorithm: Some(Passthrough),
1571 },
1572 );
1573
1574 let msg = ExecuteMsg::StoreObject {
1575 data: Binary::from_base64(content).unwrap(),
1576 pin: *pin,
1577 compression_algorithm: Some(*compression_algorithm),
1578 };
1579
1580 let res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1581 assert_eq!(res.attributes, *expected_attr);
1582 }
1583 }
1584
1585 #[test]
1586 fn object() {
1587 let mut deps = mock_dependencies();
1588 let info = message_info(&addr(CREATOR), &[]);
1589
1590 let msg = InstantiateMsg {
1591 bucket: String::from("test"),
1592 config: Default::default(),
1593 limits: Default::default(),
1594 pagination: Default::default(),
1595 };
1596 instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1597
1598 match query(
1599 deps.as_ref(),
1600 mock_env(),
1601 QueryMsg::Object {
1602 id: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824".to_string(),
1603 },
1604 )
1605 .err()
1606 .unwrap()
1607 {
1608 NotFound { .. } => (),
1609 _ => panic!("assertion failed"),
1610 }
1611
1612 let data = general_purpose::STANDARD.encode("hello");
1613 let msg = ExecuteMsg::StoreObject {
1614 data: Binary::from_base64(data.as_str()).unwrap(),
1615 pin: true,
1616 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1617 };
1618 execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1619
1620 let msg = QueryMsg::Object {
1621 id: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824".to_string(),
1622 };
1623 let result = query(deps.as_ref(), mock_env(), msg).unwrap();
1624 let response: ObjectResponse = from_json(&result).unwrap();
1625 assert_eq!(
1626 response.id,
1627 ObjectId::from("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
1628 );
1629 assert_eq!(response.owner, info.sender.as_str());
1630 assert!(response.is_pinned);
1631 assert_eq!(response.size.u128(), 5u128);
1632
1633 let data = general_purpose::STANDARD.encode("okp4");
1634 let msg = ExecuteMsg::StoreObject {
1635 data: Binary::from_base64(data.as_str()).unwrap(),
1636 pin: false,
1637 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1638 };
1639 execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1640
1641 let msg = QueryMsg::Object {
1642 id: "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6".to_string(),
1643 };
1644 let result = query(deps.as_ref(), mock_env(), msg).unwrap();
1645 let response: ObjectResponse = from_json(&result).unwrap();
1646 assert_eq!(
1647 response.id,
1648 ObjectId::from("315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6")
1649 );
1650 assert_eq!(response.owner, info.sender.as_str());
1651 assert!(!response.is_pinned);
1652 assert_eq!(response.size.u128(), 4u128);
1653 }
1654
1655 #[test]
1656 fn object_data() {
1657 struct TC {
1658 compression_algorithm: Option<CompressionAlgorithm>,
1659 }
1660
1661 let cases = vec![
1662 TC {
1663 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
1664 },
1665 TC {
1666 compression_algorithm: Some(CompressionAlgorithm::Snappy),
1667 },
1668 TC {
1669 compression_algorithm: Some(CompressionAlgorithm::Lzma),
1670 },
1671 ];
1672
1673 for case in cases {
1674 let mut deps = mock_dependencies();
1675 let info = message_info(&addr(CREATOR), &[]);
1676 let data =
1677 Binary::from_base64(general_purpose::STANDARD.encode("okp4").as_str()).unwrap();
1678
1679 let msg = InstantiateMsg {
1680 bucket: String::from("test"),
1681 config: Default::default(),
1682 limits: Default::default(),
1683 pagination: Default::default(),
1684 };
1685 instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1686
1687 match query(
1688 deps.as_ref(),
1689 mock_env(),
1690 QueryMsg::ObjectData {
1691 id: "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6"
1692 .to_string(),
1693 },
1694 )
1695 .err()
1696 .unwrap()
1697 {
1698 NotFound { .. } => (),
1699 _ => panic!("assertion failed"),
1700 }
1701
1702 let msg = ExecuteMsg::StoreObject {
1703 data: data.clone(),
1704 pin: false,
1705 compression_algorithm: case.compression_algorithm,
1706 };
1707 execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
1708
1709 let msg = QueryMsg::ObjectData {
1710 id: "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6".to_string(),
1711 };
1712 let result = query(deps.as_ref(), mock_env(), msg).unwrap();
1713 assert_eq!(result, to_json_binary(&data).unwrap());
1714 }
1715 }
1716
1717 #[test]
1718 fn object_data_error() {
1719 let mut deps = mock_dependencies();
1720 let id: Hash = vec![1, 2, 3].into();
1721 let data = &vec![255, 255, 0];
1722
1723 let object = &Object {
1724 id: id.clone(),
1725 owner: Addr::unchecked("john"),
1726 size: 42u8.into(),
1727 pin_count: Uint128::one(),
1728 compression: compress::CompressionAlgorithm::Lzma,
1729 compressed_size: Uint128::from(data.len() as u128),
1730 };
1731
1732 objects()
1733 .save(deps.as_mut().storage, object.id.clone(), object)
1734 .expect("no error when storing object");
1735 let data_path = DATA.key(id.clone());
1736 data_path
1737 .save(deps.as_mut().storage, &data)
1738 .expect("no error when storing data");
1739
1740 let msg = QueryMsg::ObjectData { id: id.to_string() };
1741
1742 let result = query(deps.as_ref(), mock_env(), msg);
1743 assert_eq!(
1744 result,
1745 Err(StdError::serialize_err(
1746 "Lzma",
1747 "lzma error: LZMA header invalid properties: 255 must be < 225"
1748 ))
1749 );
1750 }
1751
1752 #[test]
1753 fn pin_object() {
1754 struct TC {
1755 objects: Vec<ObjectId>,
1756 senders: Vec<MessageInfo>,
1757 expected_count: usize,
1758 expected_error: Option<ContractError>,
1759 expected_object_pin_count: Vec<(ObjectId, Uint128)>,
1760 }
1761
1762 let cases = vec![
1763 TC {
1764 objects: vec![ObjectId::from(
1766 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1767 )],
1768 senders: vec![message_info(&addr("bob"), &[])],
1769 expected_count: 1,
1770 expected_error: None,
1771 expected_object_pin_count: vec![(
1772 ObjectId::from(
1773 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1774 ),
1775 Uint128::one(),
1776 )],
1777 },
1778 TC {
1779 objects: vec![
1781 ObjectId::from(
1782 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1783 ),
1784 ObjectId::from(
1785 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1786 ),
1787 ],
1788 senders: vec![
1789 message_info(&addr("bob"), &[]),
1790 message_info(&addr("alice"), &[]),
1791 ],
1792 expected_count: 2,
1793 expected_error: None,
1794 expected_object_pin_count: vec![(
1795 ObjectId::from(
1796 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1797 ),
1798 Uint128::new(2),
1799 )],
1800 },
1801 TC {
1802 objects: vec![
1804 ObjectId::from(
1805 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1806 ),
1807 ObjectId::from(
1808 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1809 ),
1810 ],
1811 senders: vec![
1812 message_info(&addr("bob"), &[]),
1813 message_info(&addr("bob"), &[]),
1814 ],
1815 expected_count: 1,
1816 expected_error: None,
1817 expected_object_pin_count: vec![(
1818 ObjectId::from(
1819 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1820 ),
1821 Uint128::one(),
1822 )],
1823 },
1824 TC {
1825 objects: vec![
1827 ObjectId::from(
1828 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1829 ),
1830 ObjectId::from(
1831 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1832 ),
1833 ],
1834 senders: vec![
1835 message_info(&addr("bob"), &[]),
1836 message_info(&addr("bob"), &[]),
1837 ],
1838 expected_count: 2,
1839 expected_error: None,
1840 expected_object_pin_count: vec![
1841 (
1842 ObjectId::from(
1843 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1844 ),
1845 Uint128::one(),
1846 ),
1847 (
1848 ObjectId::from(
1849 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1850 ),
1851 Uint128::one(),
1852 ),
1853 ],
1854 },
1855 TC {
1856 objects: vec![
1858 ObjectId::from(
1859 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1860 ),
1861 ObjectId::from(
1862 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1863 ),
1864 ],
1865 senders: vec![
1866 message_info(&addr("bob"), &[]),
1867 message_info(&addr("alice"), &[]),
1868 ],
1869 expected_count: 2,
1870 expected_error: None,
1871 expected_object_pin_count: vec![
1872 (
1873 ObjectId::from(
1874 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1875 ),
1876 Uint128::one(),
1877 ),
1878 (
1879 ObjectId::from(
1880 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1881 ),
1882 Uint128::one(),
1883 ),
1884 ],
1885 },
1886 TC {
1887 objects: vec![
1889 ObjectId::from(
1890 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1891 ),
1892 ObjectId::from(
1893 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1894 ),
1895 ObjectId::from(
1896 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1897 ),
1898 ],
1899 senders: vec![
1900 message_info(&addr("bob"), &[]),
1901 message_info(&addr("alice"), &[]),
1902 message_info(&addr("alice"), &[]),
1903 ],
1904 expected_count: 2,
1905 expected_error: None,
1906 expected_object_pin_count: vec![
1907 (
1908 ObjectId::from(
1909 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1910 ),
1911 Uint128::one(),
1912 ),
1913 (
1914 ObjectId::from(
1915 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1916 ),
1917 Uint128::one(),
1918 ),
1919 ],
1920 },
1921 TC {
1922 objects: vec![
1924 ObjectId::from(
1925 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1926 ),
1927 ObjectId::from(
1928 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1929 ),
1930 ObjectId::from(
1931 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1932 ),
1933 ObjectId::from(
1934 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1935 ),
1936 ],
1937 senders: vec![
1938 message_info(&addr("bob"), &[]),
1939 message_info(&addr("alice"), &[]),
1940 message_info(&addr("martin"), &[]),
1941 message_info(&addr("pierre"), &[]),
1942 ],
1943 expected_count: 3,
1944 expected_error: Some(ContractError::Bucket(
1945 BucketError::MaxObjectPinsLimitExceeded(Uint128::new(3), Uint128::new(2)),
1946 )),
1947 expected_object_pin_count: vec![
1948 (
1949 ObjectId::from(
1950 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1951 ),
1952 Uint128::one(),
1953 ),
1954 (
1955 ObjectId::from(
1956 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
1957 ),
1958 Uint128::new(2),
1959 ),
1960 ],
1961 },
1962 TC {
1963 objects: vec![ObjectId::from(
1965 "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56",
1966 )],
1967 senders: vec![message_info(&addr("bob"), &[])],
1968 expected_count: 0,
1969 expected_error: Some(ContractError::Std(StdError::not_found(
1970 not_found_object_info::<Object>(
1971 "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56",
1972 ),
1973 ))),
1974 expected_object_pin_count: vec![(
1975 ObjectId::from(
1976 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1977 ),
1978 Uint128::zero(),
1979 )],
1980 },
1981 TC {
1982 objects: vec![ObjectId::from("invalid id")],
1984 senders: vec![message_info(&addr("bob"), &[])],
1985 expected_count: 0,
1986 expected_error: Some(ContractError::Std(StdError::parse_err(
1987 type_name::<Vec<u8>>(),
1988 "invalid Base16 encoding".to_string(),
1989 ))),
1990 expected_object_pin_count: vec![(
1991 ObjectId::from(
1992 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
1993 ),
1994 Uint128::zero(),
1995 )],
1996 },
1997 ];
1998
1999 for case in cases {
2000 let mut deps = mock_dependencies();
2001 let info = message_info(&addr(CREATOR), &[]);
2002
2003 instantiate(
2004 deps.as_mut(),
2005 mock_env(),
2006 info.clone(),
2007 InstantiateMsg {
2008 bucket: "test".to_string(),
2009 config: Default::default(),
2010 limits: BucketLimitsBuilder::default()
2011 .max_object_pins(Uint128::new(2))
2012 .build()
2013 .unwrap(),
2014 pagination: Default::default(),
2015 },
2016 )
2017 .unwrap();
2018
2019 let data = general_purpose::STANDARD.encode("okp4");
2020 let msg = ExecuteMsg::StoreObject {
2021 data: Binary::from_base64(data.as_str()).unwrap(),
2022 pin: false,
2023 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2024 };
2025 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2026
2027 let data = general_purpose::STANDARD.encode("data");
2028 let msg = ExecuteMsg::StoreObject {
2029 data: Binary::from_base64(data.as_str()).unwrap(),
2030 pin: false,
2031 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2032 };
2033 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2034
2035 let data = general_purpose::STANDARD.encode("hello");
2036 let msg = ExecuteMsg::StoreObject {
2037 data: Binary::from_base64(data.as_str()).unwrap(),
2038 pin: false,
2039 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2040 };
2041 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2042
2043 let mut last_result: Option<Result<Response, ContractError>> = None;
2044 case.objects
2045 .iter()
2046 .zip(case.senders)
2047 .for_each(|(object_id, info)| {
2048 last_result = Some(execute(
2049 deps.as_mut(),
2050 mock_env(),
2051 info,
2052 ExecuteMsg::PinObject {
2053 id: object_id.clone(),
2054 },
2055 ));
2056 });
2057
2058 match case.expected_error {
2059 Some(err) => assert_eq!(last_result.unwrap().unwrap_err(), err),
2060 _ => {
2061 assert_eq!(
2062 pins()
2063 .keys_raw(&deps.storage, None, None, Order::Ascending)
2064 .count(),
2065 case.expected_count
2066 );
2067 for (object_id, count) in case.expected_object_pin_count {
2068 assert_eq!(
2069 objects()
2070 .load(&deps.storage, decode_hex(&object_id).into())
2071 .unwrap()
2072 .pin_count,
2073 count
2074 );
2075 }
2076 }
2077 }
2078 }
2079 }
2080
2081 #[test]
2082 fn unpin_object() {
2083 struct TC {
2084 pin: Vec<ObjectId>,
2085 pin_senders: Vec<MessageInfo>,
2086 unpin: Vec<ObjectId>,
2087 unpin_senders: Vec<MessageInfo>,
2088 expected_count: usize,
2089 expected_error: Option<ContractError>,
2090 expected_object_pin_count: Vec<(ObjectId, Uint128)>,
2091 }
2092
2093 let cases = vec![
2094 TC {
2095 pin: vec![ObjectId::from(
2096 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2097 )],
2098 pin_senders: vec![message_info(&addr("bob"), &[])],
2099 unpin: vec![ObjectId::from(
2100 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2101 )],
2102 unpin_senders: vec![message_info(&addr("bob"), &[])],
2103 expected_count: 0,
2104 expected_error: None,
2105 expected_object_pin_count: vec![(
2106 ObjectId::from(
2107 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2108 ),
2109 Uint128::zero(),
2110 )],
2111 },
2112 TC {
2113 pin: vec![ObjectId::from(
2114 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2115 )],
2116 pin_senders: vec![message_info(&addr("bob"), &[])],
2117 unpin: vec![ObjectId::from(
2118 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2119 )],
2120 unpin_senders: vec![message_info(&addr("bob"), &[])],
2121 expected_count: 1,
2122 expected_error: None,
2123 expected_object_pin_count: vec![
2124 (
2125 ObjectId::from(
2126 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2127 ),
2128 Uint128::one(),
2129 ),
2130 (
2131 ObjectId::from(
2132 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2133 ),
2134 Uint128::zero(),
2135 ),
2136 ],
2137 },
2138 TC {
2139 pin: vec![
2140 ObjectId::from(
2141 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2142 ),
2143 ObjectId::from(
2144 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2145 ),
2146 ],
2147 pin_senders: vec![
2148 message_info(&addr("bob"), &[]),
2149 message_info(&addr("bob"), &[]),
2150 ],
2151 unpin: vec![ObjectId::from(
2152 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2153 )],
2154 unpin_senders: vec![message_info(&addr("bob"), &[])],
2155 expected_count: 1,
2156 expected_error: None,
2157 expected_object_pin_count: vec![
2158 (
2159 ObjectId::from(
2160 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2161 ),
2162 Uint128::zero(),
2163 ),
2164 (
2165 ObjectId::from(
2166 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2167 ),
2168 Uint128::one(),
2169 ),
2170 ],
2171 },
2172 TC {
2173 pin: vec![],
2174 pin_senders: vec![],
2175 unpin: vec![ObjectId::from(
2176 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2177 )],
2178 unpin_senders: vec![message_info(&addr("bob"), &[])],
2179 expected_count: 0,
2180 expected_error: None,
2181 expected_object_pin_count: vec![(
2182 ObjectId::from(
2183 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2184 ),
2185 Uint128::zero(),
2186 )],
2187 },
2188 TC {
2189 pin: vec![
2190 ObjectId::from(
2191 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2192 ),
2193 ObjectId::from(
2194 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2195 ),
2196 ObjectId::from(
2197 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2198 ),
2199 ObjectId::from(
2200 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2201 ),
2202 ],
2203 pin_senders: vec![
2204 message_info(&addr("bob"), &[]),
2205 message_info(&addr("alice"), &[]),
2206 message_info(&addr("martin"), &[]),
2207 message_info(&addr("pierre"), &[]),
2208 ],
2209 unpin: vec![ObjectId::from(
2210 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2211 )],
2212 unpin_senders: vec![message_info(&addr("martin"), &[])],
2213 expected_count: 3,
2214 expected_error: None,
2215 expected_object_pin_count: vec![
2216 (
2217 ObjectId::from(
2218 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2219 ),
2220 Uint128::one(),
2221 ),
2222 (
2223 ObjectId::from(
2224 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2225 ),
2226 Uint128::new(2),
2227 ),
2228 ],
2229 },
2230 TC {
2231 pin: vec![ObjectId::from(
2233 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2234 )],
2235 pin_senders: vec![message_info(&addr("bob"), &[])],
2236 unpin: vec![ObjectId::from(
2237 "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56",
2238 )],
2239 unpin_senders: vec![message_info(&addr("martin"), &[])],
2240 expected_count: 1,
2241 expected_error: Some(ContractError::Std(StdError::not_found(
2242 not_found_object_info::<Object>(
2243 "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56",
2244 ),
2245 ))),
2246 expected_object_pin_count: vec![(
2247 ObjectId::from(
2248 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2249 ),
2250 Uint128::one(),
2251 )],
2252 },
2253 TC {
2254 pin: vec![ObjectId::from(
2256 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2257 )],
2258 pin_senders: vec![message_info(&addr("bob"), &[])],
2259 unpin: vec![ObjectId::from("invalid id")],
2260 unpin_senders: vec![message_info(&addr("martin"), &[])],
2261 expected_count: 1,
2262 expected_error: Some(ContractError::Std(StdError::parse_err(
2263 type_name::<Vec<u8>>(),
2264 "invalid Base16 encoding".to_string(),
2265 ))),
2266 expected_object_pin_count: vec![(
2267 ObjectId::from(
2268 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2269 ),
2270 Uint128::one(),
2271 )],
2272 },
2273 ];
2274
2275 for case in cases {
2276 let mut deps = mock_dependencies();
2277 let info = message_info(&addr(CREATOR), &[]);
2278
2279 instantiate(
2280 deps.as_mut(),
2281 mock_env(),
2282 info.clone(),
2283 InstantiateMsg {
2284 bucket: "test".to_string(),
2285 config: Default::default(),
2286 limits: Default::default(),
2287 pagination: Default::default(),
2288 },
2289 )
2290 .unwrap();
2291
2292 let data = general_purpose::STANDARD.encode("okp4");
2293 let msg = ExecuteMsg::StoreObject {
2294 data: Binary::from_base64(data.as_str()).unwrap(),
2295 pin: false,
2296 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2297 };
2298 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2299
2300 let data = general_purpose::STANDARD.encode("data");
2301 let msg = ExecuteMsg::StoreObject {
2302 data: Binary::from_base64(data.as_str()).unwrap(),
2303 pin: false,
2304 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2305 };
2306 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2307
2308 let data = general_purpose::STANDARD.encode("hello");
2309 let msg = ExecuteMsg::StoreObject {
2310 data: Binary::from_base64(data.as_str()).unwrap(),
2311 pin: false,
2312 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2313 };
2314 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2315
2316 let mut last_result: Option<Result<Response, ContractError>> = None;
2317 case.pin
2318 .iter()
2319 .zip(case.pin_senders)
2320 .for_each(|(object_id, info)| {
2321 last_result = Some(execute(
2322 deps.as_mut(),
2323 mock_env(),
2324 info,
2325 ExecuteMsg::PinObject {
2326 id: object_id.clone(),
2327 },
2328 ));
2329 });
2330 case.unpin
2331 .iter()
2332 .zip(case.unpin_senders)
2333 .for_each(|(object_id, info)| {
2334 last_result = Some(execute(
2335 deps.as_mut(),
2336 mock_env(),
2337 info,
2338 ExecuteMsg::UnpinObject {
2339 id: object_id.clone(),
2340 },
2341 ));
2342 });
2343
2344 match case.expected_error {
2345 Some(err) => assert_eq!(last_result.unwrap().unwrap_err(), err),
2346 _ => {
2347 assert_eq!(
2348 pins()
2349 .keys_raw(&deps.storage, None, None, Order::Ascending)
2350 .count(),
2351 case.expected_count
2352 );
2353 for (object_id, count) in case.expected_object_pin_count {
2354 assert_eq!(
2355 objects()
2356 .load(&deps.storage, decode_hex(&object_id).into())
2357 .unwrap()
2358 .pin_count,
2359 count
2360 );
2361 }
2362 }
2363 }
2364 }
2365 }
2366
2367 #[test]
2368 fn fetch_objects() {
2369 let mut deps = mock_dependencies();
2370 let info1 = message_info(&addr("creator1"), &[]);
2371 let info2 = message_info(&addr("creator2"), &[]);
2372
2373 let msg = InstantiateMsg {
2374 bucket: String::from("test"),
2375 config: Default::default(),
2376 limits: Default::default(),
2377 pagination: Default::default(),
2378 };
2379 instantiate(deps.as_mut(), mock_env(), info1.clone(), msg).unwrap();
2380
2381 let msg = QueryMsg::Objects {
2382 address: None,
2383 first: None,
2384 after: None,
2385 };
2386 let result = query(deps.as_ref(), mock_env(), msg).unwrap();
2387 let response: ObjectsResponse = from_json(&result).unwrap();
2388 assert_eq!(response.data.len(), 0);
2389 assert_eq!(
2390 response.page_info,
2391 PageInfo {
2392 has_next_page: false,
2393 cursor: "".to_string(),
2394 }
2395 );
2396
2397 let data = general_purpose::STANDARD.encode("object1");
2398 let msg = ExecuteMsg::StoreObject {
2399 data: Binary::from_base64(data.as_str()).unwrap(),
2400 pin: false,
2401 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2402 };
2403 execute(deps.as_mut(), mock_env(), info1.clone(), msg).unwrap();
2404 let data = general_purpose::STANDARD.encode("object2");
2405 let msg = ExecuteMsg::StoreObject {
2406 data: Binary::from_base64(data.as_str()).unwrap(),
2407 pin: false,
2408 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2409 };
2410 execute(deps.as_mut(), mock_env(), info1, msg).unwrap();
2411 let data = general_purpose::STANDARD.encode("object3");
2412 let msg = ExecuteMsg::StoreObject {
2413 data: Binary::from_base64(data.as_str()).unwrap(),
2414 pin: false,
2415 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2416 };
2417 execute(deps.as_mut(), mock_env(), info2, msg).unwrap();
2418
2419 let cases = vec![
2420 (
2421 QueryMsg::Objects {
2422 address: None,
2423 first: None,
2424 after: None,
2425 },
2426 3,
2427 PageInfo {
2428 has_next_page: false,
2429 cursor: "CZC4Avd5xNeJaBkK6MYrA1ZSQPNr76GU1k2JJSjmaDyF".to_string(),
2430 },
2431 ),
2432 (
2433 QueryMsg::Objects {
2434 address: Some(addr("unknown").to_string()),
2435 first: None,
2436 after: None,
2437 },
2438 0,
2439 PageInfo {
2440 has_next_page: false,
2441 cursor: "".to_string(),
2442 },
2443 ),
2444 (
2445 QueryMsg::Objects {
2446 address: Some(addr("creator1").to_string()),
2447 first: None,
2448 after: None,
2449 },
2450 2,
2451 PageInfo {
2452 has_next_page: false,
2453 cursor: "CZC4Avd5xNeJaBkK6MYrA1ZSQPNr76GU1k2JJSjmaDyF".to_string(),
2454 },
2455 ),
2456 (
2457 QueryMsg::Objects {
2458 address: Some(addr("creator1").to_string()),
2459 first: Some(1),
2460 after: None,
2461 },
2462 1,
2463 PageInfo {
2464 has_next_page: true,
2465 cursor: "5bfWM6UF5MowkQVp16q5pnXvwc9SVkS4xZkFeVLdswjU".to_string(),
2466 },
2467 ),
2468 (
2469 QueryMsg::Objects {
2470 address: Some(addr("creator1").to_string()),
2471 first: Some(1),
2472 after: Some("5bfWM6UF5MowkQVp16q5pnXvwc9SVkS4xZkFeVLdswjU".to_string()),
2473 },
2474 1,
2475 PageInfo {
2476 has_next_page: false,
2477 cursor: "CZC4Avd5xNeJaBkK6MYrA1ZSQPNr76GU1k2JJSjmaDyF".to_string(),
2478 },
2479 ),
2480 ];
2481
2482 for case in cases {
2483 let msg = case.0;
2484 let result = query(deps.as_ref(), mock_env(), msg).unwrap();
2485 let response: ObjectsResponse = from_json(&result).unwrap();
2486 assert_eq!(response.data.len(), case.1);
2487 assert_eq!(response.page_info, case.2);
2488 }
2489
2490 let msg = QueryMsg::Objects {
2491 address: Some(addr("creator2").to_string()),
2492 first: None,
2493 after: None,
2494 };
2495 let result = query(deps.as_ref(), mock_env(), msg).unwrap();
2496 let response: ObjectsResponse = from_json(&result).unwrap();
2497 assert_eq!(
2498 response.data.first().unwrap(),
2499 &ObjectResponse {
2500 id: "0a6d95579ba3dd2f79c870906fd894007ce449020d111d358894cfbbcd9a03a4".to_string(),
2501 owner: addr("creator2").to_string(),
2502 is_pinned: false,
2503 size: 7u128.into(),
2504 compressed_size: 7u128.into(),
2505 compression_algorithm: CompressionAlgorithm::Passthrough,
2506 }
2507 );
2508 }
2509
2510 #[test]
2511 fn object_pins() {
2512 let mut deps = mock_dependencies();
2513 let info1 = message_info(&addr("creator1"), &[]);
2514 let info2 = message_info(&addr("creator2"), &[]);
2515
2516 let msg = InstantiateMsg {
2517 bucket: String::from("test"),
2518 config: Default::default(),
2519 limits: Default::default(),
2520 pagination: Default::default(),
2521 };
2522 instantiate(deps.as_mut(), mock_env(), info1.clone(), msg).unwrap();
2523
2524 let data = general_purpose::STANDARD.encode("object1");
2525 let msg = ExecuteMsg::StoreObject {
2526 data: Binary::from_base64(data.as_str()).unwrap(),
2527 pin: false,
2528 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2529 };
2530 execute(deps.as_mut(), mock_env(), info1.clone(), msg).unwrap();
2531 let data = general_purpose::STANDARD.encode("object2");
2533 let msg = ExecuteMsg::StoreObject {
2534 data: Binary::from_base64(data.as_str()).unwrap(),
2535 pin: true,
2536 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2537 };
2538 execute(deps.as_mut(), mock_env(), info2, msg).unwrap();
2539 let msg = ExecuteMsg::PinObject {
2541 id: "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56".to_string(),
2542 };
2543 execute(deps.as_mut(), mock_env(), info1, msg).unwrap();
2544
2545 let cases = vec![
2546 (
2547 QueryMsg::ObjectPins {
2548 id: "445008b7f2932922bdb184771d9978516a4f89d77000c2d6eab18b0894aac3a7"
2549 .to_string(),
2550 first: None,
2551 after: None,
2552 },
2553 Vec::<Addr>::new(),
2554 PageInfo {
2555 has_next_page: false,
2556 cursor: "".to_string(),
2557 },
2558 ),
2559 (
2560 QueryMsg::ObjectPins {
2561 id: "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56"
2562 .to_string(),
2563 first: None,
2564 after: None,
2565 },
2566 vec![addr("creator2"), addr("creator1")],
2567 PageInfo {
2568 has_next_page: false,
2569 cursor: "3wwsaS9LwRtjTvYihWjb64ph5BdBrkTrt8f2sG6vBiE774tMxX31Rs9Mqn91NGQ6MzUow4Gi5iBR6STw68tuG2esLAdK".to_string(),
2570 },
2571 ),
2572 (
2573 QueryMsg::ObjectPins {
2574 id: "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56"
2575 .to_string(),
2576 first: Some(1),
2577 after: None,
2578 },
2579 vec![addr("creator2")],
2580 PageInfo {
2581 has_next_page: true,
2582 cursor: "3wwsaS9LwRtjTrrGpg2qSGEo3gFkR53R2hUNX2PapfytooQuGcPiyF4zRneMECgJiZXS336Cd7pZU4CJ96nt3mcoQR8g".to_string(),
2583 },
2584 ),
2585 (
2586 QueryMsg::ObjectPins {
2587 id: "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56"
2588 .to_string(),
2589 first: Some(1),
2590 after: Some("3wwsaS9LwRtjTrrGpg2qSGEo3gFkR53R2hUNX2PapfytooQuGcPiyF4zRneMECgJiZXS336Cd7pZU4CJ96nt3mcoQR8g".to_string()),
2591 },
2592 vec![addr("creator1")],
2593 PageInfo {
2594 has_next_page: false,
2595 cursor: "3wwsaS9LwRtjTvYihWjb64ph5BdBrkTrt8f2sG6vBiE774tMxX31Rs9Mqn91NGQ6MzUow4Gi5iBR6STw68tuG2esLAdK".to_string(),
2596 },
2597 ),
2598 ];
2599
2600 for case in cases {
2601 let result = query(deps.as_ref(), mock_env_addr(), case.0).unwrap();
2602 let response: ObjectPinsResponse = from_json(&result).unwrap();
2603 assert_eq!(
2604 response
2605 .data
2606 .iter()
2607 .map(|a| Addr::unchecked(a))
2608 .collect::<Vec<Addr>>(),
2609 case.1
2610 );
2611 assert_eq!(response.page_info, case.2);
2612 }
2613 }
2614
2615 #[test]
2616 fn object_pins_errors() {
2617 let mut deps = mock_dependencies();
2618
2619 let msg = InstantiateMsg {
2620 bucket: String::from("test"),
2621 config: Default::default(),
2622 limits: Default::default(),
2623 pagination: Default::default(),
2624 };
2625 instantiate(
2626 deps.as_mut(),
2627 mock_env(),
2628 message_info(&addr("creator1"), &[]),
2629 msg,
2630 )
2631 .unwrap();
2632
2633 let cases = vec![
2634 (
2635 QueryMsg::ObjectPins {
2636 id: "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56"
2637 .to_string(),
2638 after: None,
2639 first: None,
2640 },
2641 StdError::not_found(not_found_object_info::<Object>(
2642 "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56",
2643 )),
2644 ),
2645 (
2646 QueryMsg::ObjectPins {
2647 id: "invalid id".to_string(),
2648 after: None,
2649 first: None,
2650 },
2651 StdError::parse_err(
2652 type_name::<Vec<u8>>(),
2653 "invalid Base16 encoding".to_string(),
2654 ),
2655 ),
2656 ];
2657
2658 for case in cases {
2659 let res = query(deps.as_ref(), mock_env(), case.0).err().unwrap();
2660 assert_eq!(res, case.1)
2661 }
2662 }
2663
2664 #[test]
2665 fn forget_object() {
2666 struct TC {
2667 pins: Vec<ObjectId>,
2668 pins_senders: Vec<MessageInfo>,
2669 forget_objects: Vec<ObjectId>,
2670 forget_senders: Vec<MessageInfo>,
2671 expected_count: usize,
2672 expected_total_size: Uint128,
2673 expected_compressed_size: Uint128,
2674 expected_error: Option<ContractError>,
2675 }
2676
2677 let cases = vec![
2678 TC {
2679 pins: vec![],
2680 pins_senders: vec![],
2681 forget_objects: vec![ObjectId::from(
2682 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2683 )],
2684 forget_senders: vec![message_info(&addr("bob"), &[])],
2685 expected_count: 3,
2686 expected_total_size: Uint128::new(474),
2687 expected_compressed_size: Uint128::new(418),
2688 expected_error: None,
2689 },
2690 TC {
2691 pins: vec![],
2692 pins_senders: vec![],
2693 forget_objects: vec![
2694 ObjectId::from(
2695 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2696 ),
2697 ObjectId::from(
2698 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
2699 ),
2700 ],
2701 forget_senders: vec![
2702 message_info(&addr("bob"), &[]),
2703 message_info(&addr("bob"), &[]),
2704 ],
2705 expected_count: 2,
2706 expected_total_size: Uint128::new(469),
2707 expected_compressed_size: Uint128::new(413),
2708 expected_error: None,
2709 },
2710 TC {
2711 pins: vec![],
2712 pins_senders: vec![],
2713 forget_objects: vec![ObjectId::from(
2714 "d1abcabb14dd23d2cf60472dffb4823be10ac20148e8ef7b9644cc14fcf8a073",
2715 )],
2716 forget_senders: vec![
2717 message_info(&addr("bob"), &[]),
2718 message_info(&addr("bob"), &[]),
2719 ],
2720 expected_count: 3,
2721 expected_total_size: Uint128::new(13),
2722 expected_compressed_size: Uint128::new(13),
2723 expected_error: None,
2724 },
2725 TC {
2726 pins: vec![ObjectId::from(
2727 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2728 )],
2729 pins_senders: vec![message_info(&addr("bob"), &[])],
2730 forget_objects: vec![ObjectId::from(
2731 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2732 )],
2733 forget_senders: vec![message_info(&addr("alice"), &[])], expected_count: 4,
2735 expected_total_size: Uint128::new(478),
2736 expected_compressed_size: Uint128::new(422),
2737 expected_error: Some(ContractError::ObjectPinned {}),
2738 },
2739 TC {
2740 pins: vec![ObjectId::from(
2741 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2742 )],
2743 pins_senders: vec![message_info(&addr("bob"), &[])],
2744 forget_objects: vec![ObjectId::from(
2745 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2746 )],
2747 forget_senders: vec![message_info(&addr("bob"), &[])], expected_count: 3,
2749 expected_total_size: Uint128::new(474),
2750 expected_compressed_size: Uint128::new(418),
2751 expected_error: None,
2752 },
2753 TC {
2754 pins: vec![
2755 ObjectId::from(
2756 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2757 ),
2758 ObjectId::from(
2759 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2760 ),
2761 ],
2762 pins_senders: vec![
2763 message_info(&addr("bob"), &[]),
2764 message_info(&addr("alice"), &[]),
2765 ],
2766 forget_objects: vec![ObjectId::from(
2767 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2768 )],
2769 forget_senders: vec![message_info(&addr("bob"), &[])], expected_count: 4,
2771 expected_total_size: Uint128::new(478),
2772 expected_compressed_size: Uint128::new(422),
2773 expected_error: Some(ContractError::ObjectPinned {}),
2774 },
2775 TC {
2776 pins: vec![
2777 ObjectId::from(
2778 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2779 ),
2780 ObjectId::from(
2781 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2782 ),
2783 ],
2784 pins_senders: vec![
2785 message_info(&addr("bob"), &[]),
2786 message_info(&addr("alice"), &[]),
2787 ],
2788 forget_objects: vec![ObjectId::from(
2789 "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56",
2790 )],
2791 forget_senders: vec![message_info(&addr("bob"), &[])], expected_count: 4,
2793 expected_total_size: Uint128::new(478),
2794 expected_compressed_size: Uint128::new(422),
2795 expected_error: Some(ContractError::Std(StdError::not_found(
2796 not_found_object_info::<Object>(
2797 "abafa4428bdc8c34dae28bbc17303a62175f274edf59757b3e9898215a428a56",
2798 ),
2799 ))),
2800 },
2801 TC {
2802 pins: vec![
2803 ObjectId::from(
2804 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2805 ),
2806 ObjectId::from(
2807 "315d0d9ab12c5f8884100055f79de50b72db4bd2c9bfd3df049d89640fed1fa6",
2808 ),
2809 ],
2810 pins_senders: vec![
2811 message_info(&addr("bob"), &[]),
2812 message_info(&addr("alice"), &[]),
2813 ],
2814 forget_objects: vec![ObjectId::from("invalid id")],
2815 forget_senders: vec![message_info(&addr("bob"), &[])], expected_count: 4,
2817 expected_total_size: Uint128::new(478),
2818 expected_compressed_size: Uint128::new(422),
2819 expected_error: Some(ContractError::Std(StdError::parse_err(
2820 type_name::<Vec<u8>>(),
2821 "invalid Base16 encoding".to_string(),
2822 ))),
2823 },
2824 ];
2825
2826 for case in cases {
2827 let mut deps = mock_dependencies();
2828 let info = message_info(&addr(CREATOR), &[]);
2829
2830 instantiate(
2831 deps.as_mut(),
2832 mock_env(),
2833 info.clone(),
2834 InstantiateMsg {
2835 bucket: "test".to_string(),
2836 config: Default::default(),
2837 limits: Default::default(),
2838 pagination: Default::default(),
2839 },
2840 )
2841 .unwrap();
2842
2843 let data = general_purpose::STANDARD.encode("okp4");
2844 let msg = ExecuteMsg::StoreObject {
2845 data: Binary::from_base64(data.as_str()).unwrap(),
2846 pin: false,
2847 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2848 };
2849 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2850
2851 let data = general_purpose::STANDARD.encode("data");
2852 let msg = ExecuteMsg::StoreObject {
2853 data: Binary::from_base64(data.as_str()).unwrap(),
2854 pin: false,
2855 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2856 };
2857 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2858
2859 let data = general_purpose::STANDARD.encode("hello");
2860 let msg = ExecuteMsg::StoreObject {
2861 data: Binary::from_base64(data.as_str()).unwrap(),
2862 pin: false,
2863 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2864 };
2865 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2866
2867 let data = general_purpose::STANDARD.encode(
2868 "In a magical land, there \
2869 were many realms, one of which was known as OKP4. Within this realm, druid programmers \
2870 possessed the power to create smart contracts. As the kingdom grew, the druids used \
2871 their skills to power decentralized systems, bringing prosperity and wonder to all who \
2872 sought their expertise. And so, the legend of the druid programmers and their magical \
2873 smart contracts lived on, inspiring future generations to unlock the power of the \
2874 digital realm.",
2875 );
2876 let msg = ExecuteMsg::StoreObject {
2877 data: Binary::from_base64(data.as_str()).unwrap(),
2878 pin: false,
2879 compression_algorithm: Some(CompressionAlgorithm::Snappy),
2880 };
2881 let _ = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
2882
2883 case.pins
2884 .iter()
2885 .zip(case.pins_senders)
2886 .for_each(|(object_id, info)| {
2887 _ = execute(
2888 deps.as_mut(),
2889 mock_env(),
2890 info,
2891 ExecuteMsg::PinObject {
2892 id: object_id.clone(),
2893 },
2894 );
2895 });
2896
2897 let mut last_result: Option<Result<Response, ContractError>> = None;
2898
2899 case.forget_objects
2900 .iter()
2901 .zip(case.forget_senders)
2902 .for_each(|(object_id, info)| {
2903 last_result = Some(execute(
2904 deps.as_mut(),
2905 mock_env(),
2906 info,
2907 ExecuteMsg::ForgetObject {
2908 id: object_id.clone(),
2909 },
2910 ));
2911 });
2912
2913 match case.expected_error {
2914 Some(err) => assert_eq!(last_result.unwrap().unwrap_err(), err),
2915 _ => {
2916 for object_id in case.forget_objects {
2917 assert_eq!(
2918 objects()
2919 .load(&deps.storage, decode_hex(object_id.as_str()).into())
2920 .unwrap_err(),
2921 StdError::not_found(not_found_object_info::<Object>(&object_id))
2922 );
2923 }
2924 }
2925 }
2926 assert_eq!(
2927 objects()
2928 .keys_raw(&deps.storage, None, None, Order::Ascending)
2929 .count(),
2930 case.expected_count
2931 );
2932
2933 let bucket = query::bucket(deps.as_ref()).unwrap();
2934 assert_eq!(
2935 bucket.stat,
2936 BucketStatBuilder::default()
2937 .object_count(Uint128::from(case.expected_count as u128))
2938 .size(case.expected_total_size)
2939 .compressed_size(case.expected_compressed_size)
2940 .build()
2941 .unwrap()
2942 );
2943 assert_eq!(
2944 bucket.stat.object_count,
2945 Uint128::from(case.expected_count as u128)
2946 );
2947 assert_eq!(bucket.stat.size, case.expected_total_size);
2948 assert_eq!(bucket.stat.compressed_size, case.expected_compressed_size);
2949 }
2950 }
2951
2952 #[test]
2953 fn store_forgotten_object() {
2954 let mut deps = mock_dependencies();
2955 let info = message_info(&addr(CREATOR), &[]);
2956
2957 instantiate(
2958 deps.as_mut(),
2959 mock_env(),
2960 info.clone(),
2961 InstantiateMsg {
2962 bucket: "test".to_string(),
2963 config: Default::default(),
2964 limits: Default::default(),
2965 pagination: Default::default(),
2966 },
2967 )
2968 .unwrap();
2969
2970 let data = general_purpose::STANDARD.encode("data");
2971 let _ = execute(
2972 deps.as_mut(),
2973 mock_env(),
2974 info.clone(),
2975 ExecuteMsg::StoreObject {
2976 data: Binary::from_base64(data.as_str()).unwrap(),
2977 pin: false,
2978 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
2979 },
2980 )
2981 .unwrap();
2982
2983 let _ = execute(
2984 deps.as_mut(),
2985 mock_env(),
2986 info.clone(),
2987 ExecuteMsg::ForgetObject {
2988 id: "3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7".to_string(),
2989 },
2990 )
2991 .unwrap();
2992
2993 let result = execute(
2994 deps.as_mut(),
2995 mock_env(),
2996 info,
2997 ExecuteMsg::StoreObject {
2998 data: Binary::from_base64(data.as_str()).unwrap(),
2999 pin: false,
3000 compression_algorithm: Some(CompressionAlgorithm::Passthrough),
3001 },
3002 );
3003
3004 assert_eq!(
3005 result.err(),
3006 None,
3007 "Object should successfully restored after a forgot"
3008 );
3009 }
3010}