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
7 changes: 6 additions & 1 deletion contracts/launchpad/Move.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[move]
version = 0
manifest_digest = "461044ADFC5BFF564B120D25764387C867ACD614C75866CF29CC57E3E5C0E117"
manifest_digest = "FB7BC10285E871AB700F3DC850EF22AB7AEB29A1EA8BCD02C80A1778E7635952"
deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697"

dependencies = [
Expand Down Expand Up @@ -77,3 +77,8 @@ dependencies = [
{ name = "Pseudorandom" },
{ name = "Sui" },
]

[move.toolchain-version]
compiler-version = "1.21.1"
edition = "legacy"
flavor = "sui"
92 changes: 92 additions & 0 deletions contracts/launchpad/sources/launchpad/discount/crypto.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
module ob_launchpad::crypto_utils {
use sui::ecdsa_k1;
use std::vector;
use std::debug::print;
use sui::object::{Self, ID};
use sui::address as sui_address;
use sui::hash::keccak256;

const EIncorrectSignature: u64 = 0;

const KECCAK256: u8 = 0;

public entry fun verify_message(
venue_id: ID,
addr: address,
nonce: u64,
addr_pubkey: vector<u8>,
signature: vector<u8>,
) {
let msg = construct_msg(
object::id_to_bytes(&venue_id),
address_to_bytes(addr),
address_to_bytes(sui_address::from_u256((nonce as u256))),
);

assert!(
ecdsa_k1::secp256k1_verify(&signature, &addr_pubkey, &msg, KECCAK256),
EIncorrectSignature
);
}

// This function converts a public key to a Sui address using Keccak-256 for hashing.
// The public key is expected to be in the form of a vector<u8> (byte array).
public fun public_key_to_sui_address(public_key: vector<u8>): address {
let data_to_hash = vector[];

// Append the Ed25519 signature scheme flag byte (0x00) to the data to hash.
vector::push_back(&mut data_to_hash, 0x00);

// Append the public key bytes to the data to hash.
let public_key_len = vector::length(&public_key);
let i = 0;
while (i < public_key_len) {
vector::push_back(&mut data_to_hash, *vector::borrow(&public_key, i));
i = i + 1;
};

print(&data_to_hash);

// Hash the data using Keccak-256.
sui_address::from_bytes(keccak256(&data_to_hash))
}

// This function converts a public key to a Sui address using Keccak-256 for hashing.
// The public key is expected to be in the form of a vector<u8> (byte array).
public fun public_key_to_sui_address_(public_key: vector<u8>): vector<u8> {
let data_to_hash = vector[];

// Append the Ed25519 signature scheme flag byte (0x00) to the data to hash.
vector::push_back(&mut data_to_hash, 0x00);

// Append the public key bytes to the data to hash.
let public_key_len = vector::length(&public_key);
let i = 0;
while (i < public_key_len) {
vector::push_back(&mut data_to_hash, *vector::borrow(&public_key, i));
i = i + 1;
};

print(&data_to_hash);

// Hash the data using Keccak-256.
keccak256(&data_to_hash)
}

public fun construct_msg(
listing_id: vector<u8>,
addr: vector<u8>,
nonce: vector<u8>,
): vector<u8> {
let msg = vector::empty();
vector::append(&mut msg, listing_id);
vector::append(&mut msg, addr);
vector::append(&mut msg, nonce);

msg
}

public fun address_to_bytes(addr: address): vector<u8> {
object::id_to_bytes(&object::id_from_address(addr))
}
}
127 changes: 127 additions & 0 deletions contracts/launchpad/sources/launchpad/discount/free_mint.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
module ob_launchpad::free_mint {
use sui::tx_context::TxContext;
use sui::table::{Self, Table};
use sui::object::ID;

use ob_launchpad::listing::{Self, Listing};
use ob_launchpad::marketplace::Marketplace;
use ob_launchpad::crypto_utils;

friend ob_launchpad::flat_fee;
friend ob_launchpad::fixed_price;

struct FreeMintList has store {
list: Table<address, State>,
}

struct State has store, drop {
// Bit flag:
// 1 --> Registered
// 2 --> Checked-in
// 3 --> Collected
phase: u8,
}

struct FreeMintPotato {
venue_id: ID,
addr: address,
}

public fun add_free_mint_address(
marketplace: &Marketplace,
listing: &mut Listing,
venue_id: ID,
addr: address,
ctx: &mut TxContext,
) {
listing::assert_listing_marketplace_match(marketplace, listing);
listing::assert_correct_admin_or_member(marketplace, listing, ctx);
listing::assert_venue(listing, venue_id);

let free_mint_exists = listing::free_mint_exists(listing, venue_id);

if (!free_mint_exists) {
listing::add_free_mint_internal(listing, venue_id, FreeMintList {
list: table::new(ctx),
});
};

let free_mint = listing::borrow_free_mint_mut<FreeMintList>(listing, venue_id);

assert!(!table::contains(&free_mint.list, addr), 0);

let disc = State { phase: 1 };
table::add(&mut free_mint.list, addr, disc);
}

public fun checkin_free_mint_with_sig(
listing: &mut Listing,
venue_id: ID,
addr: address,
nonce: u64,
addr_pubkey: vector<u8>,
signature: vector<u8>,
): FreeMintPotato {
listing::assert_venue(listing, venue_id);

// 1. Check if address is in the list
let free_mint = listing::borrow_free_mint_mut<FreeMintList>(listing, venue_id);
let state = table::borrow_mut(&mut free_mint.list, addr);

assert!(state.phase == 1, 0);

state.phase = 2;

crypto_utils::verify_message(
venue_id,
addr,
nonce,
addr_pubkey,
signature,
);

FreeMintPotato { venue_id, addr }
}

// To be called internally. Does not assert venue ID as it's asserted upstream
public(friend) fun apply_free_mint_if_any(
listing: &mut Listing,
venue_id: ID,
addr: address,
): bool {
let free_mint_exists = listing::free_mint_exists(listing, venue_id);

if (!free_mint_exists) {
false
} else {
let free_mint = listing::borrow_free_mint_mut<FreeMintList>(listing, venue_id);

let on_the_list = table::contains(&free_mint.list, addr);

if (!on_the_list) {
false
} else {
let state = table::borrow_mut(&mut free_mint.list, addr);
assert!(state.phase == 2, 0);

state.phase = 3;

true
}
}
}

public fun checkout_free_mint(
listing: &mut Listing,
potato: FreeMintPotato,
) {
let FreeMintPotato { venue_id, addr } = potato;
listing::assert_venue(listing, venue_id);

let free_mint = listing::borrow_free_mint_mut<FreeMintList>(listing, venue_id);

let state = table::remove(&mut free_mint.list, addr);

assert!(state.phase == 3, 0);
}
}
89 changes: 89 additions & 0 deletions contracts/launchpad/sources/launchpad/fees/fee_cut.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module ob_launchpad::fee_cut {
use sui::tx_context::TxContext;
use sui::table::{Self, Table};
use sui::table_vec::{Self, TableVec};
use sui::balance;
use sui::coin;
use sui::transfer;

use ob_launchpad::listing::{Self, Listing};
use ob_launchpad::marketplace::Marketplace;
use ob_launchpad::proceeds;

friend ob_launchpad::flat_fee;

struct FeeCutList has store {
list: Table<address, u64>,
addresses: TableVec<address>,
}

public fun add_fee_cut(
marketplace: &Marketplace,
listing: &mut Listing,
addr: address,
cut_amount: u64,
ctx: &mut TxContext,
) {
listing::assert_listing_marketplace_match(marketplace, listing);
listing::assert_correct_admin_or_member(marketplace, listing, ctx);

let fee_cut_exists = listing::fee_cut_exists(listing);

if (!fee_cut_exists) {
listing::add_fee_cut_internal(listing, FeeCutList {
list: table::new(ctx),
addresses: table_vec::empty(ctx),
});
};

let fee_cut = listing::borrow_fee_cut_mut<FeeCutList>(listing);

if (!table::contains(&fee_cut.list, addr)) {
table::add(&mut fee_cut.list, addr, cut_amount);
table_vec::push_back(&mut fee_cut.addresses, addr);
} else {
let cut = table::borrow_mut(&mut fee_cut.list, addr);
*cut = *cut + cut_amount;
};
}

public(friend) fun collect_fee_cuts_if_any<FT>(
marketplace: &Marketplace,
listing: &mut Listing,
ctx: &mut TxContext,
) {
listing::assert_listing_marketplace_match(marketplace, listing);
listing::assert_correct_admin_or_member(marketplace, listing, ctx);

let fee_cut_exists = listing::fee_cut_exists(listing);

// Pay fee cuts
if (fee_cut_exists) {
let fee_cut = listing::borrow_fee_cut_mut<FeeCutList>(listing);
let len = table_vec::length(&fee_cut.addresses);

while (len > 0) {
let fee_cut = listing::borrow_fee_cut_mut<FeeCutList>(listing);
let addr = table_vec::pop_back(&mut fee_cut.addresses);
let cut_amount = table::remove(&mut fee_cut.list, addr);

let proceeds = listing::borrow_proceeds_mut(listing);
let balance = proceeds::balance_mut<FT>(proceeds);

let cut_balance = balance::split<FT>(
balance,
cut_amount,
);

let cut = coin::from_balance(cut_balance, ctx);

transfer::public_transfer(
cut,
addr,
);

len = len - 1;
};
};
}
}
7 changes: 7 additions & 0 deletions contracts/launchpad/sources/launchpad/fees/flat_fee.move
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module ob_launchpad::flat_fee {
use ob_launchpad::proceeds;
use ob_launchpad::listing::{Self, Listing};
use ob_launchpad::marketplace::{Self as mkt, Marketplace};
use ob_launchpad::fee_cut;

/// `Listing` did not have `FlatFee` policy
const EInvalidFeePolicy: u64 = 1;
Expand Down Expand Up @@ -55,6 +56,12 @@ module ob_launchpad::flat_fee {
listing::assert_listing_marketplace_match(marketplace, listing);
listing::assert_correct_admin_or_member(marketplace, listing, ctx);

fee_cut::collect_fee_cuts_if_any<FT>(
marketplace,
listing,
ctx,
);

let (proceeds_value, listing_receiver) = {
let proceeds = listing::borrow_proceeds(listing);
let listing_receiver = listing::receiver(listing);
Expand Down
Loading