Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: ticket contract #85

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions ticketing_cameligo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode/
compiled/
111 changes: 111 additions & 0 deletions ticketing_cameligo/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
default: help

# Perl Colors, with fallback if tput command not available
GREEN := $(shell command -v tput >/dev/null 2>&1 && tput -Txterm setaf 2 || echo "")
BLUE := $(shell command -v tput >/dev/null 2>&1 && tput -Txterm setaf 4 || echo "")
WHITE := $(shell command -v tput >/dev/null 2>&1 && tput -Txterm setaf 7 || echo "")
YELLOW := $(shell command -v tput >/dev/null 2>&1 && tput -Txterm setaf 3 || echo "")
RESET := $(shell command -v tput >/dev/null 2>&1 && tput -Txterm sgr0 || echo "")

# Add help text after each target name starting with '\#\#'
# A category can be added with @category
HELP_FUN = \
%help; \
while(<>) { push @{$$help{$$2 // 'options'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
print "usage: make [target]\n\n"; \
for (sort keys %help) { \
print "${WHITE}$$_:${RESET}\n"; \
for (@{$$help{$$_}}) { \
$$sep = " " x (32 - length $$_->[0]); \
print " ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
}; \
print "\n"; }

help:
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)

#######################################
# PROJECT #
#######################################

all: install compile deploy ##@Project - Runs all the deployment chain from scratch

# nuke-all: ##@Project - Deletes IMPORTANT FILES & FOLDERS to reset to initial state
# @echo "Are you sure you want to UNPIN ALL FILES from your Pinata Cloud account ? [y/N]" && read ans && if [ $${ans:-'N'} = 'y' ]; then npx ts-node ./scripts/unpin; fi
# @echo "Are you sure you want to DELETE IMPORTANT FILES from this folder to RESET EVERYTHING to initial state ? [y/N]" && read ans && if [ $${ans:-'N'} = 'y' ]; then rm -rf node_modules/ assets/* compiled/* deployments/* ; fi

#######################################
# CONTRACTS #
#######################################
ifndef LIGO
LIGO=docker run --platform linux/amd64 --rm -v "$(PWD)":"$(PWD)" -w "$(PWD)" ligolang/ligo:0.49.0
endif

compile = $(LIGO) compile contract ./src/$(1) -o ./compiled/$(2) $(3)
# ^ Compile contracts to Michelson or Micheline

test-ligo = $(LIGO) run test ./test/ligo/$(1)
# ^ Run the given LIGO Test file

compile: ##@Contracts - Compile LIGO contracts
@if [ ! -d ./compiled ]; then mkdir ./compiled ; fi
@echo "Compiling contracts..."
## @$(call compile,generic_fa2/core/instance/NFT.mligo,fa2_nft.tz)
## @$(call compile,generic_fa2/core/instance/NFT.mligo,fa2_nft.json,--michelson-format json)
@$(call compile,main.mligo,ticketing.tz)
@$(call compile,main.mligo,ticketing.json,--michelson-format json)

.PHONY: test
test-ligo: ##@Contracts - Run LIGO tests
@$(call test-ligo,ticketing.test.mligo)
# @$(call test-ligo,nft.premint.test.mligo)
# @$(call test-ligo,nft.mint.test.mligo)
# @$(call test-ligo,nft.airdrop.test.mligo)
# @$(call test-ligo,nft.changeallocation.test.mligo)
# @$(call test-ligo,nft.changeminteedwallet.test.mligo)

test-integration: ##@Contracts - Run integration tests
$(MAKE) deploy
@npm run test

clean: ##@Contracts - Contracts clean up
@echo "Are you sure you want to DELETE ALL COMPILED CONTRACT FILES from your Compiled folder ? [y/N]" && read ans && if [ $${ans:-'N'} = 'y' ]; then rm -rf compiled/* ; fi

#######################################
# SCRIPTS #
#######################################
install: ##@Scripts - Install NPM dependencies
@if [ ! -f ./.env ]; then cp .env.dist .env ; fi
@npm i

deploy: ##@Scripts - Deploy contracts
@./scripts/deploy

# fixtures: ##@Scripts - Generate image fixtures
# @npx ts-node ./scripts/fixtures.ts

# prepare: ##@Scripts - Prepare a collection JSON
# @if [ ! -d ./assets/thumbnail ]; then mkdir assets/thumbnail ; fi
# @if [ ! -d ./assets/display ]; then mkdir assets/display ; fi
# @npx ts-node ./scripts/prepare.ts

# upload: ##@Scripts - Upload assets to IPFS and make their Metadata
# @npx ts-node ./scripts/upload.ts

# generate: ##@Scripts - Generate a collection (FACTORY=KTxx)
# @npx ts-node ./migrations/2_collection.ts $(FACTORY)

# local: ##@Scripts - Generate a JSON to avoid fetching IPFS
# @npx ts-node ./scripts/local.test

# unpin: ##@Scripts - Unpin (delete) all IPFS files on Pinata
# @echo "Are you sure you want to UNPIN ALL FILES from your Pinata Cloud account ? [y/N]" && read ans && if [ $${ans:-'N'} = 'y' ]; then npx ts-node ./scripts/unpin; fi

#######################################
# SANDBOX #
#######################################
sandbox-start: ##@Sandbox - Start Flextesa sandbox
@./scripts/run-sandbox

sandbox-stop: ##@Sandbox - Stop Flextesa sandbox
@docker stop sandbox
Empty file added ticketing_cameligo/README.md
Empty file.
2 changes: 2 additions & 0 deletions ticketing_cameligo/src/errors.mligo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let only_admin : string = "Only Admin"
let uuid_already_used : string = "The given optional UUID has already been used"
114 changes: 114 additions & 0 deletions ticketing_cameligo/src/generic_fa2/core/common/NFT.mligo
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
(**
This file implement the TZIP-12 protocol (a.k.a FA2) for NFT on Tezos
copyright Wulfman Corporation 2021
*)

#import "errors.mligo" "Errors"
#import "address.mligo" "Address"
#import "operators.mligo" "Operators"
#import "tokenMetadata.mligo" "TokenMetadata"
#import "ledger.mligo" "Ledger"
#import "storage.mligo" "Storage"

type storage = Storage.t

(** Transfer entrypoint *)
type atomic_trans = [@layout:comb] {
to_ : Address.t;
token_id : nat;
amount : nat;
}

type transfer_from = {
from_ : Address.t;
tx : atomic_trans list
}
type transfer = transfer_from list

let transfer (type a) (t:transfer) (s:a storage) : operation list * a storage =
(* This function process the "tx" list. Since all transfer share the same "from_" address, we use a se *)
let process_atomic_transfer (from_:Address.t) (ledger, t:Ledger.t * atomic_trans) =
// let {to_;token_id} = t in
// let () = Storage.assert_token_exist s token_id in
// let () = Operators.assert_authorisation s.operators from_ token_id in
// let ledger = Ledger.transfer_token_from_user_to_user ledger token_id from_ to_ in
// ledger
let {to_; token_id; amount=amount_} = t in
let () = Storage.assert_token_exist s token_id in
let () = Operators.assert_authorisation s.operators from_ token_id in
let ledger = Ledger.decrease_token_amount_for_user ledger from_ token_id amount_ in
let ledger = Ledger.increase_token_amount_for_user ledger to_ token_id amount_ in
ledger
in
let process_single_transfer (ledger, t:Ledger.t * transfer_from ) =
let {from_;tx} = t in
let ledger = List.fold_left (process_atomic_transfer from_) ledger tx in
ledger
in
let ledger = List.fold_left process_single_transfer s.ledger t in
let s = Storage.set_ledger s ledger in
([] : operation list), s

type request = {
owner : Address.t;
token_id : nat;
}

type callback = [@layout:comb] {
request : request;
balance : nat;
}

type balance_of = [@layout:comb] {
requests : request list;
callback : callback list contract;
}

(** Balance_of entrypoint *)
let balance_of (type a) (b: balance_of) (s: a storage) : operation list * a storage =
let {requests;callback} = b in
let get_balance_info (request : request) : callback =
let {owner;token_id} = request in
let balance_ = Storage.get_balance s owner token_id in
{request=request;balance=balance_}
in
let callback_param = List.map get_balance_info requests in
let operation = Tezos.transaction callback_param 0tez callback in
([operation]: operation list),s

(** Update_operators entrypoint *)
type operator = [@layout:comb] {
owner : Address.t;
operator : Address.t;
token_id : nat;
}
type unit_update = Add_operator of operator | Remove_operator of operator
type update_operators = unit_update list

let update_ops (type a) (updates: update_operators) (s: a storage) : operation list * a storage =
let update_operator (operators,update : Operators.t * unit_update) = match update with
Add_operator {owner=owner;operator=operator;token_id=token_id} -> Operators.add_operator operators owner operator token_id
| Remove_operator {owner=owner;operator=operator;token_id=token_id} -> Operators.remove_operator operators owner operator token_id
in
let operators = Storage.get_operators s in
let operators = List.fold_left update_operator operators updates in
let s = Storage.set_operators s operators in
([]: operation list),s

[@view] let get_balance (type a) (p, s : (Address.t * nat) * a storage) : nat =
let (owner, token_id) = p in
let balance_ = Storage.get_balance s owner token_id in
balance_

[@view] let total_supply (type a) ((token_id, s) : (nat * a storage)): nat =
let () = Storage.assert_token_exist s token_id in
1n

[@view] let all_tokens (type a) ((_, s) : (unit * a storage)): nat list =
s.token_ids

[@view] let is_operator (type a) ((op, s) : (operator * a storage)): bool =
Operators.is_operator (s.operators, op.owner, op.operator, op.token_id)

[@view] let token_metadata (type a) ((p, s) : (nat * a storage)): TokenMetadata.data =
TokenMetadata.get_token_metadata p s.token_metadata
8 changes: 8 additions & 0 deletions ticketing_cameligo/src/generic_fa2/core/common/address.mligo
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(**
This file implement the TZIP-12 protocol (a.k.a FA2) for NFT on Tezos
copyright Wulfman Corporation 2021
*)

type t = address

let equal (a : t) (b : t) = a = b
12 changes: 12 additions & 0 deletions ticketing_cameligo/src/generic_fa2/core/common/errors.mligo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type t = string

let undefined_token = "FA2_TOKEN_UNDEFINED"
let ins_balance = "FA2_INSUFFICIENT_BALANCE"
let not_owner = "FA2_NOT_OWNER"
let not_operator = "FA2_NOT_OPERATOR"

// Following error might be used in commented block Operators.mligo : 30
// let no_transfer = "FA2_TX_DENIED"

let only_sender_manage_operators = "The sender can only manage operators for his own token"
let only_admin = "FA2_NOT_ADMIN"
44 changes: 44 additions & 0 deletions ticketing_cameligo/src/generic_fa2/core/common/ledger.mligo
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
(**
This file implement the TZIP-12 protocol (a.k.a FA2) for NFT on Tezos
copyright Wulfman Corporation 2021
*)

#import "errors.mligo" "Errors"
#import "address.mligo" "Address"

type token_id = nat
type owner = Address.t
type amount_ = nat
type t = ((owner * token_id), amount_) big_map

// let is_owner_of (ledger:t) (token_id : token_id) (owner: Address.t) : bool =
// (** We already sanitized token_id, a failwith here indicated a patological storage *)
// let current_owner = Option.unopt (Big_map.find_opt token_id ledger) in
// Address.equal current_owner owner

// let assert_owner_of (ledger:t) (token_id : token_id) (owner: Address.t) : unit =
// assert_with_error (is_owner_of ledger token_id owner) Errors.ins_balance

// let transfer_token_from_user_to_user (ledger : t) (token_id : token_id) (from_ : owner) (to_ : owner) : t =
// let () = assert_owner_of ledger token_id from_ in
// let ledger = Big_map.update token_id (Some to_) ledger in
// ledger

let get_for_user (ledger:t) (owner: owner) (token_id : token_id) : amount_ =
match Big_map.find_opt (owner,token_id) ledger with Some (a) -> a | None -> 0n

let set_for_user (ledger:t) (owner: owner) (token_id : token_id ) (amount_:amount_) : t =
Big_map.update (owner,token_id) (Some amount_) ledger

let decrease_token_amount_for_user (ledger : t) (from_ : owner) (token_id : nat) (amount_ : nat) : t =
let balance_ = get_for_user ledger from_ token_id in
let () = assert_with_error (balance_ >= amount_) Errors.ins_balance in
let balance_ = abs (balance_ - amount_) in
let ledger = set_for_user ledger from_ token_id balance_ in
ledger

let increase_token_amount_for_user (ledger : t) (to_ : owner) (token_id : nat) (amount_ : nat) : t =
let balance_ = get_for_user ledger to_ token_id in
let balance_ = balance_ + amount_ in
let ledger = set_for_user ledger to_ token_id balance_ in
ledger
62 changes: 62 additions & 0 deletions ticketing_cameligo/src/generic_fa2/core/common/operators.mligo
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
(**
This file implement the TZIP-12 protocol (a.k.a FA2) for NFT on Tezos
copyright Wulfman Corporation 2021
*)

#import "errors.mligo" "Errors"
#import "address.mligo" "Address"

type owner = Address.t
type operator = Address.t
type token_id = nat
type t = ((owner * operator), token_id set) big_map

(** if transfer policy is Owner_or_operator_transfer *)
let assert_authorisation (operators : t) (from_ : Address.t) (token_id : nat) : unit =
let sender_ = Tezos.get_sender() in
if (Address.equal sender_ from_) then ()
else
let authorized = match Big_map.find_opt (from_,sender_) operators with
Some (a) -> a | None -> Set.empty
in if Set.mem token_id authorized then ()
else failwith Errors.not_operator
(** if transfer policy is Owner_transfer
let assert_authorisation (operators : t) (from_ : Address.t) : unit =
let sender_ = Tezos.sender in
if (sender_ = from_) then ()
else failwith Errors.not_owner
*)

(** if transfer policy is No_transfer
let assert_authorisation (operators : t) (from_ : Address.t) : unit =
failwith Errors.no_owner
*)

let is_operator (operators, owner, operator, token_id : (t * Address.t * Address.t * nat)) : bool =
let authorized = match Big_map.find_opt (owner,operator) operators with
Some (a) -> a | None -> Set.empty in
(owner = operator || Set.mem token_id authorized)

let assert_update_permission (owner : owner) : unit =
assert_with_error (Address.equal owner (Tezos.get_sender())) Errors.only_sender_manage_operators

let add_operator (operators : t) (owner : owner) (operator : operator) (token_id : token_id) : t =
if owner = operator then operators (* assert_authorisation always allow the owner so this case is not relevant *)
else
let () = assert_update_permission owner in
let auth_tokens = match Big_map.find_opt (owner,operator) operators with
Some (ts) -> ts | None -> Set.empty in
let auth_tokens = Set.add token_id auth_tokens in
Big_map.update (owner,operator) (Some auth_tokens) operators

let remove_operator (operators : t) (owner : owner) (operator : operator) (token_id : token_id) : t =
if owner = operator then operators (* assert_authorisation always allow the owner so this case is not relevant *)
else
let () = assert_update_permission owner in
let auth_tokens = match Big_map.find_opt (owner,operator) operators with
None -> None | Some (ts) ->
let ts = Set.remove token_id ts in
let is_empty = Set.cardinal ts = 0n in
if is_empty then None else Some (ts)
in
Big_map.update (owner,operator) auth_tokens operators
Loading