From 86c9341e0dae811171201ebac102cbaf2e479eda Mon Sep 17 00:00:00 2001 From: 0xChqrles Date: Tue, 8 Oct 2024 18:23:39 +0200 Subject: [PATCH] contracts init --- .github/workflows/contracts.yml | 45 ++++ .github/workflows/coverage.yml | 39 ++++ onchain/Scarb.lock | 110 ++++++++++ onchain/Scarb.toml | 26 ++- onchain/src/contracts.cairo | 1 + onchain/src/contracts/peach.cairo | 2 + onchain/src/contracts/peach/interface.cairo | 90 ++++++++ onchain/src/contracts/peach/peach.cairo | 228 ++++++++++++++++++++ onchain/src/lib.cairo | 21 +- onchain/src/utils.cairo | 1 + onchain/src/utils/validable.cairo | 43 ++++ onchain/tests/constants.cairo | 63 ++++++ onchain/tests/test_contract.cairo | 1 - onchain/tests/test_peach.cairo | 43 ++++ onchain/tests/utils.cairo | 20 ++ 15 files changed, 708 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/contracts.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 onchain/src/contracts.cairo create mode 100644 onchain/src/contracts/peach.cairo create mode 100644 onchain/src/contracts/peach/interface.cairo create mode 100644 onchain/src/contracts/peach/peach.cairo create mode 100644 onchain/src/utils.cairo create mode 100644 onchain/src/utils/validable.cairo create mode 100644 onchain/tests/constants.cairo delete mode 100644 onchain/tests/test_contract.cairo create mode 100644 onchain/tests/test_peach.cairo create mode 100644 onchain/tests/utils.cairo diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml new file mode 100644 index 0000000..d1aeae4 --- /dev/null +++ b/.github/workflows/contracts.yml @@ -0,0 +1,45 @@ +name: Cairo Contracts + +on: [push, pull_request] + +permissions: read-all + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Scarb + uses: software-mansion/setup-scarb@v1 + with: + scarb-version: "2.8.2" + + - name: Check cairo format + run: scarb fmt --check + working-directory: onchain + + - name: Build cairo onchain + run: scarb build + working-directory: onchain + + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@main + + - name: Set up Scarb + uses: software-mansion/setup-scarb@v1 + with: + scarb-version: "2.8.2" + + - name: Set up SNForge + uses: foundry-rs/setup-snfoundry@v3 + with: + starknet-foundry-version: "0.30.0" + + - name: Run tests and generate report + run: snforge test + working-directory: onchain diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..9c549cb --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,39 @@ +name: Contracts Coverage + +on: + # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g., for dependabot pull requests) + push: + branches: [ main ] + # Trigger the workflow on any pull request + pull_request: + +permissions: read-all + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@main + + - name: Set up Scarb + uses: software-mansion/setup-scarb@v1 + with: + scarb-version: "2.8.2" + + - name: Set up SNForge + uses: foundry-rs/setup-snfoundry@v3 + with: + starknet-foundry-version: "0.30.0" + + - name: Set up cairo-coverage + run: curl -L https://raw.githubusercontent.com/software-mansion/cairo-coverage/main/scripts/install.sh | sh + + - name: Run tests and generate report + run: cd onchain/ && snforge test --coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.lcov + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/onchain/Scarb.lock b/onchain/Scarb.lock index 61a4523..dce15ad 100644 --- a/onchain/Scarb.lock +++ b/onchain/Scarb.lock @@ -1,6 +1,116 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "openzeppelin" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", + "openzeppelin_merkle_tree", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_access" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" + +[[package]] +name = "openzeppelin_merkle_tree" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" + +[[package]] +name = "openzeppelin_presets" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", +] + +[[package]] +name = "openzeppelin_security" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" + +[[package]] +name = "openzeppelin_token" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" + +[[package]] +name = "openzeppelin_utils" +version = "0.16.0" +source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.16.0#ba00ce76a93dcf25c081ab2698da20690b5a1cfb" + [[package]] name = "peach" version = "0.1.0" +dependencies = [ + "openzeppelin", + "snforge_std", +] + +[[package]] +name = "snforge_scarb_plugin" +version = "0.2.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:2e4ce3ebe3f49548bd26908391b5d78537a765d827df0d96c32aeb88941d0d67" + +[[package]] +name = "snforge_std" +version = "0.30.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:2f3c4846881813ac0f5d1460981249c9f5e2a6831e752beedf9b70975495b4ec" +dependencies = [ + "snforge_scarb_plugin", +] diff --git a/onchain/Scarb.toml b/onchain/Scarb.toml index e345e7e..9bba849 100644 --- a/onchain/Scarb.toml +++ b/onchain/Scarb.toml @@ -2,15 +2,31 @@ name = "peach" version = "0.1.0" edition = "2024_07" - -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +cairo-version = "2.8.2" +scarb-version = "2.8.2" [dependencies] starknet = "2.8.2" +openzeppelin = { git = "https://github.com/openzeppelin/cairo-contracts", tag = "v0.16.0" } -[scripts] -test = "snforge test" +[dev-dependencies] +assert_macros = "2.8.2" +snforge_std = "0.30.0" + +[tool.fmt] +max-line-length = 120 +sort-module-level-items = true [[target.starknet-contract]] -casm = true sierra = true +casm = true +allowed-libfuncs-list.name = "audited" +build-external-contracts = ["openzeppelin_presets::erc20::ERC20Upgradeable"] + +[profile.dev.cairo] +unstable-add-statements-functions-debug-info = true +unstable-add-statements-code-locations-debug-info = true +inlining-strategy = "avoid" + +[scripts] +test = "snforge test" diff --git a/onchain/src/contracts.cairo b/onchain/src/contracts.cairo new file mode 100644 index 0000000..ab85f88 --- /dev/null +++ b/onchain/src/contracts.cairo @@ -0,0 +1 @@ +pub mod peach; diff --git a/onchain/src/contracts/peach.cairo b/onchain/src/contracts/peach.cairo new file mode 100644 index 0000000..c10b8a6 --- /dev/null +++ b/onchain/src/contracts/peach.cairo @@ -0,0 +1,2 @@ +pub mod interface; +pub mod peach; diff --git a/onchain/src/contracts/peach/interface.cairo b/onchain/src/contracts/peach/interface.cairo new file mode 100644 index 0000000..d93730a --- /dev/null +++ b/onchain/src/contracts/peach/interface.cairo @@ -0,0 +1,90 @@ +use starknet::{ContractAddress, ClassHash}; + +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct TicketTierParams { + pub price: u256, + pub max_supply: u32, +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct TicketTier { + pub params: TicketTierParams, + pub supply: u32, +} + +#[derive(Copy, Drop, Serde)] +pub struct PeachEvent { + pub id: u64, + pub ticket_tiers_params: Span, + pub treasury_address: ContractAddress, +} + +#[starknet::interface] +pub trait IPeach { + fn get_treasury_address(self: @TState, event_id: u64) -> Option; + fn get_ticket_tier(self: @TState, event_id: u64, ticket_tier_id: u8) -> Option; + + fn create_event(ref self: TState, event: PeachEvent); + fn buy_ticket(ref self: TState, event_id: u64, ticket_tier_id: u8, recipient: ContractAddress, amount: u32); +} + +#[starknet::interface] +pub trait PeachABI { + // IPeach + fn get_treasury_address(self: @TState, event_id: u64) -> Option; + fn get_ticket_tier(self: @TState, event_id: u64, ticket_tier_id: u8) -> Option; + fn create_event(ref self: TState, event: PeachEvent); + fn buy_ticket(ref self: TState, event_id: u64, ticket_tier_id: u8, recipient: ContractAddress, amount: u32); + + // IERC1155 + fn balance_of(self: @TState, account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch(self: @TState, accounts: Span, token_ids: Span) -> Span; + fn safe_transfer_from( + ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256, value: u256, data: Span + ); + fn safe_batch_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IERC1155MetadataURI + fn uri(self: @TState, token_id: u256) -> ByteArray; + + // IERC1155Camel + fn balanceOf(self: @TState, account: ContractAddress, tokenId: u256) -> u256; + fn balanceOfBatch(self: @TState, accounts: Span, tokenIds: Span) -> Span; + fn safeTransferFrom( + ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256, value: u256, data: Span + ); + fn safeBatchTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ); + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + + // IOwnable + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableCamelOnly + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); + + // IUpgradeable + fn upgrade(ref self: TState, new_class_hash: ClassHash); +} diff --git a/onchain/src/contracts/peach/peach.cairo b/onchain/src/contracts/peach/peach.cairo new file mode 100644 index 0000000..93fe213 --- /dev/null +++ b/onchain/src/contracts/peach/peach.cairo @@ -0,0 +1,228 @@ +#[starknet::contract] +pub mod Peach { + use core::num::traits::Zero; + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc1155::{ERC1155Component, ERC1155HooksEmptyImpl}; + use openzeppelin_token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin_upgrades::UpgradeableComponent; + use openzeppelin_upgrades::interface::IUpgradeable; + use peach::contracts::peach::interface::{IPeach, PeachEvent, TicketTier}; + use peach::utils::validable::Validable; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess + }; + use starknet::{get_caller_address, ContractAddress, ClassHash}; + + // + // Components + // + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // ERC1155 Mixin + #[abi(embed_v0)] + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + // + // Storage + // + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + // (event_id, tier_id) -> ticket_tier + ticket_tiers: Map<(u64, u8), Option>, + // event_id -> treasury_address + events_treasury_addresses: Map>, + // currencty token + currency_token: ERC20ABIDispatcher, + } + + // + // Errors + // + + pub mod Errors { + pub const INVALID_EVENT: felt252 = 'Event is not valid'; + pub const NULL_AMOUNT: felt252 = 'Amount cannot be null'; + pub const TICKET_TIER_NOT_FOUND: felt252 = 'Ticket tier not found'; + pub const NOT_ENOUGH_TICKETS_LEFT: felt252 = 'Not enough tickets left'; + } + + // + // Events + // + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + EventCreated: EventCreated, + TicketPurchased: TicketPurchased, + } + + // Emitted on event creation + #[derive(Drop, starknet::Event)] + pub struct EventCreated { + #[key] + pub id: u64, + } + + // Emitted on ticket purchase + #[derive(Drop, starknet::Event)] + pub struct TicketPurchased { + #[key] + pub event_id: u64, + #[key] + pub ticket_tier_id: u8, + pub buyer_address: ContractAddress, + pub recipient: ContractAddress, + pub amount: u32, + } + + // + // Constructor + // + + #[constructor] + fn constructor( + ref self: ContractState, owner: ContractAddress, base_uri: ByteArray, currency_token: ContractAddress + ) { + // init owner + self.ownable.initializer(:owner); + + // init erc1155 + self.erc1155.initializer(base_uri); + + // init contract + self.initializer(:currency_token); + } + + // + // Peach impl + // + + #[abi(embed_v0)] + impl PeachImpl of IPeach { + fn get_treasury_address(self: @ContractState, event_id: u64) -> Option { + self.events_treasury_addresses.read(event_id) + } + + fn get_ticket_tier(self: @ContractState, event_id: u64, ticket_tier_id: u8) -> Option { + self.ticket_tiers.read((event_id, ticket_tier_id)) + } + + fn create_event(ref self: ContractState, event: PeachEvent) { + // Check: owner only + self.ownable.assert_only_owner(); + + // Check: verify event validity + assert(event.is_valid(), Errors::INVALID_EVENT); + + // Effects: store tickets tiers + let mut tier_id: u8 = 0; + + for ticket_tier_param in event + .ticket_tiers_params { + self + .ticket_tiers + .write((event.id, tier_id), Option::Some(TicketTier { params: *ticket_tier_param, supply: 0 })); + tier_id += 1; + }; + + // Effects: store treasury address + self.events_treasury_addresses.write(event.id, Option::Some(event.treasury_address)); + + // Log event creation + self.emit(EventCreated { id: event.id }); + } + + fn buy_ticket( + ref self: ContractState, event_id: u64, ticket_tier_id: u8, recipient: ContractAddress, amount: u32 + ) { + // Checks: amount not null + assert(amount.is_non_zero(), Errors::NULL_AMOUNT); + + // Checks: ticket tier exists + let mut ticket_tier = self + .get_ticket_tier(:event_id, :ticket_tier_id) + .expect(Errors::TICKET_TIER_NOT_FOUND); + + // Checks: ticket tier supply + assert(ticket_tier.supply + amount <= ticket_tier.params.max_supply, Errors::NOT_ENOUGH_TICKETS_LEFT); + + // Effects: update supply + ticket_tier.supply += amount; + self.ticket_tiers.write((event_id, ticket_tier_id), Option::Some(ticket_tier)); + + // Effects: mint tickets + let token_id = u256 { low: event_id.into(), high: ticket_tier_id.into() }; + self + .erc1155 + .mint_with_acceptance_check(to: recipient, :token_id, value: amount.into(), data: array![].span()); + + // Interaction: spend currency token + let currency_token = self.currency_token.read(); + let caller = get_caller_address(); + // safe to unwrap due to ticket tier existance check + let treasury_address = self.get_treasury_address(:event_id).unwrap(); + + currency_token.transfer_from(sender: caller, recipient: treasury_address, amount: ticket_tier.params.price); + + // Log event creation + self.emit(TicketPurchased { event_id, ticket_tier_id, buyer_address: caller, recipient, amount }); + } + } + + // + // Upgradeable impl + // + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + /// Upgrades the contract class hash to `new_class_hash`. + /// This may only be called by the contract owner. + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); + } + } + + // + // Internals + // + + #[generate_trait] + pub impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, currency_token: ContractAddress) { + self.currency_token.write(ERC20ABIDispatcher { contract_address: currency_token }); + } + } +} diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index 13abdec..d28ef30 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -1,19 +1,2 @@ -#[starknet::interface] -trait IPeach { - fn get_version(self: @TContractState) -> felt252; -} - -#[starknet::contract] -mod Peach { - #[storage] - struct Storage { - balance: felt252, - } - - #[abi(embed_v0)] - impl PeachImpl of super::IPeach { - fn get_version(self: @ContractState) -> felt252 { - 1 - } - } -} +pub mod contracts; +pub mod utils; diff --git a/onchain/src/utils.cairo b/onchain/src/utils.cairo new file mode 100644 index 0000000..732fdd0 --- /dev/null +++ b/onchain/src/utils.cairo @@ -0,0 +1 @@ +pub mod validable; diff --git a/onchain/src/utils/validable.cairo b/onchain/src/utils/validable.cairo new file mode 100644 index 0000000..5907b08 --- /dev/null +++ b/onchain/src/utils/validable.cairo @@ -0,0 +1,43 @@ +use core::num::traits::Zero; +use peach::contracts::peach::interface::{PeachEvent, TicketTierParams}; + +pub trait Validable { + fn is_valid(self: @T) -> bool; +} + +// PeachEvent + +pub const MAX_TICKET_TIERS_COUNT: u8 = 10; + +pub impl PeachEventValidableImpl of Validable { + fn is_valid(self: @PeachEvent) -> bool { + // Checks: tresory_address + if self.treasury_address.is_zero() { + return false; + } + + // Checks: ticket_tiers_params + let mut len: u8 = 0; + let mut is_valid = true; + + for ticket_tier_param in *self + .ticket_tiers_params { + if !ticket_tier_param.is_valid() { + is_valid = false; + break; + } + + len += 1; + }; + + is_valid && len.is_non_zero() && len <= MAX_TICKET_TIERS_COUNT + } +} + +// TicketTier + +pub impl TicketTierValidableImpl of Validable { + fn is_valid(self: @TicketTierParams) -> bool { + self.max_supply.is_non_zero() + } +} diff --git a/onchain/tests/constants.cairo b/onchain/tests/constants.cairo new file mode 100644 index 0000000..f556e44 --- /dev/null +++ b/onchain/tests/constants.cairo @@ -0,0 +1,63 @@ +use peach::contracts::peach::interface::{PeachEvent, TicketTierParams}; +use starknet::{ContractAddress, contract_address_const}; + +pub fn CALLER() -> ContractAddress { + contract_address_const::<'caller'>() +} + +pub fn SPENDER() -> ContractAddress { + contract_address_const::<'spender'>() +} + +pub fn RECIPIENT() -> ContractAddress { + contract_address_const::<'recipient'>() +} + +pub fn OWNER() -> ContractAddress { + contract_address_const::<'owner'>() +} + +pub fn OTHER() -> ContractAddress { + contract_address_const::<'other'>() +} + +pub fn TREASURY_1() -> ContractAddress { + contract_address_const::<'treasury1'>() +} + +pub const SUPPLY: u256 = 1_000_000_000_000_000_000; // 1 ETH + +pub fn NAME() -> ByteArray { + "NAME" +} + +pub fn SYMBOL() -> ByteArray { + "SYMBOL" +} + +pub fn BASE_URI() -> ByteArray { + "peach.fm" +} + +pub fn TICKET_TIERS_PARAMS_GOLD() -> TicketTierParams { + TicketTierParams { price: 100, max_supply: 1, } +} + +pub fn TICKET_TIERS_PARAMS_SILVER() -> TicketTierParams { + TicketTierParams { price: 10, max_supply: 10, } +} + +pub fn TICKET_TIERS_PARAMS_BRONZE() -> TicketTierParams { + TicketTierParams { price: 1, max_supply: 100, } +} + +pub fn EVENT_1() -> PeachEvent { + PeachEvent { + id: 1, + ticket_tiers_params: array![ + TICKET_TIERS_PARAMS_GOLD(), TICKET_TIERS_PARAMS_SILVER(), TICKET_TIERS_PARAMS_BRONZE() + ] + .span(), + treasury_address: TREASURY_1(), + } +} diff --git a/onchain/tests/test_contract.cairo b/onchain/tests/test_contract.cairo deleted file mode 100644 index 8b13789..0000000 --- a/onchain/tests/test_contract.cairo +++ /dev/null @@ -1 +0,0 @@ - diff --git a/onchain/tests/test_peach.cairo b/onchain/tests/test_peach.cairo new file mode 100644 index 0000000..6e37317 --- /dev/null +++ b/onchain/tests/test_peach.cairo @@ -0,0 +1,43 @@ +use core::starknet::ContractAddress; +use openzeppelin::presets::interfaces::{ERC20UpgradeableABIDispatcher}; +use openzeppelin::utils::serde::SerializedAppend; +use peach::contracts::peach::interface::{PeachABIDispatcher, PeachABIDispatcherTrait}; +use snforge_std::{declare, DeclareResultTrait, ContractClassTrait, start_cheat_caller_address}; +use super::{constants, utils}; + +fn setup_peach(erc20_contract_address: ContractAddress) -> PeachABIDispatcher { + // declare Peach contract + let peach_contract_class = declare("Peach").unwrap().contract_class(); + + // deploy peach + let mut calldata = array![]; + + calldata.append_serde(constants::OWNER()); + calldata.append_serde(constants::BASE_URI()); + calldata.append_serde(erc20_contract_address); + + let (contract_address, _) = peach_contract_class.deploy(@calldata).unwrap(); + + PeachABIDispatcher { contract_address } +} + +fn setup() -> (PeachABIDispatcher, ERC20UpgradeableABIDispatcher) { + // deploy an ERC20 + let erc20 = utils::setup_erc20(constants::OWNER()); + + // deploy peach + let peach = setup_peach(erc20.contract_address); + + (peach, erc20) +} + +#[test] +fn test_create_event() { + let (peach, _) = setup(); + let event = constants::EVENT_1(); + let owner = constants::OWNER(); + + // create event + start_cheat_caller_address(peach.contract_address, owner); + peach.create_event(:event); +} diff --git a/onchain/tests/utils.cairo b/onchain/tests/utils.cairo new file mode 100644 index 0000000..d87c079 --- /dev/null +++ b/onchain/tests/utils.cairo @@ -0,0 +1,20 @@ +use openzeppelin::presets::interfaces::{ERC20UpgradeableABIDispatcher}; +use openzeppelin::utils::serde::SerializedAppend; +use snforge_std::{declare, DeclareResultTrait, ContractClassTrait}; +use starknet::ContractAddress; +use super::constants; + +pub fn setup_erc20(recipient: ContractAddress) -> ERC20UpgradeableABIDispatcher { + let mut calldata = array![]; + + calldata.append_serde(constants::NAME()); + calldata.append_serde(constants::SYMBOL()); + calldata.append_serde(constants::SUPPLY); // 1 ETH + calldata.append_serde(recipient); + calldata.append_serde(recipient); + + let contract = declare("ERC20Upgradeable").unwrap().contract_class(); + let (contract_address, _) = contract.deploy(@calldata).unwrap(); + + ERC20UpgradeableABIDispatcher { contract_address: contract_address } +}