Skip to content

English Auction

Soroban English Auction
#![no_std]
use soroban_sdk::{contract, contracttype, contractimpl, Address, Env, token, Symbol, log};
 
#[derive(Clone)]
#[contracttype]
pub enum DataKey {
    TicketSale(Address),
}
 
#[derive(Clone)]
#[contracttype]
pub enum ConstraintDataKey {
    TicketConstraint(Address)
}
 
#[derive(Clone)]
#[contracttype]
pub struct TicketConstraint {
    service_fee: i128,
    unitary_commission: i128,
    max_units_per_purchase: i128,
    max_units_per_account: i128,
}
 
#[derive(Clone)]
#[contracttype]
pub struct TicketSale {
    issuer: Address,
    distributor: Address,
    asset_code: Symbol,
    asset_address: Address,
    native_address: Address,
    price_per_unit: i128,
    available_units: i128,
}
 
#[contract]
pub struct TicketContract;
 
#[contractimpl]
impl TicketContract {
    /// Initiating new ticket sales
    pub fn initialize_sale (
        env: Env,
        issuer: Address,
        distributor: Address,
        asset_code: Symbol,
        asset_address: Address,
        native_address: Address,
        price_per_unit: i128,
        available_units: i128,
        service_fee: i128,
        unitary_commission: i128,
        max_units_per_purchase: i128
    ) {
        // Require authorisation from both issuer and distributor
        distributor.require_auth();
 
        // Transfer the credits from the distributor to the contract address
        let transfer_quantity = available_units.clone() * 100000;
 
        let contract = env.current_contract_address();
        let asset = token::Client::new(&env, &asset_address.clone());
        asset.transfer(&distributor.clone(), &contract, &transfer_quantity);
 
        // Initialize the ticket asset sale
        let ticket_sale = TicketSale {
            issuer: issuer.clone(),
            distributor: distributor.clone(),
            asset_code: asset_code.clone(),
            asset_address: asset_address.clone(),
            native_address: native_address.clone(),
            price_per_unit: price_per_unit.clone(),
            available_units: available_units.clone() / 100,
        };
 
        let ticket_constraint = TicketConstraint {
            service_fee: service_fee.clone(),
            unitary_commission: unitary_commission.clone(),
            max_units_per_purchase: max_units_per_purchase.clone() / 100,
            max_units_per_account: (max_units_per_purchase.clone() / 100) * 2,
        };
 
        let key = DataKey::TicketSale(asset_address.clone());
        env.storage().persistent().set(&key, &ticket_sale);
 
        let constraint_key = ConstraintDataKey::TicketConstraint(asset_address.clone());
        env.storage().persistent().set(&constraint_key, &ticket_constraint);
    }
 
    pub fn purchase (
        env: Env,
        asset_address: Address,
        amount: i128,
        buyer: Address,
    ) {
        // Require authentication from the buyer
        buyer.require_auth();
 
        let contract = env.current_contract_address();
 
        let ticket_sale: TicketSale = env.storage()
            .persistent()
            .get(&DataKey::TicketSale(asset_address.clone()))
            .unwrap();
 
        let ticket_constraint: TicketConstraint = env.storage()
            .persistent()
            .get(&ConstraintDataKey::TicketConstraint(asset_address.clone()))
            .unwrap();
 
        // Compute totals
        // Price does not have a decimal point (e.g. 9999 instead of 99.99)
        let buyer_service_fee = ticket_constraint.service_fee.clone();
        let tickets_to_buy = amount.clone() / 100;
        let buy_amount = (tickets_to_buy.clone() * ticket_constraint.service_fee.clone()) + buyer_service_fee.clone();
 
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "Buy amount: {}", buy_amount);
 
        // Check if the buyer has enough balance
        let native = token::Client::new(&env, &ticket_sale.native_address.clone());
        let native_balance = native.balance(&buyer.clone());
        if native_balance < buy_amount {
            panic!("Insufficient balance");
        }
 
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "Native balance: {}", native_balance);
 
        // Transfer total transfer amount from buyer to contract
        let purchase_total = buy_amount.clone() * 100000;
        let contract = env.current_contract_address();
        let xlm = token::Client::new(&env, &ticket_sale.native_address.clone());
        xlm.transfer(&buyer.clone(), &contract, &purchase_total);
 
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "Purchase total: {}", purchase_total);
 
        // Commission is saved as a percentage 100 = 1%
        let issuer_commission = ticket_constraint.unitary_commission.clone() * (tickets_to_buy.clone() * ticket_sale.price_per_unit.clone()) / 10000;
        let distributor_service_fee = ticket_constraint.service_fee.clone();
 
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "Issuer commission: {}", issuer_commission);
 
        // Transfer commissions from contract to issuer
        let amount_to_issuer = (issuer_commission.clone() + buyer_service_fee + distributor_service_fee.clone()) * 100000;
        xlm.transfer(&contract.clone(), &ticket_sale.issuer.clone(), &amount_to_issuer.clone());
 
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "Amount to issuer: {}", amount_to_issuer);
 
        // Transfer rest of sale from contract to distributor (owner)
        let amount_to_distributor = purchase_total - amount_to_issuer;
        xlm.transfer(&contract.clone(), &ticket_sale.distributor, &amount_to_distributor);
 
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "Amount to distributor: {}", amount_to_distributor);
 
        // Transfer asset from contract to buyer
        let asset = token::Client::new(&env, &asset_address.clone());
        asset.transfer(&contract, &buyer, &(amount * 100000));
    }
}

Explanation

#![no_std] This attribute prevents linking to the standard library, making the code lighter and more efficient for Soroban contracts. It's big so we save on size.

use soroban_sdk::{contract, contractimpl, Env, log} Imports stuffs from the Soroban SDK. Env is basic Soroban type, we need it because we can't use the Rust standard library.

pub Marks a function as external, meaning it can be invoked outside of the context of the contract code itself. struct enum This functionality in Rust allows one to define a type with a fixed set of values. It enables pattern matching, discriminant unions, as well as option and return types.

struct This is a user-defined data type that groups related data fields under a single name.

#[derive(Clone)] This attribute in Rust Soroban is used to automatically implement the Clone trait for a struct or enum. The Clone trait allows you to create a copy of an object, which is often necessary for various operations in programming.

#[contracttype] This attribute is used to define a custom contract type in Rust Soroban. Contract types are essential for defining the interface and behavior of contracts on the Soroban blockchain.

#[contract] Marks the struct as a Soroban smart contract. Soroban smart contracts are defined as Rust structs.

#[contractimpl] Marks the implementation block as containing contract methods and transforms it to code that Soroban can evaluate directly.

initialize_sale This function is responsible for setting up a ticket sale. After completing the authorization process, it transfers assets, initializes the sale and stores the sales data.

purchase This function handles the purchase of tickets in the sale. After completing the authorization process, it does the following:

  • retrieving sales details
  • calculating purchase details
  • checking buyer balance
  • transferring funds
  • calculating commissions
  • distributing funds

Run in Playground

Loading playground...