Selaa lähdekoodia

Merge branch 'sumer' of https://github.com/Joystream/joystream into cli_update_role_storage

iorveth 4 vuotta sitten
vanhempi
commit
4a56508dd7

+ 3 - 3
query-node/package.json

@@ -37,11 +37,11 @@
     "tslib": "^2.0.0",
     "@types/bn.js": "^4.11.6",
     "bn.js": "^5.1.2",
-    "@dzlzv/hydra-cli": "2.0.0-beta",
-    "@dzlzv/hydra-processor": "0.1.1"
+    "@dzlzv/hydra-cli": "2.0.1-beta.16",
+    "@dzlzv/hydra-processor": "2.0.1-beta.16"
   },
   "devDependencies": {
-    "@dzlzv/hydra-typegen": "0.0.3-1"
+    "@dzlzv/hydra-typegen": "2.0.1-beta.16"
   },
   "volta": {
 		"extends": "../package.json"

+ 112 - 88
query-node/schema.graphql

@@ -1,22 +1,9 @@
-# temporary type used before `DateTime` type is working
-type DateTime @entity {
-  timestamp: BigInt!
-}
-
 enum Network {
   BABYLON
   ALEXANDRIA
   ROME
 }
 
-type Block @entity {
-  "Block number as a string"
-  id: ID!
-  block: Int!
-  executedAt: DateTime!
-  network: Network!
-}
-
 enum MembershipEntryMethod {
   PAID
   SCREENING
@@ -44,7 +31,7 @@ type Membership @entity {
   rootAccount: String!
 
   "Blocknumber when member was registered"
-  registeredAtBlock: Block!
+  registeredAtBlock: BigInt!
 
   "Timestamp when member was registered"
   registeredAtTime: DateTime!
@@ -54,6 +41,8 @@ type Membership @entity {
 
   "The type of subscription the member has purchased if any."
   subscription: BigInt
+
+  channels: [Channel!]! @derivedFrom(field: "ownerMember")
 }
 
 "Category of media channel"
@@ -63,57 +52,21 @@ type ChannelCategory @entity {
   "The name of the category"
   name: String @fulltext(query: "channelCategoriesByName")
 
-  channels: [Channel!] @derivedFrom(field: "category")
-
-  happenedIn: Block!
-}
-
-"Storage asset"
-union Asset = AssetUrl | AssetStorage
-
-"Asset stored at an external source"
-type AssetUrl @variant {
-  id: ID!
+  channels: [Channel!]! @derivedFrom(field: "category")
 
-  "The http url pointing to the media"
-  url: String!
+  happenedIn: BigInt!
 }
 
-"Asset was never fully uploaded."
-type AssetNeverProvided @variant {
-  happenedIn: Block!
-}
-
-"Asset was deleted and is no longer available."
-type AssetDeleted @variant {
-  happenedIn: Block!
-}
-
-"Status of an asset upload"
-type AssetUploadStatus @variant {
-  """
-  Data object in upload life-cycle.
-  If this is deleted, then set oldDataObject in its place if it is set and not rejected, otherwise union goes to Deleted.
-  """
-  dataObject: AssetDataObject!
-
-  """
-  Possible prior data object which was in some stage of upload life-cycle when new one was initiated.
-  If accepted, then apps may chose to use old in place of new before it is accepted.
-  If this is deleted, then set to null.
-  """
-  oldDataObject: AssetDataObject
-
-  happenedIn: Block!
-}
-
-union AssetStorageUploadStatus = AssetNeverProvided | AssetDeleted | AssetUploadStatus
+"Asset availability representation"
+enum AssetAvailability {
+  "Asset is available in storage"
+  ACCEPTED,
 
-type AssetStorage @variant {
-  id: ID!
+  "Asset is being uploaded to storage"
+  PENDING,
 
-  "Upload to content directory status"
-  uploadStatus: AssetStorageUploadStatus!
+  "Anvalid storage (meta)data used"
+  INVALID,
 }
 
 "The decision of the storage provider when it acts as liaison"
@@ -129,12 +82,12 @@ enum LiaisonJudgement {
 }
 
 "Manages content ids, type and storage provider decision about it"
-type AssetDataObject @entity {
+type DataObject @entity {
   "Content owner"
-  owner: AssetOwner!
+  owner: DataObjectOwner!
 
   "Content added at"
-  addedAt: Block!
+  addedAt: BigInt!
 
   "Content type id"
   typeId: Int!
@@ -156,34 +109,44 @@ type AssetDataObject @entity {
 }
 
 "Owner type for storage object"
-union AssetOwner = AssetOwnerMember | AssetOwnerChannel | AssetOwnerDao | AssetOwnerCouncil | AssetOwnerWorkingGroup
+union DataObjectOwner = DataObjectOwnerMember
+  | DataObjectOwnerChannel
+  | DataObjectOwnerDao
+  | DataObjectOwnerCouncil
+  | DataObjectOwnerWorkingGroup
 
 "Asset owned by a member"
-type AssetOwnerMember @variant {
+type DataObjectOwnerMember @variant {
   "Member identifier"
-  memberId: BigInt!
+  memberId: Membership!
+
+  "Variant needs to have at least one property. This value is not used."
+  dummy: Int!
 }
 
 "Asset owned by a channel"
-type AssetOwnerChannel @variant {
+type DataObjectOwnerChannel @variant {
   "Channel identifier"
   channel: Channel!
+
+  "Variant needs to have at least one property. This value is not used."
+  dummy: Int!
 }
 
 "Asset owned by a DAO"
-type AssetOwnerDao @variant {
+type DataObjectOwnerDao @variant {
   "DAO identifier"
   daoId: BigInt!
 }
 
 "Asset owned by the Council"
-type AssetOwnerCouncil @variant {
+type DataObjectOwnerCouncil @variant {
   "Variant needs to have at least one property. This value is not used."
   dummy: Int!
 }
 
 "Asset owned by a WorkingGroup"
-type AssetOwnerWorkingGroup @variant {
+type DataObjectOwnerWorkingGroup @variant {
   "Working group identifier"
   workingGroupId: BigInt!
 }
@@ -197,16 +160,18 @@ type Language @entity {
   "Language identifier ISO 639-1"
   iso: String!
 
-  happenedIn: Block!
+  happenedIn: BigInt!
 }
 
 type Channel @entity {
   "Runtime entity identifier (EntityId)"
   id: ID!
 
-  # "Owner of the channel" Commenting out this field: 'owner' can be curator_group, lead
-  # or a member. We are not handling events related to curator group so we will not set this field
-  # owner: Member!
+  "Member owning the channel (if any)"
+  ownerMember: Membership
+
+  "Curator group owning the channel (if any)"
+  ownerCuratorGroup: CuratorGroup
 
   category: ChannelCategory
 
@@ -219,11 +184,33 @@ type Channel @entity {
   "The description of a Channel"
   description: String
 
-  "Channel's cover (background) photo. Recommended ratio: 16:9."
-  coverPhoto: Asset
+  ### Cover photo asset ###
 
-  "Channel's avatar photo."
-  avatarPhoto: Asset
+  # Channel's cover (background) photo. Recommended ratio: 16:9.
+
+  "Asset's data object"
+  coverPhotoDataObject: DataObject
+
+  "URLs where the asset content can be accessed (if any)"
+  coverPhotoUrls: [String!]
+
+  "Availability meta information"
+  coverPhotoAvailability: AssetAvailability!
+
+  ### Avatar photo asset ###
+
+  # Channel's avatar photo.
+
+  "Asset's data object"
+  avatarDataObject: DataObject
+
+  "URLs where the asset content can be accessed (if any)"
+  avatarUrls: [String!]
+
+  "Availability meta information"
+  avatarAvailability: AssetAvailability!
+
+  ##########################
 
   "Flag signaling whether a channel is public."
   isPublic: Boolean
@@ -234,9 +221,22 @@ type Channel @entity {
   "The primary langauge of the channel's content"
   language: Language
 
-  videos: [Video!] @derivedFrom(field: "channel")
+  videos: [Video!]! @derivedFrom(field: "channel")
+
+  happenedIn: BigInt!
+}
+
+type CuratorGroup @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "Curators belonging to this group"
+  curatorIds: [BigInt!]!
 
-  happenedIn: Block!
+  "Is group active or not"
+  isActive: Boolean!
+
+  channels: [Channel!]! @derivedFrom(field: "ownerCuratorGroup")
 }
 
 type VideoCategory @entity {
@@ -246,9 +246,9 @@ type VideoCategory @entity {
   "The name of the category"
   name: String @fulltext(query: "videoCategoriesByName")
 
-  videos: [Video!] @derivedFrom(field: "category")
+  videos: [Video!]! @derivedFrom(field: "category")
 
-  happenedIn: Block!
+  happenedIn: BigInt!
 }
 
 type Video @entity {
@@ -270,8 +270,20 @@ type Video @entity {
   "Video duration in seconds"
   duration: Int
 
-  "Video thumbnail (recommended ratio: 16:9)"
-  thumbnail: Asset
+  ### Thumbnail asset ###
+
+  # Video thumbnail (recommended ratio: 16:9)
+
+  "Asset's data object"
+  thumbnailDataObject: DataObject
+
+  "URLs where the asset content can be accessed (if any)"
+  thumbnailUrls: [String!]
+
+  "Availability meta information"
+  thumbnailAvailability: AssetAvailability!
+
+  ##########################
 
   "Video's main langauge"
   language: Language
@@ -294,13 +306,25 @@ type Video @entity {
   "License under the video is published"
   license: License
 
-  "Reference to video asset"
-  media: Asset
+  ### Media asset ###
+
+  # Reference to video asset
+
+  "Asset's data object"
+  mediaDataObject: DataObject
+
+  "URLs where the asset content can be accessed (if any)"
+  mediaUrls: [String!]
+
+  "Availability meta information"
+  mediaAvailability: AssetAvailability!
+
+  ##########################
 
   "Video file metadata"
   mediaMetadata: VideoMediaMetadata
 
-  happenedIn: Block!
+  happenedIn: BigInt!
 
   "Is video featured or not"
   isFeatured: Boolean!
@@ -326,7 +350,7 @@ type VideoMediaMetadata @entity {
 
   video: Video @derivedFrom(field: "mediaMetadata")
 
-  happenedIn: Block!
+  happenedIn: BigInt!
 }
 
 type VideoMediaEncoding @entity {

+ 306 - 152
runtime-modules/storage/src/data_directory.rs

@@ -106,12 +106,6 @@ decl_error! {
         /// Voucher objects limit upper bound exceeded
         VoucherObjectsLimitUpperBoundExceeded,
 
-        /// Contant uploading failed. Actor voucher size limit exceeded.
-        GlobalVoucherSizeLimitExceeded,
-
-        /// Contant uploading failed. Actor voucher objects limit exceeded.
-        GlobalVoucherObjectsLimitExceeded,
-
         /// Content uploading blocked.
         ContentUploadingBlocked,
 
@@ -119,7 +113,13 @@ decl_error! {
         OwnersAreNotEqual,
 
         /// No storage provider available to service the request
-        NoProviderAvailable
+        NoProviderAvailable,
+
+        /// New voucher limit being set is less than used.
+        VoucherLimitLessThanUsed,
+
+        /// Overflow detected when changing
+        VoucherOverflow,
     }
 }
 
@@ -195,15 +195,17 @@ pub struct Delta {
 }
 
 /// Uploading voucher for StorageObjectOwner
+/// All fields are private. All changes should be done through the methods
+/// to avoid invalid state.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, Default)]
 pub struct Voucher {
     // Total objects size limit per StorageObjectOwner
-    pub size_limit: u64,
+    size_limit: u64,
     // Total objects number limit per StorageObjectOwner
-    pub objects_limit: u64,
-    pub size_used: u64,
-    pub objects_used: u64,
+    objects_limit: u64,
+    size_used: u64,
+    objects_used: u64,
 }
 
 impl Voucher {
@@ -217,7 +219,24 @@ impl Voucher {
         }
     }
 
-    /// Calculate voucher delta
+    pub fn get_size_limit(&self) -> u64 {
+        self.size_limit
+    }
+
+    pub fn get_objects_limit(&self) -> u64 {
+        self.objects_limit
+    }
+
+    pub fn get_size_used(&self) -> u64 {
+        self.size_used
+    }
+
+    pub fn get_objects_used(&self) -> u64 {
+        self.objects_used
+    }
+
+    /// Calculate voucher delta. We don't do checked_sub as the other methods
+    /// protect against state which would result in underflow.
     pub fn calculate_delta(&self) -> Delta {
         Delta {
             size: self.size_limit - self.size_used,
@@ -225,28 +244,63 @@ impl Voucher {
         }
     }
 
-    pub fn fill_voucher(self, voucher_delta: Delta) -> Self {
-        Self {
-            size_used: self.size_used + voucher_delta.size,
-            objects_used: self.objects_used + voucher_delta.objects,
-            ..self
+    /// Attempts to fill voucher and returns an updated Voucher if no overlflows occur
+    /// or limits are exceeded. Error otherwise.
+    pub fn fill_voucher<T: Trait>(self, voucher_delta: Delta) -> Result<Self, Error<T>> {
+        if let Some(size_used) = self.size_used.checked_add(voucher_delta.size) {
+            // Ensure size limit not exceeded
+            ensure!(
+                size_used <= self.size_limit,
+                Error::<T>::VoucherSizeLimitExceeded
+            );
+            if let Some(objects_used) = self.objects_used.checked_add(voucher_delta.objects) {
+                // Ensure objects limit not exceeded
+                ensure!(
+                    objects_used <= self.objects_limit,
+                    Error::<T>::VoucherObjectsLimitExceeded
+                );
+                return Ok(Self {
+                    size_used,
+                    objects_used,
+                    ..self
+                });
+            }
         }
+        Err(Error::<T>::VoucherOverflow)
     }
 
-    pub fn release_voucher(self, voucher_delta: Delta) -> Self {
-        Self {
-            size_used: self.size_used - voucher_delta.size,
-            objects_used: self.objects_used - voucher_delta.objects,
-            ..self
+    pub fn release_voucher<T: Trait>(self, voucher_delta: Delta) -> Result<Self, Error<T>> {
+        if let Some(size_used) = self.size_used.checked_sub(voucher_delta.size) {
+            if let Some(objects_used) = self.objects_used.checked_sub(voucher_delta.objects) {
+                return Ok(Self {
+                    size_used,
+                    objects_used,
+                    ..self
+                });
+            }
         }
+        Err(Error::<T>::VoucherOverflow)
     }
 
-    pub fn set_new_size_limit(&mut self, new_size_limit: u64) {
-        self.size_limit = new_size_limit;
+    pub fn set_new_size_limit<T: Trait>(&mut self, new_size_limit: u64) -> Result<(), Error<T>> {
+        if self.size_used > new_size_limit {
+            Err(Error::<T>::VoucherLimitLessThanUsed)
+        } else {
+            self.size_limit = new_size_limit;
+            Ok(())
+        }
     }
 
-    pub fn set_new_objects_limit(&mut self, new_objects_limit: u64) {
-        self.objects_limit = new_objects_limit;
+    pub fn set_new_objects_limit<T: Trait>(
+        &mut self,
+        new_objects_limit: u64,
+    ) -> Result<(), Error<T>> {
+        if self.objects_used > new_objects_limit {
+            Err(Error::<T>::VoucherLimitLessThanUsed)
+        } else {
+            self.objects_limit = new_objects_limit;
+            Ok(())
+        }
     }
 }
 
@@ -271,7 +325,7 @@ decl_storage! {
         pub VoucherObjectsLimitUpperBound get(fn voucher_objects_limit_upper_bound) config(): u64 = DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND;
 
         /// Default content voucher for all actors.
-        pub DefaultVoucher get(fn default_voucher) config(): Voucher;
+        pub DefaultVoucher get(fn default_voucher) config(): Voucher = DEFAULT_VOUCHER;
 
         /// Global voucher.
         pub GlobalVoucher get(fn global_voucher) config(): Voucher = DEFAULT_GLOBAL_VOUCHER;
@@ -332,6 +386,32 @@ decl_event! {
         /// Params:
         /// - UploadingStatus bool flag.
         ContentUploadingStatusUpdated(UploadingStatus),
+
+        /// Emits when the global voucher size limit is updated.
+        /// Params:
+        /// - New limit
+        GlobalVoucherSizeLimitUpdated(u64),
+
+        /// Emits when the global voucher objects limit is updated.
+        /// Params:
+        /// - New limit
+        GlobalVoucherObjectsLimitUpdated(u64),
+
+        /// Emits when the size limit upper bound is updated.
+        /// Params:
+        /// - New Upper bound
+        VoucherSizeLimitUpperBoundUpdated(u64),
+
+        /// Emits when the objects limit upper bound is updated.
+        /// Params:
+        /// - New Upper bound
+        VoucherObjectsLimitUpperBoundUpdated(u64),
+
+        /// Emits when the lead sets a new default voucher
+        /// Params:
+        /// - New size limit
+        /// - New objects limit
+        DefaultVoucherUpdated(u64, u64),
     }
 }
 
@@ -360,14 +440,8 @@ decl_module! {
 
             Self::ensure_content_is_valid(&content)?;
 
-            let owner_voucher = Self::get_voucher(&owner);
-
-            // Ensure owner voucher constraints satisfied.
-            // Calculate upload voucher delta
-            let upload_voucher_delta = Self::ensure_owner_voucher_constraints_satisfied(owner_voucher, &content)?;
-
-            // Ensure global voucher constraints satisfied.
-            Self::ensure_global_voucher_constraints_satisfied(upload_voucher_delta)?;
+            // Ensure owner and global voucher constraints satisfied.
+            let (new_owner_voucher, new_global_voucher) = Self::ensure_voucher_constraints_satisfied(&owner, &content)?;
 
             let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
 
@@ -375,8 +449,13 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            // Let's create the entry then
-            Self::upload_content(owner_voucher, upload_voucher_delta, liaison, content.clone(), owner.clone());
+            // Updade or create owner voucher.
+            <Vouchers<T>>::insert(&owner, new_owner_voucher);
+
+            // Update global voucher
+            <GlobalVoucher>::put(new_global_voucher);
+
+            Self::upload_content(liaison, content.clone(), owner.clone());
 
             Self::deposit_event(RawEvent::ContentAdded(content, owner));
         }
@@ -395,68 +474,181 @@ decl_module! {
             // Ensure content under given content ids can be successfully removed
             let content = Self::ensure_content_can_be_removed(&content_ids, &owner)?;
 
+            let owner_voucher = Self::get_voucher(&owner);
+            let removal_voucher = Self::calculate_content_voucher(content);
+            let new_owner_voucher = owner_voucher.release_voucher::<T>(removal_voucher)?;
+            let new_global_voucher = Self::global_voucher().release_voucher::<T>(removal_voucher)?;
+
             //
             // == MUTATION SAFE ==
             //
 
-            // Let's remove a content
-            Self::delete_content(&owner, &content_ids, content);
+            // Updade owner voucher
+            <Vouchers<T>>::insert(&owner, new_owner_voucher);
+
+            // Update global voucher
+            <GlobalVoucher>::put(new_global_voucher);
+
+            // Let's remove content
+            for content_id in &content_ids {
+                <DataByContentId<T>>::remove(content_id);
+            }
 
             Self::deposit_event(RawEvent::ContentRemoved(content_ids, owner));
         }
 
         /// Updates storage object owner voucher objects limit. Requires leader privileges.
+        /// New limit cannot be less that used value.
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn update_storage_object_owner_voucher_objects_limit(
             origin,
-            abstract_owner: ObjectOwner<T>,
+            object_owner: ObjectOwner<T>,
             new_voucher_objects_limit: u64
         ) {
             <StorageWorkingGroup<T>>::ensure_origin_is_active_leader(origin)?;
-            ensure!(new_voucher_objects_limit <= Self::voucher_objects_limit_upper_bound(), Error::<T>::VoucherObjectsLimitUpperBoundExceeded);
+            ensure!(
+                new_voucher_objects_limit <= Self::voucher_objects_limit_upper_bound(),
+                Error::<T>::VoucherObjectsLimitUpperBoundExceeded
+            );
+
+            let mut voucher = Self::get_voucher(&object_owner);
+
+            voucher.set_new_objects_limit::<T>(new_voucher_objects_limit)?;
 
             //
             // == MUTATION SAFE ==
             //
 
-            if <Vouchers<T>>::contains_key(&abstract_owner) {
-                <Vouchers<T>>::mutate(&abstract_owner, |voucher| {
-                    voucher.set_new_objects_limit(new_voucher_objects_limit);
-                });
-            } else {
-                let mut voucher = Self::default_voucher();
-                voucher.set_new_objects_limit(new_voucher_objects_limit);
-                <Vouchers<T>>::insert(&abstract_owner, voucher);
-            };
+            <Vouchers<T>>::insert(&object_owner, voucher);
 
-            Self::deposit_event(RawEvent::StorageObjectOwnerVoucherObjectsLimitUpdated(abstract_owner, new_voucher_objects_limit));
+            Self::deposit_event(RawEvent::StorageObjectOwnerVoucherObjectsLimitUpdated(object_owner, new_voucher_objects_limit));
         }
 
         /// Updates storage object owner voucher size limit. Requires leader privileges.
+        /// New limit cannot be less that used value.
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn update_storage_object_owner_voucher_size_limit(
             origin,
-            abstract_owner: ObjectOwner<T>,
+            object_owner: ObjectOwner<T>,
             new_voucher_size_limit: u64
         ) {
             <StorageWorkingGroup<T>>::ensure_origin_is_active_leader(origin)?;
-            ensure!(new_voucher_size_limit <= Self::voucher_size_limit_upper_bound(), Error::<T>::VoucherSizeLimitUpperBoundExceeded);
+            ensure!(
+                new_voucher_size_limit <= Self::voucher_size_limit_upper_bound(),
+                Error::<T>::VoucherSizeLimitUpperBoundExceeded
+            );
+
+            let mut voucher = Self::get_voucher(&object_owner);
+
+            voucher.set_new_size_limit::<T>(new_voucher_size_limit)?;
 
             //
             // == MUTATION SAFE ==
             //
 
-            if <Vouchers<T>>::contains_key(&abstract_owner) {
-                <Vouchers<T>>::mutate(&abstract_owner, |voucher| {
-                    voucher.set_new_size_limit(new_voucher_size_limit);
-                });
-            } else {
-                let mut voucher = Self::default_voucher();
-                voucher.set_new_size_limit(new_voucher_size_limit);
-                <Vouchers<T>>::insert(&abstract_owner, voucher);
-            };
+            <Vouchers<T>>::insert(&object_owner, voucher);
+
+            Self::deposit_event(RawEvent::StorageObjectOwnerVoucherSizeLimitUpdated(object_owner, new_voucher_size_limit));
+        }
+
+        /// Sets global voucher size limit. Requires root privileges.
+        /// New limit cannot be less that used value.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn set_global_voucher_size_limit(
+            origin,
+            new_size_limit: u64
+        ) {
+            ensure_root(origin)?;
+
+            let mut voucher =  Self::global_voucher();
+            voucher.set_new_size_limit::<T>(new_size_limit)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            GlobalVoucher::put(voucher);
+
+            Self::deposit_event(RawEvent::GlobalVoucherSizeLimitUpdated(new_size_limit));
+        }
+
+        /// Sets global voucher objects limit. Requires root privileges.
+        /// New limit cannot be less that used value.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn set_global_voucher_objects_limit(
+            origin,
+            new_objects_limit: u64
+        ) {
+            ensure_root(origin)?;
+
+            let mut voucher = Self::global_voucher();
+            voucher.set_new_objects_limit::<T>(new_objects_limit)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            GlobalVoucher::put(voucher);
+
+            Self::deposit_event(RawEvent::GlobalVoucherObjectsLimitUpdated(new_objects_limit));
+        }
+
+        /// Sets VoucherSizeLimitUpperBound. Requires root privileges.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn set_voucher_size_limit_upper_bound(
+            origin,
+            new_upper_bound: u64
+        ) {
+            ensure_root(origin)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            VoucherSizeLimitUpperBound::put(new_upper_bound);
 
-            Self::deposit_event(RawEvent::StorageObjectOwnerVoucherSizeLimitUpdated(abstract_owner, new_voucher_size_limit));
+            Self::deposit_event(RawEvent::VoucherSizeLimitUpperBoundUpdated(new_upper_bound));
+        }
+
+        /// Sets VoucherObjectsLimitUpperBound. Requires root privileges.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn set_voucher_objects_limit_upper_bound(
+            origin,
+            new_upper_bound: u64
+        ) {
+            ensure_root(origin)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            VoucherObjectsLimitUpperBound::put(new_upper_bound);
+
+            Self::deposit_event(RawEvent::VoucherObjectsLimitUpperBoundUpdated(new_upper_bound));
+        }
+
+        /// Set the default owner voucher
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn set_default_voucher(
+            origin,
+            size_limit: u64,
+            objects_limit: u64
+        ) {
+            <StorageWorkingGroup<T>>::ensure_origin_is_active_leader(origin)?;
+            // constrain to upper bounds
+            ensure!(
+                size_limit <= Self::voucher_size_limit_upper_bound(),
+                Error::<T>::VoucherSizeLimitUpperBoundExceeded
+            );
+            ensure!(
+                objects_limit <= Self::voucher_objects_limit_upper_bound(),
+                Error::<T>::VoucherObjectsLimitUpperBoundExceeded
+            );
+
+            // == MUTATION SAFE ==
+            DefaultVoucher::put(Voucher::new(size_limit, objects_limit));
+
+            Self::deposit_event(RawEvent::DefaultVoucherUpdated(size_limit, objects_limit));
         }
 
         /// Storage provider accepts a content. Requires signed storage provider account and its id.
@@ -557,37 +749,6 @@ impl<T: Trait> Module<T> {
         Ok(())
     }
 
-    // Ensure owner voucher constraints satisfied, returns total object length and total size voucher delta for this upload.
-    fn ensure_owner_voucher_constraints_satisfied(
-        owner_voucher: Voucher,
-        content: &[ContentParameters<T::ContentId, DataObjectTypeId<T>>],
-    ) -> Result<Delta, Error<T>> {
-        let owner_voucher_delta = owner_voucher.calculate_delta();
-
-        // Ensure total content length is less or equal then available per given owner voucher
-        let content_length = content.len() as u64;
-
-        ensure!(
-            owner_voucher_delta.objects >= content_length,
-            Error::<T>::VoucherObjectsLimitExceeded
-        );
-
-        // Ensure total content size is less or equal then available per given owner voucher
-        let content_size = content
-            .iter()
-            .fold(0, |total_size, content| total_size + content.size);
-
-        ensure!(
-            owner_voucher_delta.size >= content_size,
-            Error::<T>::VoucherSizeLimitExceeded
-        );
-
-        Ok(Delta {
-            size: content_size,
-            objects: content_length,
-        })
-    }
-
     // Ensure content under given content ids can be successfully removed
     fn ensure_content_can_be_removed(
         content_ids: &[T::ContentId],
@@ -603,7 +764,7 @@ impl<T: Trait> Module<T> {
         Ok(content)
     }
 
-    // Calculates content voucher delta
+    /// Calculates content voucher delta of existing data objects
     fn calculate_content_voucher(content: Vec<DataObject<T>>) -> Delta {
         let content_length = content.len() as u64;
 
@@ -617,27 +778,36 @@ impl<T: Trait> Module<T> {
         }
     }
 
-    // Ensures global voucher constraints satisfied.
-    fn ensure_global_voucher_constraints_satisfied(upload_voucher_delta: Delta) -> DispatchResult {
-        let global_voucher_voucher = Self::global_voucher().calculate_delta();
+    /// Calculates content voucher delta for new content being added
+    fn calculate_new_content_voucher(
+        content: &[ContentParameters<T::ContentId, DataObjectTypeId<T>>],
+    ) -> Delta {
+        let objects = content.len() as u64;
+        let size = content
+            .iter()
+            .fold(0, |total_size, content| total_size + content.size);
+
+        Delta { size, objects }
+    }
 
-        ensure!(
-            global_voucher_voucher.objects >= upload_voucher_delta.objects,
-            Error::<T>::GlobalVoucherObjectsLimitExceeded
-        );
+    /// Ensures new content satisfies both owner and global vouchers. Returns new vouchers
+    /// if constraints are satisfied, Error otherwise.
+    fn ensure_voucher_constraints_satisfied(
+        owner: &ObjectOwner<T>,
+        content: &[ContentParameters<T::ContentId, DataObjectTypeId<T>>],
+    ) -> Result<(Voucher, Voucher), Error<T>> {
+        let owner_voucher = Self::get_voucher(owner);
+        let global_voucher = Self::global_voucher();
+        let content_voucher_delta = Self::calculate_new_content_voucher(content);
 
-        ensure!(
-            global_voucher_voucher.size >= upload_voucher_delta.size,
-            Error::<T>::GlobalVoucherSizeLimitExceeded
-        );
+        let new_owner_voucher = owner_voucher.fill_voucher::<T>(content_voucher_delta)?;
+        let new_global_voucher = global_voucher.fill_voucher::<T>(content_voucher_delta)?;
 
-        Ok(())
+        Ok((new_owner_voucher, new_global_voucher))
     }
 
     // Complete content upload, update vouchers
     fn upload_content(
-        owner_voucher: Voucher,
-        upload_voucher_delta: Delta,
         liaison: StorageProviderId<T>,
         multi_content: Vec<ContentParameters<T::ContentId, DataObjectTypeId<T>>>,
         owner: ObjectOwner<T>,
@@ -655,33 +825,6 @@ impl<T: Trait> Module<T> {
 
             <DataByContentId<T>>::insert(content.content_id, data);
         }
-
-        // Updade or create owner voucher.
-        <Vouchers<T>>::insert(owner, owner_voucher.fill_voucher(upload_voucher_delta));
-
-        // Update global voucher
-        <GlobalVoucher>::put(Self::global_voucher().fill_voucher(upload_voucher_delta));
-    }
-
-    // Complete content removal
-    fn delete_content(
-        owner: &ObjectOwner<T>,
-        content_ids: &[T::ContentId],
-        content: Vec<DataObject<T>>,
-    ) {
-        let removal_voucher = Self::calculate_content_voucher(content);
-
-        for content_id in content_ids {
-            <DataByContentId<T>>::remove(content_id);
-        }
-
-        // Updade owner voucher.
-        <Vouchers<T>>::mutate(owner, |owner_voucher| {
-            owner_voucher.release_voucher(removal_voucher)
-        });
-
-        // Update global voucher
-        <GlobalVoucher>::put(Self::global_voucher().release_voucher(removal_voucher));
     }
 
     fn ensure_content_is_valid(
@@ -759,15 +902,9 @@ impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
 
         Self::ensure_uploading_is_not_blocked()?;
 
-        let owner_voucher = Self::get_voucher(&owner);
-
-        // Ensure owner voucher constraints satisfied.
-        // Calculate upload voucher
-        let upload_voucher =
-            Self::ensure_owner_voucher_constraints_satisfied(owner_voucher, &content)?;
-
-        // Ensure global voucher constraints satisfied.
-        Self::ensure_global_voucher_constraints_satisfied(upload_voucher)?;
+        // Ensure owner and global vouchers constraints satisfied.
+        let (new_owner_voucher, new_global_voucher) =
+            Self::ensure_voucher_constraints_satisfied(&owner, &content)?;
 
         let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
 
@@ -775,9 +912,13 @@ impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
         // == MUTATION SAFE ==
         //
 
-        // Let's create the entry then
+        // Updade or create owner voucher.
+        <Vouchers<T>>::insert(&owner, new_owner_voucher);
+
+        // Update global voucher
+        <GlobalVoucher>::put(new_global_voucher);
 
-        Self::upload_content(owner_voucher, upload_voucher, liaison, content, owner);
+        Self::upload_content(liaison, content, owner);
         Ok(())
     }
 
@@ -788,12 +929,26 @@ impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
         // Ensure content under given content ids can be successfully removed
         let content = Self::ensure_content_can_be_removed(content_ids, owner)?;
 
+        let owner_voucher = Self::get_voucher(&owner);
+        let removal_voucher = Self::calculate_content_voucher(content);
+        let new_owner_voucher = owner_voucher.release_voucher::<T>(removal_voucher)?;
+        let new_global_voucher = Self::global_voucher().release_voucher::<T>(removal_voucher)?;
+
         //
         // == MUTATION SAFE ==
         //
 
-        // Let's remove a content
-        Self::delete_content(owner, content_ids, content);
+        // Updade owner voucher.
+        <Vouchers<T>>::insert(owner, new_owner_voucher);
+
+        // Update global voucher
+        <GlobalVoucher>::put(new_global_voucher);
+
+        // Let's remove content
+        for content_id in content_ids {
+            <DataByContentId<T>>::remove(content_id);
+        }
+
         Ok(())
     }
 
@@ -804,10 +959,9 @@ impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
         Self::ensure_uploading_is_not_blocked()?;
 
         T::StorageProviderHelper::get_random_storage_provider()?;
-        let owner_voucher = Self::get_voucher(&owner);
 
-        // Ensure owner voucher constraints satisfied.
-        Self::ensure_owner_voucher_constraints_satisfied(owner_voucher, &content)?;
+        let _ = Self::ensure_voucher_constraints_satisfied(&owner, &content)?;
+
         Self::ensure_content_is_valid(&content)
     }
 

+ 157 - 12
runtime-modules/storage/src/tests/data_directory.rs

@@ -2,6 +2,7 @@
 
 use crate::data_directory::Error;
 use common::storage::StorageObjectOwner;
+use frame_support::assert_ok;
 use frame_support::dispatch::DispatchError;
 use system::RawOrigin;
 
@@ -91,7 +92,7 @@ fn add_content_size_limit_reached() {
         let content_parameters = ContentParameters {
             content_id: 1,
             type_id: 1234,
-            size: DEFAULT_VOUCHER.size_limit + 1,
+            size: DEFAULT_VOUCHER.get_size_limit() + 1,
             ipfs_content_id: vec![1, 2, 3, 4],
         };
 
@@ -111,7 +112,7 @@ fn add_content_objects_limit_reached() {
 
         let mut content = vec![];
 
-        for i in 0..=DEFAULT_VOUCHER.objects_limit {
+        for i in 0..=DEFAULT_VOUCHER.get_objects_limit() {
             let content_parameters = ContentParameters {
                 content_id: i + 1,
                 type_id: 1234,
@@ -156,10 +157,7 @@ fn add_content_global_size_limit_reached() {
                 owner,
                 vec![content_parameters],
             );
-            assert_eq!(
-                res,
-                Err(Error::<Test>::GlobalVoucherSizeLimitExceeded.into())
-            );
+            assert_eq!(res, Err(Error::<Test>::VoucherSizeLimitExceeded.into()));
         });
 }
 
@@ -192,10 +190,7 @@ fn add_content_global_objects_limit_reached() {
                 owner,
                 vec![content_parameters],
             );
-            assert_eq!(
-                res,
-                Err(Error::<Test>::GlobalVoucherObjectsLimitExceeded.into())
-            );
+            assert_eq!(res, Err(Error::<Test>::VoucherObjectsLimitExceeded.into()));
         });
 }
 
@@ -320,7 +315,7 @@ fn update_storage_object_owner_voucher_objects_limit() {
         assert!(res.is_ok());
 
         assert_eq!(
-            TestDataDirectory::vouchers(owner).objects_limit,
+            TestDataDirectory::vouchers(owner).get_objects_limit(),
             new_objects_limit
         );
     });
@@ -367,7 +362,7 @@ fn update_storage_object_owner_voucher_size_limit() {
         assert!(res.is_ok());
 
         assert_eq!(
-            TestDataDirectory::vouchers(owner).size_limit,
+            TestDataDirectory::vouchers(owner).get_size_limit(),
             new_objects_total_size_limit
         );
     });
@@ -547,3 +542,153 @@ fn reject_content_as_liaison() {
         assert_eq!(res, Ok(()));
     });
 }
+
+#[test]
+fn set_global_voucher_limits() {
+    with_default_mock_builder(|| {
+        /*
+           Events are not emitted on block 0.
+           So any dispatchable calls made during genesis block formation will have no events emitted.
+           https://substrate.dev/recipes/2-appetizers/4-events.html
+        */
+        run_to_block(1);
+
+        let size_limit = TestDataDirectory::global_voucher().get_size_limit();
+        let increment_size_limit_by = 10;
+        let expected_new_size_limit = size_limit + increment_size_limit_by;
+
+        assert_ok!(TestDataDirectory::set_global_voucher_size_limit(
+            RawOrigin::Root.into(),
+            expected_new_size_limit
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::data_directory(data_directory::RawEvent::GlobalVoucherSizeLimitUpdated(
+                expected_new_size_limit
+            ))
+        );
+
+        assert_eq!(
+            TestDataDirectory::global_voucher().get_size_limit(),
+            expected_new_size_limit
+        );
+
+        let objects_limit = TestDataDirectory::global_voucher().get_objects_limit();
+        let increment_objects_limit_by = 10;
+        let expected_new_objects_limit = objects_limit + increment_objects_limit_by;
+
+        assert_ok!(TestDataDirectory::set_global_voucher_objects_limit(
+            RawOrigin::Root.into(),
+            expected_new_objects_limit
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::data_directory(data_directory::RawEvent::GlobalVoucherObjectsLimitUpdated(
+                expected_new_objects_limit
+            ))
+        );
+
+        assert_eq!(
+            TestDataDirectory::global_voucher().get_objects_limit(),
+            expected_new_objects_limit
+        );
+    })
+}
+
+#[test]
+fn set_limit_upper_bounds() {
+    with_default_mock_builder(|| {
+        /*
+           Events are not emitted on block 0.
+           So any dispatchable calls made during genesis block formation will have no events emitted.
+           https://substrate.dev/recipes/2-appetizers/4-events.html
+        */
+        run_to_block(1);
+
+        let size_limit_upper_bound = TestDataDirectory::voucher_size_limit_upper_bound();
+        let increment_size_limit_upper_bound_by = 10;
+        let expected_new_size_limit_upper_bound =
+            size_limit_upper_bound + increment_size_limit_upper_bound_by;
+
+        assert_ok!(TestDataDirectory::set_voucher_size_limit_upper_bound(
+            RawOrigin::Root.into(),
+            expected_new_size_limit_upper_bound
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::data_directory(data_directory::RawEvent::VoucherSizeLimitUpperBoundUpdated(
+                expected_new_size_limit_upper_bound
+            ))
+        );
+
+        assert_eq!(
+            TestDataDirectory::voucher_size_limit_upper_bound(),
+            expected_new_size_limit_upper_bound
+        );
+
+        let objects_limit_upper_bound = TestDataDirectory::voucher_objects_limit_upper_bound();
+        let increment_objects_limit_upper_bound_by = 10;
+        let expected_new_objects_limit_upper_bound =
+            objects_limit_upper_bound + increment_objects_limit_upper_bound_by;
+
+        assert_ok!(TestDataDirectory::set_voucher_objects_limit_upper_bound(
+            RawOrigin::Root.into(),
+            expected_new_objects_limit_upper_bound
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::data_directory(
+                data_directory::RawEvent::VoucherObjectsLimitUpperBoundUpdated(
+                    expected_new_objects_limit_upper_bound
+                )
+            )
+        );
+
+        assert_eq!(
+            TestDataDirectory::voucher_objects_limit_upper_bound(),
+            expected_new_objects_limit_upper_bound
+        );
+    })
+}
+
+#[test]
+fn set_default_voucher() {
+    with_default_mock_builder(|| {
+        /*
+           Events are not emitted on block 0.
+           So any dispatchable calls made during genesis block formation will have no events emitted.
+           https://substrate.dev/recipes/2-appetizers/4-events.html
+        */
+        run_to_block(1);
+
+        SetLeadFixture::set_default_lead();
+
+        let default_voucher = TestDataDirectory::default_voucher();
+
+        let new_size_limit = default_voucher.get_size_limit() + 1;
+        let new_objects_limit = default_voucher.get_objects_limit() + 1;
+
+        assert_ok!(TestDataDirectory::set_default_voucher(
+            Origin::signed(DEFAULT_LEADER_ACCOUNT_ID),
+            new_size_limit,
+            new_objects_limit
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::data_directory(data_directory::RawEvent::DefaultVoucherUpdated(
+                new_size_limit,
+                new_objects_limit
+            ))
+        );
+
+        assert_eq!(
+            TestDataDirectory::default_voucher(),
+            Voucher::new(new_size_limit, new_objects_limit)
+        );
+    })
+}

+ 2 - 2
runtime-modules/storage/src/tests/mock.rs

@@ -287,8 +287,8 @@ pub struct ExtBuilder {
 impl Default for ExtBuilder {
     fn default() -> Self {
         Self {
-            voucher_objects_limit_upper_bound: DEFAULT_VOUCHER_SIZE_LIMIT_UPPER_BOUND,
-            voucher_size_limit_upper_bound: DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND,
+            voucher_objects_limit_upper_bound: DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND,
+            voucher_size_limit_upper_bound: DEFAULT_VOUCHER_SIZE_LIMIT_UPPER_BOUND,
             global_voucher: DEFAULT_GLOBAL_VOUCHER,
             default_voucher: DEFAULT_VOUCHER,
             first_data_object_type_id: 1,