Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions kiosk/Move.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[move]
version = 3
manifest_digest = "E5E2B73D4A2E45CD2F3F1BBF8D2EDEB17C41E4A6CF5685C7049EE342D5245487"
manifest_digest = "955C4B6EC9AB5C70702544008200CFFADC7FBACC70C9868A8020680FCE4959E8"
deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082"

dependencies = [
Expand All @@ -22,8 +22,8 @@ dependencies = [
]

[move.toolchain-version]
compiler-version = "1.45.2"
edition = "2024"
compiler-version = "1.54.2"
edition = "2024.beta"
flavor = "sui"

[env]
Expand Down
2 changes: 1 addition & 1 deletion kiosk/Move.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "Kiosk"
edition = "2024.beta" # or (just) "2024"
version = "0.0.2"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/mainnet" }

[addresses]
kiosk = "0x0"
257 changes: 128 additions & 129 deletions kiosk/sources/extensions/personal_kiosk.move
Original file line number Diff line number Diff line change
Expand Up @@ -5,153 +5,152 @@
/// This module provides a wrapper for the KioskOwnerCap that makes the Kiosk
/// non-transferable and "owned".
///
module kiosk::personal_kiosk {
use std::option::{Self, Option};
use sui::transfer;
use sui::kiosk::{Self, Kiosk, KioskOwnerCap};
use sui::object::{Self, ID, UID};
use sui::tx_context::{sender, TxContext};
use sui::dynamic_field as df;

/// Trying to return the Cap / Borrow to a wrong PersonalKioskCap object.
const EIncorrectCapObject: u64 = 0;
/// Trying to return the Cap / Borrow to a wrong PersonalKioskCap object.
const EIncorrectOwnedObject: u64 = 1;
/// Trying to get the owner of a non-personal Kiosk.
const EKioskNotOwned: u64 = 2;
/// Trying to make a someone else's Kiosk "personal".
const EWrongKiosk: u64 = 3;

/// A key-only wrapper for the KioskOwnerCap. Makes sure that the Kiosk can
/// not be traded altogether with its contents.
struct PersonalKioskCap has key {
id: UID,
cap: Option<KioskOwnerCap>
}
module kiosk::personal_kiosk;

use sui::dynamic_field as df;
use sui::kiosk::{Self, Kiosk, KioskOwnerCap};

/// Trying to return the Cap / Borrow to a wrong PersonalKioskCap object.
const EIncorrectCapObject: u64 = 0;
/// Trying to return the Cap / Borrow to a wrong PersonalKioskCap object.
const EIncorrectOwnedObject: u64 = 1;
/// Trying to get the owner of a non-personal Kiosk.
const EKioskNotOwned: u64 = 2;
/// Trying to make a someone else's Kiosk "personal".
const EWrongKiosk: u64 = 3;

/// A key-only wrapper for the KioskOwnerCap. Makes sure that the Kiosk can
/// not be traded altogether with its contents.
public struct PersonalKioskCap has key {
id: UID,
cap: Option<KioskOwnerCap>,
}

/// The hot potato making sure the KioskOwnerCap is returned after borrowing.
struct Borrow { cap_id: ID, owned_id: ID }
/// The hot potato making sure the KioskOwnerCap is returned after borrowing.
public struct Borrow { cap_id: ID, owned_id: ID }

/// The dynamic field to mark the Kiosk as owned (to allow guaranteed owner
/// checks through the Kiosk).
struct OwnerMarker has copy, store, drop {}
/// The dynamic field to mark the Kiosk as owned (to allow guaranteed owner
/// checks through the Kiosk).
public struct OwnerMarker has copy, drop, store {}

/// Event that is emitted when a new PersonalKioskCap is created. The sender
/// of the transaction is always the new Kiosk Owner.
struct NewPersonalKiosk has copy, drop { kiosk_id: ID }
/// Event that is emitted when a new PersonalKioskCap is created. The sender
/// of the transaction is always the new Kiosk Owner.
public struct NewPersonalKiosk has copy, drop { kiosk_id: ID }

/// The default setup for the PersonalKioskCap.
entry fun default(kiosk: &mut Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext) {
transfer_to_sender(new(kiosk, cap, ctx), ctx);
}
/// The default setup for the PersonalKioskCap.
entry fun default(kiosk: &mut Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext) {
transfer_to_sender(new(kiosk, cap, ctx), ctx);
}

/// Wrap the KioskOwnerCap making the Kiosk "owned" and non-transferable.
/// The `PersonalKioskCap` is returned to allow chaining within a PTB, but
/// the value must be consumed by the `transfer_to_sender` call in any case.
public fun new(
kiosk: &mut Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext
): PersonalKioskCap {
create(kiosk, cap, sender(ctx), ctx)
}
/// Wrap the KioskOwnerCap making the Kiosk "owned" and non-transferable.
/// The `PersonalKioskCap` is returned to allow chaining within a PTB, but
/// the value must be consumed by the `transfer_to_sender` call in any case.
public fun new(kiosk: &mut Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext): PersonalKioskCap {
create(kiosk, cap, ctx.sender(), ctx)
}

/// Create a `PersonalKiosk` for `recipient`.
/// This is useful when (e.g.) an admin account wants to mint an asset with
/// royalty enforcement on behalf of a user.
public fun create_for(
kiosk: &mut Kiosk, cap: KioskOwnerCap, recipient: address, ctx: &mut TxContext
) {
let personal_owner_cap = create(kiosk, cap, recipient, ctx);
transfer::transfer(personal_owner_cap, recipient)
}
/// Create a `PersonalKiosk` for `recipient`.
/// This is useful when (e.g.) an admin account wants to mint an asset with
/// royalty enforcement on behalf of a user.
public fun create_for(
kiosk: &mut Kiosk,
cap: KioskOwnerCap,
recipient: address,
ctx: &mut TxContext,
) {
let personal_owner_cap = create(kiosk, cap, recipient, ctx);
transfer::transfer(personal_owner_cap, recipient)
}

/// Wrap the KioskOwnerCap making the Kiosk "owned" and non-transferable.
/// The `PersonalKioskCap` is returned to allow chaining within a PTB, but
/// the value must be consumed by the `transfer_to_sender` call in any case.
fun create(
kiosk: &mut Kiosk, cap: KioskOwnerCap, owner: address, ctx: &mut TxContext
): PersonalKioskCap {
assert!(kiosk::has_access(kiosk, &cap), EWrongKiosk);

// set the owner property of the Kiosk
kiosk::set_owner_custom(kiosk, &cap, owner);

// add the owner marker to the Kiosk; uses `_as_owner` to always pass,
// even if Kiosk "allow_extensions" is set to false
df::add(
kiosk::uid_mut_as_owner(kiosk, &cap),
OwnerMarker {},
owner
);

sui::event::emit(NewPersonalKiosk {
kiosk_id: object::id(kiosk)
});

// wrap the Cap in the `PersonalKioskCap`
PersonalKioskCap {
id: object::new(ctx),
cap: option::some(cap)
}
/// Wrap the KioskOwnerCap making the Kiosk "owned" and non-transferable.
/// The `PersonalKioskCap` is returned to allow chaining within a PTB, but
/// the value must be consumed by the `transfer_to_sender` call in any case.
fun create(
kiosk: &mut Kiosk,
cap: KioskOwnerCap,
owner: address,
ctx: &mut TxContext,
): PersonalKioskCap {
assert!(kiosk.has_access(&cap), EWrongKiosk);

// set the owner property of the Kiosk
kiosk.set_owner_custom(&cap, owner);

// add the owner marker to the Kiosk; uses `_as_owner` to always pass,
// even if Kiosk "allow_extensions" is set to false
df::add(
kiosk::uid_mut_as_owner(kiosk, &cap),
OwnerMarker {},
owner,
);

sui::event::emit(NewPersonalKiosk {
kiosk_id: object::id(kiosk),
});

// wrap the Cap in the `PersonalKioskCap`
PersonalKioskCap {
id: object::new(ctx),
cap: option::some(cap),
}
}

/// Borrow the `KioskOwnerCap` from the `PersonalKioskCap` object.
public fun borrow(self: &PersonalKioskCap): &KioskOwnerCap {
option::borrow(&self.cap)
}
/// Borrow the `KioskOwnerCap` from the `PersonalKioskCap` object.
public fun borrow(self: &PersonalKioskCap): &KioskOwnerCap {
option::borrow(&self.cap)
}

/// Mutably borrow the `KioskOwnerCap` from the `PersonalKioskCap` object.
public fun borrow_mut(self: &mut PersonalKioskCap): &mut KioskOwnerCap {
option::borrow_mut(&mut self.cap)
}
/// Mutably borrow the `KioskOwnerCap` from the `PersonalKioskCap` object.
public fun borrow_mut(self: &mut PersonalKioskCap): &mut KioskOwnerCap {
option::borrow_mut(&mut self.cap)
}

/// Borrow the `KioskOwnerCap` from the `PersonalKioskCap` object; `Borrow`
/// hot-potato makes sure that the Cap is returned via `return_val` call.
public fun borrow_val(
self: &mut PersonalKioskCap
): (KioskOwnerCap, Borrow) {
let cap = option::extract(&mut self.cap);
let id = object::id(&cap);
/// Borrow the `KioskOwnerCap` from the `PersonalKioskCap` object; `Borrow`
/// hot-potato makes sure that the Cap is returned via `return_val` call.
public fun borrow_val(self: &mut PersonalKioskCap): (KioskOwnerCap, Borrow) {
let cap = option::extract(&mut self.cap);
let id = object::id(&cap);

(cap, Borrow {
(
cap,
Borrow {
owned_id: object::id(self),
cap_id: id
})
}
cap_id: id,
},
)
}

/// Return the Cap to the PersonalKioskCap object.
public fun return_val(
self: &mut PersonalKioskCap, cap: KioskOwnerCap, borrow: Borrow
) {
let Borrow { owned_id, cap_id } = borrow;
assert!(object::id(self) == owned_id, EIncorrectOwnedObject);
assert!(object::id(&cap) == cap_id, EIncorrectCapObject);
/// Return the Cap to the PersonalKioskCap object.
public fun return_val(self: &mut PersonalKioskCap, cap: KioskOwnerCap, borrow: Borrow) {
let Borrow { owned_id, cap_id } = borrow;
assert!(object::id(self) == owned_id, EIncorrectOwnedObject);
assert!(object::id(&cap) == cap_id, EIncorrectCapObject);

option::fill(&mut self.cap, cap)
}
option::fill(&mut self.cap, cap)
}

/// Check if the Kiosk is "personal".
public fun is_personal(kiosk: &Kiosk): bool {
df::exists_(kiosk::uid(kiosk), OwnerMarker {})
}
/// Check if the Kiosk is "personal".
public fun is_personal(kiosk: &Kiosk): bool {
df::exists_(kiosk::uid(kiosk), OwnerMarker {})
}

/// Get the owner of the Kiosk if the Kiosk is "personal". Aborts otherwise.
public fun owner(kiosk: &Kiosk): address {
assert!(is_personal(kiosk), EKioskNotOwned);
*df::borrow(kiosk::uid(kiosk), OwnerMarker {})
}
/// Get the owner of the Kiosk if the Kiosk is "personal". Aborts otherwise.
public fun owner(kiosk: &Kiosk): address {
assert!(is_personal(kiosk), EKioskNotOwned);
*df::borrow(kiosk::uid(kiosk), OwnerMarker {})
}

/// Try to get the owner of the Kiosk if the Kiosk is "personal". Returns
/// None otherwise.
public fun try_owner(kiosk: &Kiosk): Option<address> {
if (is_personal(kiosk)) {
option::some(owner(kiosk))
} else {
option::none()
}
/// Try to get the owner of the Kiosk if the Kiosk is "personal". Returns
/// None otherwise.
public fun try_owner(kiosk: &Kiosk): Option<address> {
if (is_personal(kiosk)) {
option::some(owner(kiosk))
} else {
option::none()
}
}

/// Transfer the `PersonalKioskCap` to the transaction sender.
public fun transfer_to_sender(self: PersonalKioskCap, ctx: &mut TxContext) {
transfer::transfer(self, sender(ctx));
}
/// Transfer the `PersonalKioskCap` to the transaction sender.
public fun transfer_to_sender(self: PersonalKioskCap, ctx: &mut TxContext) {
transfer::transfer(self, ctx.sender());
}
72 changes: 30 additions & 42 deletions kiosk/sources/rules/floor_price_rule.move
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,34 @@
/// - Defining a floor price for all trades of type T.
/// - Prevent trading of locked items with low amounts (e.g. by using purchase_cap).
///
module kiosk::floor_price_rule {
use sui::transfer_policy::{
Self as policy,
TransferPolicy,
TransferPolicyCap,
TransferRequest
};

/// The price was lower than the floor price.
const EPriceTooSmall: u64 = 0;

/// The "Rule" witness to authorize the policy.
struct Rule has drop {}

/// Configuration for the `Floor Price Rule`.
/// It holds the minimum price that an item can be sold at.
/// There can't be any sales with a price < than the floor_price.
struct Config has store, drop {
floor_price: u64
}

/// Creator action: Add the Floor Price Rule for the `T`.
/// Pass in the `TransferPolicy`, `TransferPolicyCap` and `floor_price`.
public fun add<T>(
policy: &mut TransferPolicy<T>,
cap: &TransferPolicyCap<T>,
floor_price: u64
) {
policy::add_rule(Rule {}, policy, cap, Config { floor_price })
}

/// Buyer action: Prove that the amount is higher or equal to the floor_price.
public fun prove<T>(
policy: &mut TransferPolicy<T>,
request: &mut TransferRequest<T>
) {
let config: &Config = policy::get_rule(Rule {}, policy);

assert!(policy::paid(request) >= config.floor_price, EPriceTooSmall);

policy::add_receipt(Rule {}, request)
}
module kiosk::floor_price_rule;

use sui::transfer_policy::{Self as policy, TransferPolicy, TransferPolicyCap, TransferRequest};

/// The price was lower than the floor price.
const EPriceTooSmall: u64 = 0;

/// The "Rule" witness to authorize the policy.
public struct Rule has drop {}

/// Configuration for the `Floor Price Rule`.
/// It holds the minimum price that an item can be sold at.
/// There can't be any sales with a price < than the floor_price.
public struct Config has drop, store {
floor_price: u64,
}

/// Creator action: Add the Floor Price Rule for the `T`.
/// Pass in the `TransferPolicy`, `TransferPolicyCap` and `floor_price`.
public fun add<T>(policy: &mut TransferPolicy<T>, cap: &TransferPolicyCap<T>, floor_price: u64) {
policy::add_rule(Rule {}, policy, cap, Config { floor_price })
}

/// Buyer action: Prove that the amount is higher or equal to the floor_price.
public fun prove<T>(policy: &mut TransferPolicy<T>, request: &mut TransferRequest<T>) {
let config: &Config = policy::get_rule(Rule {}, policy);

assert!(request.paid() >= config.floor_price, EPriceTooSmall);

policy::add_receipt(Rule {}, request)
}
Loading