Skip to content

Commit

Permalink
Merge pull request #8 from keep-starknet-strange/lucas/backend
Browse files Browse the repository at this point in the history
feat(backend): remaining tickets for event
  • Loading branch information
0xLucqs authored Oct 24, 2024
2 parents 9e121e0 + 6354cd7 commit 846909b
Show file tree
Hide file tree
Showing 27 changed files with 2,730 additions and 13 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ jobs:
--health-timeout 5s
--health-retries 5
env:
MIX_ENV: test
steps:
- name: Checkout
Expand All @@ -78,6 +76,14 @@ jobs:
mix ecto.drop
mix ecto.create
mix ecto.migrate

- name: Create .env file
run: |
echo "PRIVATE_KEY=${{ secrets.PRIVATE_KEY }}" >> .env
echo "ADDRESS=${{ secrets.ADDRESS }}" >> .env
echo "PROVIDER_URL=${{ secrets.PROVIDER_URL }}" >> .env
echo "CONTRACT_ADDRESS=${{ secrets.CONTRACT_ADDRESS }}" >> .env
echo "CHAIN_ID=${{ secrets.CHAIN_ID }}" >> .env
- name: Run tests
env:
MIX_ENV: test
Expand Down
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
peach-*.tar

.env
9 changes: 9 additions & 0 deletions backend/config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ if System.get_env("PHX_SERVER") do
config :peach, PeachWeb.Endpoint, server: true
end

env = Dotenv.load()

config :peach, Peach.Config,
private_key: env.values["PRIVATE_KEY"],
address: env.values["ADDRESS"],
provider_url: env.values["PROVIDER_URL"],
contract_address: env.values["CONTRACT_ADDRESS"],
chain_id: env.values["CHAIN_ID"]

if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
Expand Down
87 changes: 87 additions & 0 deletions backend/lib/peach/calldata_builder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
defmodule Peach.CalldataBuilder do
@moduledoc """
Converts an Event struct into a calldata list for the smart contract.
"""

import Bitwise

def build_calldata(event) do
# 1. Event ID as u64
event_id = to_u64(event.id)

# 2. Ticket Tiers
ticket_tiers_params = build_ticket_tiers_params(event.ticket_tiers)

# Combine all parts into the calldata list
["0x" <> Integer.to_string(event_id, 16)] ++
Enum.map(ticket_tiers_params, &("0x" <> Integer.to_string(&1, 16))) ++
[event.treasury]
end

defp to_u64(value) do
value
|> to_integer()
|> check_integer_size(64)
end

defp build_ticket_tiers_params(ticket_tiers) do
# Number of ticket tiers
len = length(ticket_tiers)

# Flattened list of ticket tier parameters
tiers_params =
ticket_tiers
|> Enum.flat_map(&ticket_tier_to_params/1)

[len] ++ tiers_params
end

defp ticket_tier_to_params(tier) do
# Convert price to u256 (two u128)
{price_low, price_high} = to_u256(tier.price)

# Convert max_supply to u32
max_supply = tier.max_supply |> to_integer() |> check_integer_size(32)

[price_low, price_high, max_supply]
end

defp to_u256(value) do
# Convert the value to a big integer
bigint = to_integer(value)

# Split into low and high 128 bits
price_low = bigint &&& (1 <<< 128) - 1
price_high = bigint >>> 128

{price_low, price_high}
end

defp check_integer_size(value, bits) do
max_value = (1 <<< bits) - 1

if value < 0 or value > max_value do
raise ArgumentError, "Value #{value} does not fit in #{bits} bits"
else
value
end
end

defp to_integer(value) do
cond do
is_integer(value) ->
value

is_binary(value) ->
try do
String.to_integer(value)
rescue
ArgumentError ->
reraise ArgumentError, "Cannot parse integer from string: #{value}"
end

true ->
raise ArgumentError, "Cannot convert #{inspect(value)} to integer"
end
end
end
27 changes: 27 additions & 0 deletions backend/lib/peach/config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule Peach.Config do
@moduledoc """
Provides access to application configuration.
"""

@app :peach

def private_key do
Application.fetch_env!(@app, __MODULE__)[:private_key]
end

def address do
Application.fetch_env!(@app, __MODULE__)[:address]
end

def provider_url do
Application.fetch_env!(@app, __MODULE__)[:provider_url]
end

def contract_address do
Application.fetch_env!(@app, __MODULE__)[:contract_address]
end

def chain_id do
Application.fetch_env!(@app, __MODULE__)[:chain_id]
end
end
41 changes: 38 additions & 3 deletions backend/lib/peach/events.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,55 @@ defmodule Peach.Events do
@moduledoc """
Manages the events for the peach app
"""
alias Peach.CalldataBuilder
alias Peach.Config
alias Peach.Event
alias Peach.Repo
alias Peach.TicketTiers
import Ecto.Query

@default_limit 50
@default_event_id 0

@selector "0x005b3134506a8ff22ce883984545296af6e65577777882051fa04dc6ecb84e99"

@doc """
Creates an event with the given attributes.
"""
def create_event(event \\ %{}) do
%Event{}
|> Event.changeset(event)
|> Repo.insert()
event =
%Event{}
|> Event.changeset(event)
|> Repo.insert()

case event do
{:ok, real_event} ->
calls = {
Config.contract_address(),
@selector,
CalldataBuilder.build_calldata(real_event)
}

Starknet.execute_tx(
Config.provider_url(),
Config.private_key(),
Config.address(),
Config.chain_id(),
[calls]
)

err ->
err
end

event
end

def remaining_event_tickets(event_id) do
Enum.map(TicketTiers.event_ticket_tiers(event_id), fn ticket_tier ->
{:ok, remaining} = TicketTiers.remaining_tickets(ticket_tier.id)
remaining
end)
end

@doc """
Expand Down
7 changes: 4 additions & 3 deletions backend/lib/peach/ticket_tier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ defmodule Peach.TicketTier do
use Ecto.Schema
import Ecto.Changeset

@derive {Jason.Encoder, only: [:id, :name, :description, :max_supply]}
@derive {Jason.Encoder, only: [:id, :name, :description, :max_supply, :price]}
schema "ticket_tiers" do
field :name, :string
field :description, :string
field :max_supply, :integer
field :price, :integer

belongs_to :event, Peach.Event

Expand All @@ -19,7 +20,7 @@ defmodule Peach.TicketTier do
@doc false
def changeset(ticket_tier, attrs) do
ticket_tier
|> cast(attrs, [:name, :description, :max_supply])
|> validate_required([:name, :description, :max_supply])
|> cast(attrs, [:name, :description, :max_supply, :price])
|> validate_required([:name, :description, :max_supply, :price])
end
end
1 change: 1 addition & 0 deletions backend/lib/peach/ticket_tiers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ defmodule Peach.TicketTiers do
id: ticket_tier.id,
name: ticket_tier.name,
description: ticket_tier.description,
price: ticket_tier.price,
remaining: ticket_tier.max_supply - sold_tickets,
max_supply: ticket_tier.max_supply
}}
Expand Down
16 changes: 15 additions & 1 deletion backend/lib/peach_web/controllers/event_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule PeachWeb.EventController do
end

@doc """
Updates the name of an event.
Updates an event.
"""
def update(conn, %{"id" => id, "event" => event_params}) do
case Events.update_event(id, event_params) do
Expand All @@ -55,4 +55,18 @@ defmodule PeachWeb.EventController do
|> json(%{errors: errors})
end
end

def remaining_event_tickets(conn, %{"id" => id}) do
case Events.remaining_event_tickets(id) do
[] ->
conn
|> put_status(:not_found)
|> json(%{errors: "Event not found"})

ticket_tier ->
conn
|> put_status(:ok)
|> json(%{tickets: ticket_tier})
end
end
end
1 change: 1 addition & 0 deletions backend/lib/peach_web/controllers/ticket_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defmodule PeachWeb.TicketController do
%{
"tier_id" => tier.id,
"name" => tier.name,
"price" => tier.price,
"description" => tier.description,
"ticket_ids" => Enum.map(tickets, & &1.id) |> Enum.sort()
}
Expand Down
1 change: 1 addition & 0 deletions backend/lib/peach_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule PeachWeb.Router do
get "/tickets/:address", TicketController, :index
get "/events/:id/ticket_tiers", TicketTierController, :index
get "/ticket_tiers/:id", TicketTierController, :show
get "/events/:id/remaining_tickets", EventController, :remaining_event_tickets
end

# Enable LiveDashboard and Swoosh mailbox preview in development
Expand Down
10 changes: 10 additions & 0 deletions backend/lib/starknet.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Starknet do
@moduledoc """
Binding to call the starknet rust sdk to execute a transaction
"""
use Rustler, otp_app: :peach, crate: "starknet"

# Fallback function in case the NIF is not loaded
def execute_tx(_provider_url, _private_key, _address, _chain_id, _calls),
do: :erlang.nif_error(:nif_not_loaded)
end
4 changes: 3 additions & 1 deletion backend/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ defmodule Peach.MixProject do
{:dns_cluster, "~> 0.1.1"},
{:bandit, "~> 1.5"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:excoveralls, "~> 0.18", only: [:test]}
{:excoveralls, "~> 0.18", only: [:test]},
{:dotenv, "~> 3.0.0"},
{:rustler, "~> 0.35.0"}
]
end

Expand Down
4 changes: 4 additions & 0 deletions backend/mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
"dotenv": {:hex, :dotenv, "3.0.0", "52a28976955070d8312a81d59105b57ecf5d6a755c728b49c70a7e2120e6cb40", [:mix], [], "hexpm", "f8a7d800b6b419a8d8a8bc5b5cd820a181c2b713aab7621794febe934f7bd84e"},
"ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"},
"ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"},
"excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"},
Expand All @@ -29,11 +30,14 @@
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"},
"req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"},
"rustler": {:hex, :rustler, "0.35.0", "1e2e379e1150fab9982454973c74ac9899bd0377b3882166ee04127ea613b2d9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "a176bea1bb6711474f9dfad282066f2b7392e246459bf4e29dfff6d828779fdf"},
"swoosh": {:hex, :swoosh, "1.17.1", "01295a82bddd2c6cac1e65856e29444d7c23c4501e0ebc69cea8a82018227e25", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b20d25e580cb79af631335a1bdcfbffd835c08ebcdc16e98577223a241a18a1"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
}
5 changes: 5 additions & 0 deletions backend/native/starknet/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[target.'cfg(target_os = "macos")']
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
1 change: 1 addition & 0 deletions backend/native/starknet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
Loading

0 comments on commit 846909b

Please sign in to comment.