collection - [mainnet]
This defines an object-based Collection. A collection acts as a set organizer for a group of tokens. This includes aspects such as a general description, project URI, name, and may contain other useful generalizations across this set of tokens.
Being built upon objects enables collections to be relatively flexible. As core primitives it supports:
- Common fields: name, uri, description, creator
- MutatorRef leaving mutability configuration to a higher level component
- Addressed by a global identifier of creator’s address and collection name, thus collections cannot be deleted as a restriction of the object model.
- Optional support for collection-wide royalties
- Optional support for tracking of supply with events on mint or burn
TODO:
- Consider supporting changing the name of the collection with the MutatorRef. This would require adding the field original_name.
- Consider supporting changing the aspects of supply with the MutatorRef.
- Add aggregator support when added to framework
use 0x1::aggregator_v2;use 0x1::error;use 0x1::event;use 0x1::features;use 0x1::object;use 0x1::option;use 0x1::signer;use 0x1::string;use 0x4::royalty;
Constants
const MAX_U64: u64 = 18446744073709551615;
The URI is over the maximum length
const EURI_TOO_LONG: u64 = 4;
const MAX_URI_LENGTH: u64 = 512;
Tried upgrading collection to concurrent, but collection is already concurrent
const EALREADY_CONCURRENT: u64 = 8;
The collection does not exist
const ECOLLECTION_DOES_NOT_EXIST: u64 = 1;
The collection name is over the maximum length
const ECOLLECTION_NAME_TOO_LONG: u64 = 3;
The collection owner feature is not supported
const ECOLLECTION_OWNER_NOT_SUPPORTED: u64 = 11;
The collection has reached its supply and no more tokens can be minted, unless some are burned
const ECOLLECTION_SUPPLY_EXCEEDED: u64 = 2;
Concurrent feature flag is not yet enabled, so the function cannot be performed
const ECONCURRENT_NOT_ENABLED: u64 = 7;
The description is over the maximum length
const EDESCRIPTION_TOO_LONG: u64 = 5;
The new max supply cannot be less than the current supply
const EINVALID_MAX_SUPPLY: u64 = 9;
The max supply must be positive
const EMAX_SUPPLY_CANNOT_BE_ZERO: u64 = 6;
The collection does not have a max supply
const ENO_MAX_SUPPLY_IN_COLLECTION: u64 = 10;
const MAX_COLLECTION_NAME_LENGTH: u64 = 128;
const MAX_DESCRIPTION_LENGTH: u64 = 2048;
Structs
MutatorRef
This enables mutating description and URI by higher level services.
struct MutatorRef has drop, store
Fields
-
self: address
MutationEvent
Contains the mutated fields name. This makes the life of indexers easier, so that they can directly understand the behavior in a writeset.
struct MutationEvent has drop, store
Fields
-
mutated_field_name: string::String
Mutation
Contains the mutated fields name. This makes the life of indexers easier, so that they can directly understand the behavior in a writeset.
#[event]struct Mutation has drop, store
Fields
-
mutated_field_name: string::String
-
collection: object::Object<collection::Collection>
-
old_value: string::String
-
new_value: string::String
BurnEvent
struct BurnEvent has drop, store
Fields
-
index: u64
-
token: address
MintEvent
struct MintEvent has drop, store
Fields
-
index: u64
-
token: address
Burn
#[event]struct Burn has drop, store
Fields
-
collection: address
-
index: u64
-
token: address
-
previous_owner: address
Mint
#[event]struct Mint has drop, store
Fields
-
collection: address
-
index: aggregator_v2::AggregatorSnapshot<u64>
-
token: address
ConcurrentBurnEvent
#[event]#[deprecated]struct ConcurrentBurnEvent has drop, store
Fields
-
collection_addr: address
-
index: u64
-
token: address
ConcurrentMintEvent
#[event]#[deprecated]struct ConcurrentMintEvent has drop, store
Fields
-
collection_addr: address
-
index: aggregator_v2::AggregatorSnapshot<u64>
-
token: address
SetMaxSupply
#[event]struct SetMaxSupply has drop, store
Fields
-
collection: object::Object<collection::Collection>
-
old_max_supply: u64
-
new_max_supply: u64
Resources
Collection
Represents the common fields for a collection.
#[resource_group_member(#[group = 0x1::object::ObjectGroup])]struct Collection has key
Fields
-
creator: address
- The creator of this collection.
-
description: string::String
- A brief description of the collection.
-
name: string::String
- An optional categorization of similar token.
-
uri: string::String
- The Uniform Resource Identifier (uri) pointing to the JSON file stored in off-chain storage; the URL length will likely need a maximum any suggestions?
-
mutation_events: event::EventHandle<collection::MutationEvent>
- Emitted upon any mutation of the collection.
FixedSupply
Fixed supply tracker, this is useful for ensuring that a limited number of tokens are minted. and adding events and supply tracking to a collection.
#[resource_group_member(#[group = 0x1::object::ObjectGroup])]struct FixedSupply has key
Fields
-
current_supply: u64
- Total minted - total burned
-
max_supply: u64
-
total_minted: u64
-
burn_events: event::EventHandle<collection::BurnEvent>
- Emitted upon burning a Token.
-
mint_events: event::EventHandle<collection::MintEvent>
- Emitted upon minting an Token.
UnlimitedSupply
Unlimited supply tracker, this is useful for adding events and supply tracking to a collection.
#[resource_group_member(#[group = 0x1::object::ObjectGroup])]struct UnlimitedSupply has key
Fields
-
current_supply: u64
-
total_minted: u64
-
burn_events: event::EventHandle<collection::BurnEvent>
- Emitted upon burning a Token.
-
mint_events: event::EventHandle<collection::MintEvent>
- Emitted upon minting an Token.
ConcurrentSupply
Supply tracker, useful for tracking amount of issued tokens. If max_value is not set to U64_MAX, this ensures that a limited number of tokens are minted.
#[resource_group_member(#[group = 0x1::object::ObjectGroup])]struct ConcurrentSupply has key
Fields
-
current_supply: aggregator_v2::Aggregator<u64>
- Total minted - total burned
-
total_minted: aggregator_v2::Aggregator<u64>
Functions
create_fixed_collection
Creates a fixed-sized collection, or a collection that supports a fixed amount of tokens. This is useful to create a guaranteed, limited supply on-chain digital asset. For example, a collection 1111 vicious vipers. Note, creating restrictions such as upward limits results in data structures that prevent Aptos from parallelizing mints of this collection type. Beyond that, it adds supply tracking with events.
public fun create_fixed_collection(creator: &signer, description: string::String, max_supply: u64, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
Implementation
public fun create_fixed_collection( creator: &signer, description: String, max_supply: u64, name: String, royalty: Option<Royalty>, uri: String,): ConstructorRef { assert!(max_supply != 0, error::invalid_argument(EMAX_SUPPLY_CANNOT_BE_ZERO)); let collection_seed = create_collection_seed(&name); let constructor_ref = object::create_named_object(creator, collection_seed);
let supply = ConcurrentSupply { current_supply: aggregator_v2::create_aggregator(max_supply), total_minted: aggregator_v2::create_unbounded_aggregator(), };
create_collection_internal( creator, constructor_ref, description, name, royalty, uri, option::some(supply), )}
create_fixed_collection_as_owner
Same functionality as create_fixed_collection
, but the caller is the owner of the collection.
This means that the caller can transfer the collection to another address.
This transfers ownership and minting permissions to the new address.
public fun create_fixed_collection_as_owner(creator: &signer, description: string::String, max_supply: u64, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
Implementation
public fun create_fixed_collection_as_owner( creator: &signer, description: String, max_supply: u64, name: String, royalty: Option<Royalty>, uri: String,): ConstructorRef { assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED));
let constructor_ref = create_fixed_collection( creator, description, max_supply, name, royalty, uri, ); enable_ungated_transfer(&constructor_ref); constructor_ref}
create_unlimited_collection
Creates an unlimited collection. This has support for supply tracking but does not limit the supply of tokens.
public fun create_unlimited_collection(creator: &signer, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
Implementation
public fun create_unlimited_collection( creator: &signer, description: String, name: String, royalty: Option<Royalty>, uri: String,): ConstructorRef { let collection_seed = create_collection_seed(&name); let constructor_ref = object::create_named_object(creator, collection_seed);
let supply = ConcurrentSupply { current_supply: aggregator_v2::create_unbounded_aggregator(), total_minted: aggregator_v2::create_unbounded_aggregator(), };
create_collection_internal( creator, constructor_ref, description, name, royalty, uri, option::some(supply), )}
create_unlimited_collection_as_owner
Same functionality as create_unlimited_collection
, but the caller is the owner of the collection.
This means that the caller can transfer the collection to another address.
This transfers ownership and minting permissions to the new address.
public fun create_unlimited_collection_as_owner(creator: &signer, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
Implementation
public fun create_unlimited_collection_as_owner( creator: &signer, description: String, name: String, royalty: Option<Royalty>, uri: String,): ConstructorRef { assert!(features::is_collection_owner_enabled(), error::unavailable(ECOLLECTION_OWNER_NOT_SUPPORTED));
let constructor_ref = create_unlimited_collection( creator, description, name, royalty, uri, ); enable_ungated_transfer(&constructor_ref); constructor_ref}
create_untracked_collection
Creates an untracked collection, or a collection that supports an arbitrary amount of tokens. This is useful for mass airdrops that fully leverage Aptos parallelization. TODO: Hide this until we bring back meaningful way to enforce burns
fun create_untracked_collection(creator: &signer, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
Implementation
fun create_untracked_collection( creator: &signer, description: String, name: String, royalty: Option<Royalty>, uri: String,): ConstructorRef { let collection_seed = create_collection_seed(&name); let constructor_ref = object::create_named_object(creator, collection_seed);
create_collection_internal<FixedSupply>( creator, constructor_ref, description, name, royalty, uri, option::none(), )}
create_collection_internal
fun create_collection_internal<Supply: key>(creator: &signer, constructor_ref: object::ConstructorRef, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String, supply: option::Option<Supply>): object::ConstructorRef
Implementation
inline fun create_collection_internal<Supply: key>( creator: &signer, constructor_ref: ConstructorRef, description: String, name: String, royalty: Option<Royalty>, uri: String, supply: Option<Supply>,): ConstructorRef { assert!(name.length() <= MAX_COLLECTION_NAME_LENGTH, error::out_of_range(ECOLLECTION_NAME_TOO_LONG)); assert!(uri.length() <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); assert!(description.length() <= MAX_DESCRIPTION_LENGTH, error::out_of_range(EDESCRIPTION_TOO_LONG));
let object_signer = object::generate_signer(&constructor_ref);
let collection = Collection { creator: signer::address_of(creator), description, name, uri, mutation_events: object::new_event_handle(&object_signer), }; move_to(&object_signer, collection);
if (supply.is_some()) { move_to(&object_signer, supply.destroy_some()) } else { supply.destroy_none() };
if (royalty.is_some()) { royalty::init(&constructor_ref, royalty.extract()) };
let transfer_ref = object::generate_transfer_ref(&constructor_ref); object::disable_ungated_transfer(&transfer_ref);
constructor_ref}
enable_ungated_transfer
fun enable_ungated_transfer(constructor_ref: &object::ConstructorRef)
Implementation
inline fun enable_ungated_transfer(constructor_ref: &ConstructorRef) { let transfer_ref = object::generate_transfer_ref(constructor_ref); object::enable_ungated_transfer(&transfer_ref);}
create_collection_address
Generates the collections address based upon the creators address and the collection’s name
public fun create_collection_address(creator: &address, name: &string::String): address
Implementation
public fun create_collection_address(creator: &address, name: &String): address { object::create_object_address(creator, create_collection_seed(name))}
create_collection_seed
Named objects are derived from a seed, the collection’s seed is its name.
public fun create_collection_seed(name: &string::String): vector<u8>
Implementation
public fun create_collection_seed(name: &String): vector<u8> { assert!(name.length() <= MAX_COLLECTION_NAME_LENGTH, error::out_of_range(ECOLLECTION_NAME_TOO_LONG)); *name.bytes()}
increment_supply
Called by token on mint to increment supply if there’s an appropriate Supply struct.
public(friend) fun increment_supply(collection: &object::Object<collection::Collection>, token: address): option::Option<aggregator_v2::AggregatorSnapshot<u64>>
Implementation
friend fun increment_supply( collection: &Object<Collection>, token: address,): Option<AggregatorSnapshot<u64>> acquires FixedSupply, UnlimitedSupply, ConcurrentSupply { let collection_addr = object::object_address(collection); if (exists<ConcurrentSupply>(collection_addr)) { let supply = &mut ConcurrentSupply[collection_addr]; assert!( aggregator_v2::try_add(&mut supply.current_supply, 1), error::out_of_range(ECOLLECTION_SUPPLY_EXCEEDED), ); aggregator_v2::add(&mut supply.total_minted, 1); event::emit( Mint { collection: collection_addr, index: aggregator_v2::snapshot(&supply.total_minted), token, }, ); option::some(aggregator_v2::snapshot(&supply.total_minted)) } else if (exists<FixedSupply>(collection_addr)) { let supply = &mut FixedSupply[collection_addr]; supply.current_supply += 1; supply.total_minted += 1; assert!( supply.current_supply <= supply.max_supply, error::out_of_range(ECOLLECTION_SUPPLY_EXCEEDED), ); if (std::features::module_event_migration_enabled()) { event::emit( Mint { collection: collection_addr, index: aggregator_v2::create_snapshot(supply.total_minted), token, }, ); } else { event::emit_event(&mut supply.mint_events, MintEvent { index: supply.total_minted, token, }, ); }; option::some(aggregator_v2::create_snapshot<u64>(supply.total_minted)) } else if (exists<UnlimitedSupply>(collection_addr)) { let supply = &mut UnlimitedSupply[collection_addr]; supply.current_supply += 1; supply.total_minted += 1; if (std::features::module_event_migration_enabled()) { event::emit( Mint { collection: collection_addr, index: aggregator_v2::create_snapshot(supply.total_minted), token, }, ); } else { event::emit_event( &mut supply.mint_events, MintEvent { index: supply.total_minted, token, }, ); }; option::some(aggregator_v2::create_snapshot<u64>(supply.total_minted)) } else { option::none() }}
decrement_supply
Called by token on burn to decrement supply if there’s an appropriate Supply struct.
public(friend) fun decrement_supply(collection: &object::Object<collection::Collection>, token: address, index: option::Option<u64>, previous_owner: address)
Implementation
friend fun decrement_supply( collection: &Object<Collection>, token: address, index: Option<u64>, previous_owner: address,) acquires FixedSupply, UnlimitedSupply, ConcurrentSupply { let collection_addr = object::object_address(collection); if (exists<ConcurrentSupply>(collection_addr)) { let supply = &mut ConcurrentSupply[collection_addr]; aggregator_v2::sub(&mut supply.current_supply, 1);
event::emit( Burn { collection: collection_addr, index: *index.borrow(), token, previous_owner, }, ); } else if (exists<FixedSupply>(collection_addr)) { let supply = &mut FixedSupply[collection_addr]; supply.current_supply -= 1; if (std::features::module_event_migration_enabled()) { event::emit( Burn { collection: collection_addr, index: *index.borrow(), token, previous_owner, }, ); } else { event::emit_event( &mut supply.burn_events, BurnEvent { index: *index.borrow(), token, }, ); }; } else if (exists<UnlimitedSupply>(collection_addr)) { let supply = &mut UnlimitedSupply[collection_addr]; supply.current_supply -= 1; if (std::features::module_event_migration_enabled()) { event::emit( Burn { collection: collection_addr, index: *index.borrow(), token, previous_owner, }, ); } else { event::emit_event( &mut supply.burn_events, BurnEvent { index: *index.borrow(), token, }, ); }; }}
generate_mutator_ref
Creates a MutatorRef, which gates the ability to mutate any fields that support mutation.
public fun generate_mutator_ref(ref: &object::ConstructorRef): collection::MutatorRef
Implementation
public fun generate_mutator_ref(ref: &ConstructorRef): MutatorRef { let object = object::object_from_constructor_ref<Collection>(ref); MutatorRef { self: object::object_address(&object) }}
upgrade_to_concurrent
public fun upgrade_to_concurrent(ref: &object::ExtendRef)
Implementation
public fun upgrade_to_concurrent( ref: &ExtendRef,) acquires FixedSupply, UnlimitedSupply { let metadata_object_address = object::address_from_extend_ref(ref); let metadata_object_signer = object::generate_signer_for_extending(ref);
let (supply, current_supply, total_minted, burn_events, mint_events) = if (exists<FixedSupply>( metadata_object_address )) { let FixedSupply { current_supply, max_supply, total_minted, burn_events, mint_events, } = move_from<FixedSupply>(metadata_object_address);
let supply = ConcurrentSupply { current_supply: aggregator_v2::create_aggregator(max_supply), total_minted: aggregator_v2::create_unbounded_aggregator(), }; (supply, current_supply, total_minted, burn_events, mint_events) } else if (exists<UnlimitedSupply>(metadata_object_address)) { let UnlimitedSupply { current_supply, total_minted, burn_events, mint_events, } = move_from<UnlimitedSupply>(metadata_object_address);
let supply = ConcurrentSupply { current_supply: aggregator_v2::create_unbounded_aggregator(), total_minted: aggregator_v2::create_unbounded_aggregator(), }; (supply, current_supply, total_minted, burn_events, mint_events) } else { // untracked collection is already concurrent, and other variants too. abort error::invalid_argument(EALREADY_CONCURRENT) };
// update current state: aggregator_v2::add(&mut supply.current_supply, current_supply); aggregator_v2::add(&mut supply.total_minted, total_minted); move_to(&metadata_object_signer, supply);
event::destroy_handle(burn_events); event::destroy_handle(mint_events);}
check_collection_exists
fun check_collection_exists(addr: address)
Implementation
inline fun check_collection_exists(addr: address) { assert!( exists<Collection>(addr), error::not_found(ECOLLECTION_DOES_NOT_EXIST), );}
borrow
fun borrow<T: key>(collection: &object::Object<T>): &collection::Collection
Implementation
inline fun borrow<T: key>(collection: &Object<T>): &Collection { let collection_address = object::object_address(collection); check_collection_exists(collection_address); &Collection[collection_address]}
count
Provides the count of the current selection if supply tracking is used
Note: Calling this method from transaction that also mints/burns, prevents it from being parallelized.
#[view]public fun count<T: key>(collection: object::Object<T>): option::Option<u64>
Implementation
public fun count<T: key>( collection: Object<T>): Option<u64> acquires FixedSupply, UnlimitedSupply, ConcurrentSupply { let collection_address = object::object_address(&collection); check_collection_exists(collection_address);
if (exists<ConcurrentSupply>(collection_address)) { let supply = &ConcurrentSupply[collection_address]; option::some(aggregator_v2::read(&supply.current_supply)) } else if (exists<FixedSupply>(collection_address)) { let supply = &FixedSupply[collection_address]; option::some(supply.current_supply) } else if (exists<UnlimitedSupply>(collection_address)) { let supply = &UnlimitedSupply[collection_address]; option::some(supply.current_supply) } else { option::none() }}
creator
#[view]public fun creator<T: key>(collection: object::Object<T>): address
Implementation
public fun creator<T: key>(collection: Object<T>): address acquires Collection { borrow(&collection).creator}
description
#[view]public fun description<T: key>(collection: object::Object<T>): string::String
Implementation
public fun description<T: key>(collection: Object<T>): String acquires Collection { borrow(&collection).description}
name
#[view]public fun name<T: key>(collection: object::Object<T>): string::String
Implementation
public fun name<T: key>(collection: Object<T>): String acquires Collection { borrow(&collection).name}
uri
#[view]public fun uri<T: key>(collection: object::Object<T>): string::String
Implementation
public fun uri<T: key>(collection: Object<T>): String acquires Collection { borrow(&collection).uri}
borrow_mut
fun borrow_mut(mutator_ref: &collection::MutatorRef): &mut collection::Collection
Implementation
inline fun borrow_mut(mutator_ref: &MutatorRef): &mut Collection { check_collection_exists(mutator_ref.self); &mut Collection[mutator_ref.self]}
set_name
Callers of this function must be aware that changing the name will change the calculated
collection’s address when calling create_collection_address
.
Once the collection has been created, the collection address should be saved for reference and
create_collection_address
should not be used to derive the collection’s address.
After changing the collection’s name, to create tokens - only call functions that accept the collection object as an argument.
public fun set_name(mutator_ref: &collection::MutatorRef, name: string::String)
Implementation
public fun set_name(mutator_ref: &MutatorRef, name: String) acquires Collection { assert!(name.length() <= MAX_COLLECTION_NAME_LENGTH, error::out_of_range(ECOLLECTION_NAME_TOO_LONG)); let collection = borrow_mut(mutator_ref); event::emit(Mutation { mutated_field_name: string::utf8(b"name") , collection: object::address_to_object(mutator_ref.self), old_value: collection.name, new_value: name, }); collection.name = name;}
set_description
public fun set_description(mutator_ref: &collection::MutatorRef, description: string::String)
Implementation
public fun set_description(mutator_ref: &MutatorRef, description: String) acquires Collection { assert!(description.length() <= MAX_DESCRIPTION_LENGTH, error::out_of_range(EDESCRIPTION_TOO_LONG)); let collection = borrow_mut(mutator_ref); if (std::features::module_event_migration_enabled()) { event::emit(Mutation { mutated_field_name: string::utf8(b"description"), collection: object::address_to_object(mutator_ref.self), old_value: collection.description, new_value: description, }); } else { event::emit_event( &mut collection.mutation_events, MutationEvent { mutated_field_name: string::utf8(b"description") }, ); }; collection.description = description;}
set_uri
public fun set_uri(mutator_ref: &collection::MutatorRef, uri: string::String)
Implementation
public fun set_uri(mutator_ref: &MutatorRef, uri: String) acquires Collection { assert!(uri.length() <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); let collection = borrow_mut(mutator_ref); if (std::features::module_event_migration_enabled()) { event::emit(Mutation { mutated_field_name: string::utf8(b"uri"), collection: object::address_to_object(mutator_ref.self), old_value: collection.uri, new_value: uri, }); } else { event::emit_event( &mut collection.mutation_events, MutationEvent { mutated_field_name: string::utf8(b"uri") }, ); }; collection.uri = uri;}
set_max_supply
public fun set_max_supply(mutator_ref: &collection::MutatorRef, max_supply: u64)
Implementation
public fun set_max_supply(mutator_ref: &MutatorRef, max_supply: u64) acquires ConcurrentSupply, FixedSupply { let collection = object::address_to_object<Collection>(mutator_ref.self); let collection_address = object::object_address(&collection); let old_max_supply;
if (exists<ConcurrentSupply>(collection_address)) { let supply = &mut ConcurrentSupply[collection_address]; let current_supply = aggregator_v2::read(&supply.current_supply); assert!( max_supply >= current_supply, error::out_of_range(EINVALID_MAX_SUPPLY), ); old_max_supply = aggregator_v2::max_value(&supply.current_supply); supply.current_supply = aggregator_v2::create_aggregator(max_supply); aggregator_v2::add(&mut supply.current_supply, current_supply); } else if (exists<FixedSupply>(collection_address)) { let supply = &mut FixedSupply[collection_address]; assert!( max_supply >= supply.current_supply, error::out_of_range(EINVALID_MAX_SUPPLY), ); old_max_supply = supply.max_supply; supply.max_supply = max_supply; } else { abort error::invalid_argument(ENO_MAX_SUPPLY_IN_COLLECTION) };
event::emit(SetMaxSupply { collection, old_max_supply, new_max_supply: max_supply });}
Specification
increment_supply
public(friend) fun increment_supply(collection: &object::Object<collection::Collection>, token: address): option::Option<aggregator_v2::AggregatorSnapshot<u64>>
pragma aborts_if_is_partial;let collection_addr = object::object_address(collection);let supply = global<ConcurrentSupply>(collection_addr);let post supply_post = global<ConcurrentSupply>(collection_addr);aborts_if exists<ConcurrentSupply>(collection_addr) && aggregator_v2::spec_get_value(supply.current_supply) + 1 > aggregator_v2::spec_get_max_value(supply.current_supply);aborts_if exists<ConcurrentSupply>(collection_addr) && aggregator_v2::spec_get_value(supply.total_minted) + 1 > aggregator_v2::spec_get_max_value(supply.total_minted);ensures aggregator_v2::spec_get_max_value(supply.current_supply) == aggregator_v2::spec_get_max_value(supply_post.current_supply);ensures exists<ConcurrentSupply>(collection_addr) && aggregator_v2::spec_get_value(supply.current_supply) + 1 <= aggregator_v2::spec_get_max_value(supply.current_supply) ==> aggregator_v2::spec_get_value(supply.current_supply) + 1 == aggregator_v2::spec_get_value(supply_post.current_supply);