Skip to content

Commit

Permalink
feat(backend): create event onchain
Browse files Browse the repository at this point in the history
  • Loading branch information
0xLucqs committed Oct 24, 2024
1 parent 3782396 commit f0db49c
Show file tree
Hide file tree
Showing 16 changed files with 228 additions and 25 deletions.
9 changes: 7 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,13 @@ 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
- name: Run tests
env:
MIX_ENV: test
Expand Down
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
33 changes: 30 additions & 3 deletions backend/lib/peach/events.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ 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
Expand All @@ -10,13 +12,38 @@ defmodule Peach.Events do
@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
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
4 changes: 2 additions & 2 deletions backend/lib/peach_web/controllers/event_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ defmodule PeachWeb.EventController do
conn
|> put_status(:not_found)
|> json(%{errors: "Event not found"})
ticket_tier ->

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
3 changes: 3 additions & 0 deletions backend/lib/starknet.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Peach.Repo.Migrations.CreateTicketTiers do
add :name, :string
add :description, :string
add :max_supply, :integer
add :price, :integer
add :event_id, references(:events, on_delete: :nothing)

timestamps(type: :utc_datetime)
Expand Down
53 changes: 41 additions & 12 deletions backend/test/native/starknet_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ defmodule StarknetTest do

@moduletag :integration

@private_key System.get_env("PRIVATE_KEY") ||
raise("PRIVATE_KEY environment variable is not set")
@address System.get_env("ADDRESS") || raise("ADDRESS environment variable is not set")
@provider_url System.get_env("PROVIDER_URL") ||
raise("PROVIDER_URL environment variable is not set")
@chain_id "SN_SEPOLIA"
setup_all do
# Ensure the NIF is loaded
Application.ensure_all_started(:starknet)
Expand All @@ -21,11 +15,18 @@ defmodule StarknetTest do
{
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"0x0083afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e",
[@address, "0x01", "0x0"]
[Peach.Config.address(), "0x01", "0x0"]
}
]

tx_hash = Starknet.execute_tx(@provider_url, @private_key, @address, @chain_id, calls)
tx_hash =
Starknet.execute_tx(
Peach.Config.provider_url(),
Peach.Config.private_key(),
Peach.Config.address(),
Peach.Config.chain_id(),
calls
)

assert String.starts_with?(tx_hash, "0x")
end
Expand All @@ -34,7 +35,14 @@ defmodule StarknetTest do
provider_url = "invalid_url"
calls = []

result = Starknet.execute_tx(provider_url, @private_key, @address, @chain_id, calls)
result =
Starknet.execute_tx(
provider_url,
Peach.Config.private_key(),
Peach.Config.address(),
Peach.Config.chain_id(),
calls
)

assert {:error, :invalid_provider_url} = result
end
Expand All @@ -43,7 +51,14 @@ defmodule StarknetTest do
private_key = "invalid_private_key"
calls = []

result = Starknet.execute_tx(@provider_url, private_key, @address, @chain_id, calls)
result =
Starknet.execute_tx(
Peach.Config.provider_url(),
private_key,
Peach.Config.address(),
Peach.Config.chain_id(),
calls
)

assert {:error, :invalid_pk} = result
end
Expand All @@ -52,7 +67,14 @@ defmodule StarknetTest do
address = "invalid_address"
calls = []

result = Starknet.execute_tx(@provider_url, @private_key, address, @chain_id, calls)
result =
Starknet.execute_tx(
Peach.Config.provider_url(),
Peach.Config.private_key(),
address,
Peach.Config.chain_id(),
calls
)

assert {:error, :invalid_address} = result
end
Expand All @@ -66,7 +88,14 @@ defmodule StarknetTest do
}
]

result = Starknet.execute_tx(@provider_url, @private_key, @address, @chain_id, calls)
result =
Starknet.execute_tx(
Peach.Config.provider_url(),
Peach.Config.private_key(),
Peach.Config.address(),
Peach.Config.chain_id(),
calls
)

assert {:error, :invalid_to_address} = result
end
Expand Down
2 changes: 2 additions & 0 deletions backend/test/peach_web/controllers/create_event_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ defmodule PeachWeb.EventCreateControllerTest do
%{
"name" => "General Admission",
"description" => "Access to all sessions",
"price" => 5,
"max_supply" => 100
},
%{
"name" => "VIP",
"description" => "Access to VIP sessions and perks",
"price" => 10,
"max_supply" => 20
}
]
Expand Down
Loading

0 comments on commit f0db49c

Please sign in to comment.