diff --git a/only-on-aptos/Move.toml b/only-on-aptos/Move.toml index 310223e..8cf43f5 100644 --- a/only-on-aptos/Move.toml +++ b/only-on-aptos/Move.toml @@ -8,7 +8,6 @@ only_on_aptos = "_" [dev-addresses] only_on_aptos = "0x123" -minter = "0x456" [dependencies.AptosFramework] git = "https://github.com/aptos-labs/aptos-core.git" @@ -25,7 +24,4 @@ git = 'https://github.com/aptos-labs/aptos-core.git' rev = 'mainnet' subdir = 'aptos-move/framework/aptos-token' -[dependencies.TokenMinter] -local = "../token-minter" - [dev-dependencies] diff --git a/only-on-aptos/sources/coin_payment.move b/only-on-aptos/sources/coin_payment.move new file mode 100644 index 0000000..4222439 --- /dev/null +++ b/only-on-aptos/sources/coin_payment.move @@ -0,0 +1,118 @@ +/// This module allows users to create recurring payments for services, fees, or subscriptions in any type of coin. +/// The `CoinPayment` struct holds the details of such payments, including the amount, recipient, and payment category. +/// +/// # Features +/// - Users can create recurring payment instructions to be executed as needed. +/// - Only the owner of the `CoinPayment` can destroy it. +/// - All payment executions emit events that can be tracked and audited. +/// - Payments can be categorized (e.g., mint fees, subscription fees), making it easier to manage and report financial activities. +/// +/// # Usage +/// +/// # Notes +/// `CoinPayment` must not be used in function parameters or return types, +/// as the value can be changed by anyone with reference to it. +/// +/// `CoinPayment` acts as a contract which holds the details of a payment instruction. +/// +/// # Example +/// ``` +/// Create a recurring payment instruction for a subscription fee. +/// let payment = coin_payment::create(100, recipient_address, "Subscription Fee"); +/// +/// // Execute the payment when due. +/// coin_payment::execute(&signer, &payment); +/// +/// // Optionally, destroy the payment instruction when the subscription ends, or if the payment is a one time payment. +/// coin_payment::destroy(payment); +/// ``` +/// +module only_on_aptos::coin_payment { + + use std::error; + use std::signer; + use std::string::String; + use aptos_framework::coin; + use aptos_framework::event; + + /// Amount must be greater than zero. + const EINVALID_AMOUNT: u64 = 1; + /// Insufficient coin balance to make the payment. + const EINSUFFICIENT_BALANCE: u64 = 2; + /// The caller is not the owner of the coin payment. + const ENOT_OWNER: u64 = 3; + + struct CoinPayment has store { + /// The amount of coin to be paid. + amount: u64, + /// The address to which the coin is to be paid to. + destination: address, + /// The category of this payment, e.g. mint fee, launchpad fee + category: String, + } + + #[event] + /// Event emitted when a coin payment of type `T` is made. + struct CoinPaymentEvent has drop, store { + from: address, + amount: u64, + destination: address, + category: String, + } + + /// Creates a new `CoinPayment` instance which is to be stored in a data structure for future reference. + /// `CoinPayment` acts as a contract which holds the details of a payment instruction. + /// `CoinPayment` must not be used in function parameters or return types, + /// as the value can be changed by anyone with reference to it. + public fun create(amount: u64, destination: address, category: String): CoinPayment { + assert!(amount > 0, error::invalid_argument(EINVALID_AMOUNT)); + CoinPayment { amount, destination, category } + } + + public fun execute(minter: &signer, coin_payment: &CoinPayment) { + let amount = amount(coin_payment); + let from = signer::address_of(minter); + assert!( + coin::balance(from) >= amount, + error::invalid_state(EINSUFFICIENT_BALANCE), + ); + + let destination = destination(coin_payment); + coin::transfer(minter, destination, amount); + + event::emit(CoinPaymentEvent { + from, + amount, + destination, + category: category(coin_payment), + }); + } + + public fun destroy(coin_payment: CoinPayment) { + let CoinPayment { amount: _, destination: _, category: _ } = coin_payment; + } + + public fun amount(coin_payment: &CoinPayment): u64 { + coin_payment.amount + } + + public fun destination(coin_payment: &CoinPayment): address { + coin_payment.destination + } + + public fun category(coin_payment: &CoinPayment): String { + coin_payment.category + } + + public fun set_amount(coin_payment: &mut CoinPayment, amount: u64) { + coin_payment.amount = amount; + } + + public fun set_destination(coin_payment: &mut CoinPayment, destination: address) { + coin_payment.destination = destination; + } + + public fun set_category(coin_payment: &mut CoinPayment, category: String) { + coin_payment.category = category; + } +} diff --git a/only-on-aptos/sources/collection_components.move b/only-on-aptos/sources/collection_components.move new file mode 100644 index 0000000..0e5487f --- /dev/null +++ b/only-on-aptos/sources/collection_components.move @@ -0,0 +1,266 @@ +module only_on_aptos::collection_components { + + use std::error; + use std::option; + use std::option::Option; + use std::signer; + use std::string::String; + use aptos_framework::object::{Self, ConstructorRef, Object}; + + use aptos_token_objects::collection; + use aptos_token_objects::collection::Collection; + use aptos_token_objects::royalty; + use only_on_aptos::migration_helper; + + use only_on_aptos::collection_properties; + use only_on_aptos::collection_properties::CollectionProperties; + + /// Collection refs does not exist on this object. + const ECOLLECTION_REFS_DOES_NOT_EXIST: u64 = 1; + /// The provided signer does not own the object. + const ENOT_OBJECT_OWNER: u64 = 2; + /// The field being changed is not mutable. + const EFIELD_NOT_MUTABLE: u64 = 3; + /// The collection does not have ExtendRef, so it is not extendable. + const ECOLLECTION_NOT_EXTENDABLE: u64 = 4; + /// The collection does not support forced transfers by collection owner. + const ECOLLECTION_NOT_TRANSFERABLE_BY_COLLECTION_OWNER: u64 = 5; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + struct CollectionRefs has key { + /// Used to mutate collection fields + mutator_ref: Option, + /// Used to mutate royalties + royalty_mutator_ref: Option, + /// Used to generate signer, needed for extending object if needed in the future. + extend_ref: Option, + /// Used to transfer the collection as the collection owner. + transfer_ref: Option, + } + + /// This function creates all the refs to extend the collection, mutate the collection and royalties. + public fun create_refs_and_properties(constructor_ref: &ConstructorRef): Object { + let collection_signer = &object::generate_signer(constructor_ref); + + init_collection_refs( + collection_signer, + option::some(collection::generate_mutator_ref(constructor_ref)), + option::some(royalty::generate_mutator_ref(object::generate_extend_ref(constructor_ref))), + option::some(object::generate_extend_ref(constructor_ref)), + option::some(object::generate_transfer_ref(constructor_ref)), + ); + + let properties = create_default_properties(true); + collection_properties::init(constructor_ref, properties); + + object::object_from_constructor_ref(constructor_ref) + } + + fun create_default_properties(value: bool): CollectionProperties { + collection_properties::create_uninitialized_properties(value, value, value, value, value, value, value, value, value) + } + + public fun set_collection_description( + collection_owner: &signer, + collection: Object, + description: String, + ) acquires CollectionRefs { + assert!(is_mutable_description(collection), error::permission_denied(EFIELD_NOT_MUTABLE)); + collection::set_description( + option::borrow(&authorized_borrow_refs_mut(collection, collection_owner).mutator_ref), + description, + ); + } + + public fun set_collection_uri( + collection_owner: &signer, + collection: Object, + uri: String, + ) acquires CollectionRefs { + assert!(is_mutable_uri(collection), error::permission_denied(EFIELD_NOT_MUTABLE)); + collection::set_uri(option::borrow(&authorized_borrow_refs_mut(collection, collection_owner).mutator_ref), uri); + } + + public fun set_collection_royalties( + collection_owner: &signer, + collection: Object, + royalty: royalty::Royalty, + ) acquires CollectionRefs { + assert!(is_mutable_royalty(collection), error::permission_denied(EFIELD_NOT_MUTABLE)); + royalty::update( + option::borrow(&authorized_borrow_refs_mut(collection, collection_owner).royalty_mutator_ref), + royalty + ); + } + + /// Force transfer a collection as the collection owner. + /// Feature only works if the `TransferRef` is stored in the `CollectionRefs`. + public fun transfer_as_owner( + collection_owner: &signer, + collection: Object, + to_addr: address, + ) acquires CollectionRefs { + let transfer_ref = &authorized_borrow_refs_mut(collection, collection_owner).transfer_ref; + assert!(option::is_some(transfer_ref), error::not_found(ECOLLECTION_NOT_TRANSFERABLE_BY_COLLECTION_OWNER)); + + let linear_transfer_ref = object::generate_linear_transfer_ref(option::borrow(transfer_ref)); + object::transfer_with_ref(linear_transfer_ref, to_addr) + } + + inline fun authorized_borrow_refs_mut( + collection: Object, + collection_owner: &signer + ): &mut CollectionRefs { + assert_owner(signer::address_of(collection_owner), collection); + borrow_global_mut(collection_refs_address(collection)) + } + + inline fun assert_owner(collection_owner: address, obj: Object) { + assert!( + object::owner(obj) == collection_owner, + error::permission_denied(ENOT_OBJECT_OWNER), + ); + } + + fun init_collection_refs( + collection_object_signer: &signer, + mutator_ref: Option, + royalty_mutator_ref: Option, + extend_ref: Option, + transfer_ref: Option, + ) { + move_to(collection_object_signer, CollectionRefs { + mutator_ref, + royalty_mutator_ref, + extend_ref, + transfer_ref, + }); + } + + /// Can only be called if the `collection_owner` is the owner of the collection. + public fun collection_object_signer( + collection_owner: &signer, + collection: Object, + ): signer acquires CollectionRefs { + let extend_ref = &authorized_borrow_refs_mut(collection, collection_owner).extend_ref; + assert!(option::is_some(extend_ref), ECOLLECTION_NOT_EXTENDABLE); + + object::generate_signer_for_extending(option::borrow(extend_ref)) + } + + #[view] + public fun collection_refs_exist(obj_address: address): bool { + exists(obj_address) + } + + #[view] + public fun is_mutable_description(obj: Object): bool { + collection_properties::is_mutable_description(obj) + } + + #[view] + public fun is_mutable_uri(obj: Object): bool { + collection_properties::is_mutable_uri(obj) + } + + #[view] + public fun is_mutable_royalty(obj: Object): bool { + collection_properties::is_mutable_royalty(obj) + } + + // ================================== MIGRATE OUT FUNCTIONS ================================== // + /// Migration function used for migrating the refs from one object to another. + /// This is called when the contract has been upgraded to a new address and version. + /// This function is used to migrate the refs from the old object to the new object. + /// + /// Only the migration contract is allowed to call migration functions. The user must + /// call the migration function on the migration contract to migrate. + /// + /// To migrate in to the new contract, the `ExtendRef` must be present as the `ExtendRef` + /// is used to generate the collection object signer. + + public fun migrate_out_extend_ref( + migration_signer: &signer, + collection_owner: &signer, + collection: Object, + ): Option acquires CollectionRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection, collection_owner); + let extend_ref = extract_ref_if_present(&mut refs.extend_ref); + destroy_collection_refs_if_all_refs_migrated(refs, object::object_address(&collection)); + extend_ref + } + + public fun migrate_out_mutator_ref( + migration_signer: &signer, + collection_owner: &signer, + collection: Object, + ): Option acquires CollectionRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection, collection_owner); + let mutator_ref = extract_ref_if_present(&mut refs.mutator_ref); + destroy_collection_refs_if_all_refs_migrated(refs, object::object_address(&collection)); + mutator_ref + } + + public fun migrate_out_royalty_mutator_ref( + migration_signer: &signer, + collection_owner: &signer, + collection: Object, + ): Option acquires CollectionRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection, collection_owner); + let royalty_mutator_ref = extract_ref_if_present(&mut refs.royalty_mutator_ref); + destroy_collection_refs_if_all_refs_migrated(refs, object::object_address(&collection)); + royalty_mutator_ref + } + + public fun migrate_out_transfer_ref( + migration_signer: &signer, + collection_owner: &signer, + collection: Object, + ): Option acquires CollectionRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection, collection_owner); + let transfer_ref = extract_ref_if_present(&mut refs.transfer_ref); + destroy_collection_refs_if_all_refs_migrated(refs, object::object_address(&collection)); + transfer_ref + } + + fun extract_ref_if_present(ref: &mut Option): Option { + if (option::is_some(ref)) { + option::some(option::extract(ref)) + } else { + option::none() + } + } + + inline fun destroy_collection_refs_if_all_refs_migrated( + collection_refs: &mut CollectionRefs, + collection_address: address, + ) acquires CollectionRefs { + if (option::is_none(&collection_refs.mutator_ref) + && option::is_none(&collection_refs.royalty_mutator_ref) + && option::is_none(&collection_refs.extend_ref)) { + let CollectionRefs { + mutator_ref: _, + royalty_mutator_ref: _, + extend_ref: _, + transfer_ref: _, + } = move_from(collection_address); + } + } + + fun collection_refs_address(collection: Object): address { + let collection_address = object::object_address(&collection); + assert!( + collection_refs_exist(collection_address), + error::not_found(ECOLLECTION_REFS_DOES_NOT_EXIST) + ); + collection_address + } +} diff --git a/only-on-aptos/sources/collection_properties.move b/only-on-aptos/sources/collection_properties.move new file mode 100644 index 0000000..5eedef6 --- /dev/null +++ b/only-on-aptos/sources/collection_properties.move @@ -0,0 +1,355 @@ +module only_on_aptos::collection_properties { + + use std::error; + use std::signer; + use std::string; + use std::string::String; + use aptos_framework::event; + use aptos_framework::object::{Self, ConstructorRef, Object}; + use only_on_aptos::migration_helper; + + /// Collection properties does not exist on this object. + const ECOLLECTION_PROPERTIES_DOES_NOT_EXIST: u64 = 1; + /// The signer is not the owner of the object. + const ENOT_OBJECT_OWNER: u64 = 2; + /// The collection property is already initialized. + const ECOLLECTION_PROPERTY_ALREADY_INITIALIZED: u64 = 3; + + struct CollectionProperty has copy, drop, store { + value: bool, + initialized: bool, + } + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + struct CollectionProperties has copy, drop, key { + /// Determines if the collection owner can mutate the collection_properties's description + mutable_description: CollectionProperty, + /// Determines if the collection owner can mutate the collection_properties's uri + mutable_uri: CollectionProperty, + /// Determines if the collection owner can mutate token descriptions + mutable_token_description: CollectionProperty, + /// Determines if the collection owner can mutate token names + mutable_token_name: CollectionProperty, + /// Determines if the collection owner can mutate token properties + mutable_token_properties: CollectionProperty, + /// Determines if the collection owner can mutate token uris + mutable_token_uri: CollectionProperty, + /// Determines if the collection owner can change royalties + mutable_royalty: CollectionProperty, + /// Determines if the collection owner can burn tokens + tokens_burnable_by_collection_owner: CollectionProperty, + /// Determines if the collection owner can transfer tokens + tokens_transferable_by_collection_owner: CollectionProperty, + } + + #[event] + /// Event emitted when CollectionProperties are created. + struct InitCollectionProperties has drop, store { + mutable_description: CollectionProperty, + mutable_uri: CollectionProperty, + mutable_token_description: CollectionProperty, + mutable_token_name: CollectionProperty, + mutable_token_properties: CollectionProperty, + mutable_token_uri: CollectionProperty, + mutable_royalty: CollectionProperty, + tokens_burnable_by_collection_owner: CollectionProperty, + tokens_transferable_by_collection_owner: CollectionProperty, + } + + #[event] + /// Contains the mutated fields name. This makes the life of indexers easier, so that they can + /// directly understand the behavior in a writeset. + struct Mutation has drop, store { + mutated_field_name: String, + } + + /// Creates a new CollectionProperties resource with the values provided. + /// These are initialized as `false`, and can only be changed once with the setter functions. + public fun create_uninitialized_properties( + mutable_description: bool, + mutable_uri: bool, + mutable_token_description: bool, + mutable_token_name: bool, + mutable_token_properties: bool, + mutable_token_uri: bool, + mutable_royalty: bool, + tokens_burnable_by_collection_owner: bool, + tokens_transferable_by_collection_owner: bool, + ): CollectionProperties { + CollectionProperties { + mutable_description: create_property(mutable_description, false), + mutable_uri: create_property(mutable_uri, false), + mutable_token_description: create_property(mutable_token_description, false), + mutable_token_name: create_property(mutable_token_name, false), + mutable_token_properties: create_property(mutable_token_properties, false), + mutable_token_uri: create_property(mutable_token_uri, false), + mutable_royalty: create_property(mutable_royalty, false), + tokens_burnable_by_collection_owner: create_property(tokens_burnable_by_collection_owner, false), + tokens_transferable_by_collection_owner: create_property(tokens_transferable_by_collection_owner, false), + } + } + + public fun create_property(value: bool, initialized: bool): CollectionProperty { + CollectionProperty { value, initialized } + } + + public fun init(constructor_ref: &ConstructorRef, properties: CollectionProperties): Object { + let collection_signer = &object::generate_signer(constructor_ref); + move_to(collection_signer, properties); + + event::emit(InitCollectionProperties { + mutable_description: properties.mutable_description, + mutable_uri: properties.mutable_uri, + mutable_token_description: properties.mutable_token_description, + mutable_token_name: properties.mutable_token_name, + mutable_token_properties: properties.mutable_token_properties, + mutable_token_uri: properties.mutable_token_uri, + mutable_royalty: properties.mutable_royalty, + tokens_burnable_by_collection_owner: properties.tokens_burnable_by_collection_owner, + tokens_transferable_by_collection_owner: properties.tokens_transferable_by_collection_owner, + }); + + object::object_from_constructor_ref(constructor_ref) + } + + public fun set_mutable_description( + collection_owner: &signer, + obj: Object, + mutable_description: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).mutable_description; + set_property(property, mutable_description, string::utf8(b"mutable_description")); + } + + public fun set_mutable_uri( + collection_owner: &signer, + obj: Object, + mutable_uri: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).mutable_uri; + set_property(property, mutable_uri, string::utf8(b"mutable_uri")); + } + + public fun set_mutable_token_description( + collection_owner: &signer, + obj: Object, + mutable_token_description: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).mutable_token_description; + set_property(property, mutable_token_description, string::utf8(b"mutable_token_description")); + } + + public fun set_mutable_token_name( + collection_owner: &signer, + obj: Object, + mutable_token_name: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).mutable_token_name; + set_property(property, mutable_token_name, string::utf8(b"mutable_token_name")); + } + + public fun set_mutable_token_properties( + collection_owner: &signer, + obj: Object, + mutable_token_properties: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).mutable_token_properties; + set_property(property, mutable_token_properties, string::utf8(b"mutable_token_properties")); + } + + public fun set_mutable_token_uri( + collection_owner: &signer, + obj: Object, + mutable_token_uri: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).mutable_token_uri; + set_property(property, mutable_token_uri, string::utf8(b"mutable_uri")); + } + + public fun set_mutable_royalty( + collection_owner: &signer, + obj: Object, + mutable_royalty: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).mutable_royalty; + set_property(property, mutable_royalty, string::utf8(b"mutable_royalty")); + } + + public fun set_tokens_burnable_by_collection_owner( + collection_owner: &signer, + obj: Object, + tokens_burnable_by_collection_owner: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).tokens_burnable_by_collection_owner; + set_property( + property, + tokens_burnable_by_collection_owner, + string::utf8(b"tokens_burnable_by_collection_owner"), + ); + } + + public fun set_tokens_transferable_by_collection_owner( + collection_owner: &signer, + obj: Object, + tokens_transferable_by_collection_owner: bool, + ) acquires CollectionProperties { + let property = &mut authorized_borrow_mut(collection_owner, obj).tokens_transferable_by_collection_owner; + set_property( + property, + tokens_transferable_by_collection_owner, + string::utf8(b"tokens_transferable_by_collection_owner"), + ); + } + + fun set_property(property: &mut CollectionProperty, value: bool, mutated_field_name: String) { + assert!(!property.initialized, error::invalid_state(ECOLLECTION_PROPERTY_ALREADY_INITIALIZED)); + + property.value = value; + property.initialized = true; + + event::emit(Mutation { mutated_field_name }); + } + + inline fun borrow(obj: Object): &CollectionProperties acquires CollectionProperties { + assert!(collection_properties_exists(obj), error::not_found(ECOLLECTION_PROPERTIES_DOES_NOT_EXIST)); + borrow_global(object::object_address(&obj)) + } + + inline fun authorized_borrow_mut( + collection_owner: &signer, + obj: Object, + ): &mut CollectionProperties acquires CollectionProperties { + assert_owner(signer::address_of(collection_owner), obj); + assert!(collection_properties_exists(obj), error::not_found(ECOLLECTION_PROPERTIES_DOES_NOT_EXIST)); + + borrow_global_mut(object::object_address(&obj)) + } + + inline fun assert_owner(collection_owner: address, obj: Object) { + assert!( + object::owner(obj) == collection_owner, + error::permission_denied(ENOT_OBJECT_OWNER), + ); + } + + public fun mutable_description(properties: &CollectionProperties): (bool, bool) { + (properties.mutable_description.value, properties.mutable_description.initialized) + } + + public fun mutable_uri(properties: &CollectionProperties): (bool, bool) { + (properties.mutable_uri.value, properties.mutable_uri.initialized) + } + + public fun mutable_token_description(properties: &CollectionProperties): (bool, bool) { + (properties.mutable_token_description.value, properties.mutable_token_description.initialized) + } + + public fun mutable_token_name(properties: &CollectionProperties): (bool, bool) { + (properties.mutable_token_name.value, properties.mutable_token_name.initialized) + } + + public fun mutable_token_properties(properties: &CollectionProperties): (bool, bool) { + (properties.mutable_token_properties.value, properties.mutable_token_properties.initialized) + } + + public fun mutable_token_uri(properties: &CollectionProperties): (bool, bool) { + (properties.mutable_token_uri.value, properties.mutable_token_uri.initialized) + } + + public fun mutable_royalty(properties: &CollectionProperties): (bool, bool) { + (properties.mutable_royalty.value, properties.mutable_royalty.initialized) + } + + public fun tokens_burnable_by_collection_owner(properties: &CollectionProperties): (bool, bool) { + (properties.tokens_burnable_by_collection_owner.value, properties.tokens_burnable_by_collection_owner.initialized) + } + + public fun tokens_transferable_by_collection_owner(properties: &CollectionProperties): (bool, bool) { + (properties.tokens_transferable_by_collection_owner.value, properties.tokens_transferable_by_collection_owner.initialized) + } + + #[view] + public fun collection_properties_exists(obj: Object): bool { + exists(object::object_address(&obj)) + } + + #[view] + public fun is_mutable_description(obj: Object): bool acquires CollectionProperties { + borrow(obj).mutable_description.value + } + + #[view] + public fun is_mutable_uri(obj: Object): bool acquires CollectionProperties { + borrow(obj).mutable_uri.value + } + + #[view] + public fun is_mutable_token_description(obj: Object): bool acquires CollectionProperties { + borrow(obj).mutable_token_description.value + } + + #[view] + public fun is_mutable_token_name(properties: Object): bool acquires CollectionProperties { + borrow(properties).mutable_token_name.value + } + + #[view] + public fun is_mutable_token_properties(obj: Object): bool acquires CollectionProperties { + borrow(obj).mutable_token_properties.value + } + + #[view] + public fun is_mutable_token_uri(obj: Object): bool acquires CollectionProperties { + borrow(obj).mutable_token_uri.value + } + + #[view] + public fun is_mutable_royalty(obj: Object): bool acquires CollectionProperties { + borrow(obj).mutable_royalty.value + } + + #[view] + public fun is_tokens_burnable_by_collection_owner(obj: Object): bool acquires CollectionProperties { + borrow(obj).tokens_burnable_by_collection_owner.value + } + + #[view] + public fun is_tokens_transferable_by_collection_owner(obj: Object): bool acquires CollectionProperties { + borrow(obj).tokens_transferable_by_collection_owner.value + } + + // ================================== MIGRATE OUT FUNCTIONS ================================== // + /// Migration function used for migrating the refs from one object to another. + /// This is called when the contract has been upgraded to a new address and version. + /// This function is used to migrate the refs from the old object to the new object. + /// + /// Only the migration contract is allowed to call migration functions. The user must + /// call the migration function on the migration contract to migrate. + /// + /// To migrate in to the new contract, the `ExtendRef` must be present as the `ExtendRef` + /// is used to generate the collection object signer. + + public fun migrate_out_collection_properties( + migration_signer: &signer, + collection_owner: &signer, + obj: Object, + ): CollectionProperties acquires CollectionProperties { + migration_helper::assert_migration_object_signer(migration_signer); + + let properties = *authorized_borrow_mut(collection_owner, obj); + + let CollectionProperties { + mutable_description: _, + mutable_uri: _, + mutable_token_description: _, + mutable_token_name: _, + mutable_token_properties: _, + mutable_token_uri: _, + mutable_royalty: _, + tokens_burnable_by_collection_owner: _, + tokens_transferable_by_collection_owner: _, + } = move_from(object::object_address(&obj)); + + properties + } +} diff --git a/only-on-aptos/sources/migration/migration_helper.move b/only-on-aptos/sources/migration/migration_helper.move new file mode 100644 index 0000000..c0c2927 --- /dev/null +++ b/only-on-aptos/sources/migration/migration_helper.move @@ -0,0 +1,22 @@ +module only_on_aptos::migration_helper { + + use std::signer; + use aptos_framework::object; + + /// Caller not authorized to call migration functions. + const ENOT_MIGRATION_SIGNER: u64 = 1; + + // The seed for the migration contract + const MIGRATION_CONTRACT_SEED: vector = b"only_on_aptos::migration_contract"; + + #[view] + /// Helper function to get the address of the migration object signer. + public fun migration_object_address(): address { + object::create_object_address(&@only_on_aptos, MIGRATION_CONTRACT_SEED) + } + + public fun assert_migration_object_signer(migration_signer: &signer) { + let migration_object_signer = migration_object_address(); + assert!(signer::address_of(migration_signer) == migration_object_signer, ENOT_MIGRATION_SIGNER); + } +} diff --git a/only-on-aptos/sources/only_on_aptos.move b/only-on-aptos/sources/only_on_aptos.move index aa2005e..8223e3c 100644 --- a/only-on-aptos/sources/only_on_aptos.move +++ b/only-on-aptos/sources/only_on_aptos.move @@ -19,10 +19,10 @@ module only_on_aptos::only_on_aptos { use aptos_token_objects::token; use aptos_token_objects::token::Token; - use minter::collection_components; - use minter::collection_properties; - use minter::token_components; - use minter::transfer_token; + use only_on_aptos::collection_components; + use only_on_aptos::collection_properties; + use only_on_aptos::token_components; + use only_on_aptos::transfer_token; /// The provided signer is not the collection owner. const ENOT_OWNER: u64 = 1; diff --git a/only-on-aptos/sources/token_components.move b/only-on-aptos/sources/token_components.move new file mode 100644 index 0000000..796338a --- /dev/null +++ b/only-on-aptos/sources/token_components.move @@ -0,0 +1,389 @@ +module only_on_aptos::token_components { + + use std::error; + use std::option; + use std::option::Option; + use std::signer; + use std::string::String; + use aptos_framework::object; + use aptos_framework::object::{ConstructorRef, Object}; + + use aptos_token_objects::property_map; + use aptos_token_objects::token; + use aptos_token_objects::token::Token; + use only_on_aptos::migration_helper; + + use only_on_aptos::collection_properties; + + /// Token refs does not exist on this object. + const ETOKEN_REFS_DOES_NOT_EXIST: u64 = 1; + /// The provided signer does not own the token's collection. + const ENOT_TOKEN_COLLECTION_OWNER: u64 = 2; + /// The field being changed is not mutable. + const EFIELD_NOT_MUTABLE: u64 = 3; + /// The token being burned is not burnable. + const ETOKEN_NOT_BURNABLE: u64 = 4; + /// The property map being mutated is not mutable. + const EPROPERTIES_NOT_MUTABLE: u64 = 5; + /// The token does not support forced transfers by collection owner. + const ETOKEN_NOT_TRANSFERABLE_BY_COLLECTION_OWNER: u64 = 6; + /// The token does not have ExtendRef, so it is not extendable. + const ETOKEN_NOT_EXTENDABLE: u64 = 7; + /// This token is not owned by the address. + const ENOT_TOKEN_OWNER: u64 = 8; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + struct TokenRefs has key { + /// Used to generate signer for the token. Can be used for extending the + /// token or transferring out objects from the token + extend_ref: Option, + /// Used to burn. + burn_ref: Option, + /// Used to control freeze. + transfer_ref: Option, + /// Used to mutate fields + mutator_ref: Option, + /// Used to mutate properties + property_mutator_ref: Option, + } + + public fun create_refs(constructor_ref: &ConstructorRef): Object { + let token_signer = &object::generate_signer(constructor_ref); + + move_to(token_signer, TokenRefs { + extend_ref: option::some(object::generate_extend_ref(constructor_ref)), + burn_ref: option::some(token::generate_burn_ref(constructor_ref)), + transfer_ref: option::some(object::generate_transfer_ref(constructor_ref)), + mutator_ref: option::some(token::generate_mutator_ref(constructor_ref)), + property_mutator_ref: option::some(property_map::generate_mutator_ref(constructor_ref)), + }); + + // Initialize property map with empty properties + property_map::init(constructor_ref, property_map::prepare_input(vector[], vector[], vector[])); + + object::object_from_constructor_ref(constructor_ref) + } + + /// Force transfer a token as the collection owner. Feature only works if + /// the `TransferRef` is stored in the `TokenRefs`. + public fun transfer_as_collection_owner( + collection_owner: &signer, + token: Object, + to_addr: address, + ) acquires TokenRefs { + assert!( + is_transferable_by_collection_owner(token), + error::permission_denied(ETOKEN_NOT_TRANSFERABLE_BY_COLLECTION_OWNER), + ); + let transfer_ref = &authorized_borrow_refs_mut(collection_owner, token).transfer_ref; + assert!(option::is_some(transfer_ref), error::not_found(ETOKEN_NOT_TRANSFERABLE_BY_COLLECTION_OWNER)); + + let linear_transfer_ref = object::generate_linear_transfer_ref(option::borrow(transfer_ref)); + object::transfer_with_ref(linear_transfer_ref, to_addr) + } + + public fun freeze_transfer(collection_owner: &signer, token: Object) acquires TokenRefs { + assert!(is_transferable_by_collection_owner(token), error::permission_denied( + ETOKEN_NOT_TRANSFERABLE_BY_COLLECTION_OWNER + )); + let transfer_ref = &authorized_borrow_refs_mut(collection_owner, token).transfer_ref; + assert!(option::is_some(transfer_ref), error::not_found(ETOKEN_NOT_TRANSFERABLE_BY_COLLECTION_OWNER)); + + object::disable_ungated_transfer(option::borrow(transfer_ref)); + } + + public fun unfreeze_transfer(collection_owner: &signer, token: Object) acquires TokenRefs { + assert!( + is_transferable_by_collection_owner(token), error::permission_denied( + ETOKEN_NOT_TRANSFERABLE_BY_COLLECTION_OWNER + )); + let transfer_ref = &authorized_borrow_refs_mut(collection_owner, token).transfer_ref; + assert!(option::is_some(transfer_ref), error::not_found(ETOKEN_NOT_TRANSFERABLE_BY_COLLECTION_OWNER)); + + object::enable_ungated_transfer(option::borrow(transfer_ref)); + } + + public fun set_description( + collection_owner: &signer, + token: Object, + description: String, + ) acquires TokenRefs { + assert!(is_mutable_description(token), error::permission_denied(EFIELD_NOT_MUTABLE)); + let mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).mutator_ref; + assert!(option::is_some(mutator_ref), error::not_found(EFIELD_NOT_MUTABLE)); + + token::set_description(option::borrow(mutator_ref), description); + } + + public fun set_name(collection_owner: &signer, token: Object, name: String) acquires TokenRefs { + assert!(is_mutable_name(token), error::permission_denied(EFIELD_NOT_MUTABLE)); + let mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).mutator_ref; + assert!(option::is_some(mutator_ref), error::not_found(EFIELD_NOT_MUTABLE)); + + token::set_name(option::borrow(mutator_ref), name); + } + + public fun set_uri(collection_owner: &signer, token: Object, uri: String) acquires TokenRefs { + assert!(is_mutable_uri(token), error::permission_denied(EFIELD_NOT_MUTABLE)); + let mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).mutator_ref; + assert!(option::is_some(mutator_ref), error::not_found(EFIELD_NOT_MUTABLE)); + + token::set_uri(option::borrow(mutator_ref), uri); + } + + public fun add_property( + collection_owner: &signer, + token: Object, + key: String, + type: String, + value: vector, + ) acquires TokenRefs { + assert!(are_properties_mutable(token), error::permission_denied(EPROPERTIES_NOT_MUTABLE)); + let property_mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).property_mutator_ref; + assert!(option::is_some(property_mutator_ref), error::not_found(EPROPERTIES_NOT_MUTABLE)); + + property_map::add(option::borrow(property_mutator_ref), key, type, value); + } + + public fun add_typed_property( + collection_owner: &signer, + token: Object, + key: String, + value: V, + ) acquires TokenRefs { + assert!(are_properties_mutable(token), error::permission_denied(EPROPERTIES_NOT_MUTABLE)); + let property_mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).property_mutator_ref; + assert!(option::is_some(property_mutator_ref), error::not_found(EPROPERTIES_NOT_MUTABLE)); + + property_map::add_typed(option::borrow(property_mutator_ref), key, value); + } + + public fun remove_property(collection_owner: &signer, token: Object, key: String) acquires TokenRefs { + assert!(are_properties_mutable(token), error::permission_denied(EPROPERTIES_NOT_MUTABLE)); + let property_mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).property_mutator_ref; + assert!(option::is_some(property_mutator_ref), error::not_found(EPROPERTIES_NOT_MUTABLE)); + + property_map::remove(option::borrow(property_mutator_ref), &key); + } + + public fun update_property( + collection_owner: &signer, + token: Object, + key: String, + type: String, + value: vector, + ) acquires TokenRefs { + assert!(are_properties_mutable(token), error::permission_denied(EPROPERTIES_NOT_MUTABLE)); + let property_mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).property_mutator_ref; + assert!(option::is_some(property_mutator_ref), error::not_found(EPROPERTIES_NOT_MUTABLE)); + + property_map::update(option::borrow(property_mutator_ref), &key, type, value); + } + + public fun update_typed_property( + collection_owner: &signer, + token: Object, + key: String, + value: V, + ) acquires TokenRefs { + assert!(are_properties_mutable(token), error::permission_denied(EPROPERTIES_NOT_MUTABLE)); + let property_mutator_ref = &authorized_borrow_refs_mut(collection_owner, token).property_mutator_ref; + assert!(option::is_some(property_mutator_ref), error::not_found(EPROPERTIES_NOT_MUTABLE)); + + property_map::update_typed(option::borrow(property_mutator_ref), &key, value); + } + + /// Allow borrowing the `TokenRefs` resource if the `collection_owner` owns the token's collection. + inline fun authorized_borrow_refs_mut(collection_owner: &signer, token: Object): &mut TokenRefs { + assert_token_collection_owner(signer::address_of(collection_owner), token); + let token_address = object::object_address(&token); + assert!(token_refs_exist(token_address), error::not_found(ETOKEN_REFS_DOES_NOT_EXIST)); + + borrow_global_mut(token_address) + } + + inline fun assert_token_collection_owner(collection_owner: address, token: Object) { + let collection = token::collection_object(token); + assert!( + object::owner(collection) == collection_owner, + error::permission_denied(ENOT_TOKEN_COLLECTION_OWNER), + ); + } + + /// Burn the `TokenRefs` object, making the Token immutable + public entry fun burn(collection_owner: &signer, token: Object) acquires TokenRefs { + assert_token_collection_owner(signer::address_of(collection_owner), token); + assert!(is_burnable(token), error::permission_denied(ETOKEN_NOT_BURNABLE)); + + let token_address = assert_token_refs_exist(token); + let token_refs = move_from(token_address); + let TokenRefs { + extend_ref: _, + burn_ref, + transfer_ref: _, + mutator_ref: _, + property_mutator_ref, + } = token_refs; + if (option::is_some(&property_mutator_ref)) { + property_map::burn(option::extract(&mut property_mutator_ref)); + }; + if (option::is_some(&burn_ref)) { + token::burn(option::extract(&mut burn_ref)); + }; + } + + /// Can only be called if the `collection_owner` is the owner of the collection the `token` belongs to. + public fun token_object_signer(collection_owner: &signer, token: Object): signer acquires TokenRefs { + let extend_ref = &authorized_borrow_refs_mut(collection_owner, token).extend_ref; + assert!(option::is_some(extend_ref), ETOKEN_NOT_EXTENDABLE); + + object::generate_signer_for_extending(option::borrow(extend_ref)) + } + + #[view] + public fun token_refs_exist(obj_address: address): bool { + exists(obj_address) + } + + #[view] + public fun are_properties_mutable(token: Object): bool { + collection_properties::is_mutable_token_properties(token::collection_object(token)) + } + + #[view] + public fun is_burnable(token: Object): bool { + collection_properties::is_tokens_burnable_by_collection_owner(token::collection_object(token)) + } + + #[view] + public fun is_transferable_by_collection_owner(token: Object): bool { + collection_properties::is_tokens_transferable_by_collection_owner(token::collection_object(token)) + } + + #[view] + public fun is_mutable_description(token: Object): bool { + collection_properties::is_mutable_token_description(token::collection_object(token)) + } + + #[view] + public fun is_mutable_name(token: Object): bool { + collection_properties::is_mutable_token_name(token::collection_object(token)) + } + + #[view] + public fun is_mutable_uri(token: Object): bool { + collection_properties::is_mutable_token_uri(token::collection_object(token)) + } + + // ================================== MIGRATE OUT FUNCTIONS ================================== // + /// Migration function used for migrating the refs from one object to another. + /// This is called when the contract has been upgraded to a new address and version. + /// This function is used to migrate the refs from the old object to the new object. + /// + /// Only the migration contract is allowed to call migration functions. The user must + /// call the migration function on the migration contract to migrate. + /// + /// To migrate in to the new contract, the `ExtendRef` must be present as the `ExtendRef` + /// is used to generate the token object signer. + + public fun migrate_out_extend_ref( + migration_signer: &signer, + collection_owner: &signer, + token: Object, + ): Option acquires TokenRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection_owner, token); + let extend_ref = extract_ref_if_present(&mut refs.extend_ref); + destroy_token_refs_if_all_refs_migrated(refs, object::object_address(&token)); + extend_ref + } + + public fun migrate_out_burn_ref( + migration_signer: &signer, + collection_owner: &signer, + token: Object, + ): Option acquires TokenRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection_owner, token); + let burn_ref = extract_ref_if_present(&mut refs.burn_ref); + destroy_token_refs_if_all_refs_migrated(refs, object::object_address(&token)); + burn_ref + } + + public fun migrate_out_transfer_ref( + migration_signer: &signer, + collection_owner: &signer, + token: Object, + ): Option acquires TokenRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection_owner, token); + let transfer_ref = extract_ref_if_present(&mut refs.transfer_ref); + destroy_token_refs_if_all_refs_migrated(refs, object::object_address(&token)); + transfer_ref + } + + public fun migrate_out_mutator_ref( + migration_signer: &signer, + collection_owner: &signer, + token: Object, + ): Option acquires TokenRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection_owner, token); + let mutator_ref = extract_ref_if_present(&mut refs.mutator_ref); + destroy_token_refs_if_all_refs_migrated(refs, object::object_address(&token)); + mutator_ref + } + + public fun migrate_out_property_mutator_ref( + migration_signer: &signer, + collection_owner: &signer, + token: Object, + ): Option acquires TokenRefs { + migration_helper::assert_migration_object_signer(migration_signer); + + let refs = authorized_borrow_refs_mut(collection_owner, token); + let property_mutator_ref = extract_ref_if_present(&mut refs.property_mutator_ref); + destroy_token_refs_if_all_refs_migrated(refs, object::object_address(&token)); + property_mutator_ref + } + + fun extract_ref_if_present(ref: &mut Option): Option { + if (option::is_some(ref)) { + option::some(option::extract(ref)) + } else { + option::none() + } + } + + inline fun destroy_token_refs_if_all_refs_migrated( + token_refs: &mut TokenRefs, + token_address: address + ) acquires TokenRefs { + if (option::is_none(&token_refs.extend_ref) + && option::is_none(&token_refs.burn_ref) + && option::is_none(&token_refs.transfer_ref) + && option::is_none(&token_refs.mutator_ref) + && option::is_none(&token_refs.property_mutator_ref)) { + let TokenRefs { + extend_ref: _, + burn_ref: _, + transfer_ref: _, + mutator_ref: _, + property_mutator_ref: _, + } = move_from(token_address); + } + } + + fun assert_token_refs_exist(token: Object): address { + let token_address = object::object_address(&token); + assert!( + token_refs_exist(token_address), + error::not_found(ETOKEN_REFS_DOES_NOT_EXIST) + ); + token_address + } +} diff --git a/only-on-aptos/sources/transfer_token.move b/only-on-aptos/sources/transfer_token.move new file mode 100644 index 0000000..5e2e0c6 --- /dev/null +++ b/only-on-aptos/sources/transfer_token.move @@ -0,0 +1,32 @@ +module only_on_aptos::transfer_token { + + use aptos_framework::object; + use aptos_framework::object::{ConstructorRef, Object}; + + use aptos_token_objects::token::Token; + + public fun transfer( + owner: &signer, + to: address, + token_constructor_ref: &ConstructorRef, + ): Object { + let token = object::object_from_constructor_ref(token_constructor_ref); + object::transfer(owner, token, to); + + token + } + + public fun transfer_soulbound( + to: address, + token_constructor_ref: &ConstructorRef, + ): Object { + let token = object::object_from_constructor_ref(token_constructor_ref); + let transfer_ref = &object::generate_transfer_ref(token_constructor_ref); + let linear_transfer_ref = object::generate_linear_transfer_ref(transfer_ref); + + object::transfer_with_ref(linear_transfer_ref, to); + object::disable_ungated_transfer(transfer_ref); + + token + } +}