From f4365e412be7233190ce645e549f4336129299b7 Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 5 Nov 2023 14:51:14 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20Introduce=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/{test.yml => crates.yml} | 0 .github/workflows/examples.yml | 53 + Scarb.toml | 5 +- crates/algebra/src/lib.cairo | 2 +- crates/algebra/src/matrix.cairo | 2 +- crates/algebra/src/vec2.cairo | 10 +- crates/algebra/src/vector.cairo | 2 +- examples/market/.gitignore | 1 + examples/market/Scarb.lock | 27 + examples/market/Scarb.toml | 9 + examples/market/src/lib.cairo | 17 + examples/market/src/models/cash.cairo | 10 + examples/market/src/models/item.cairo | 12 + examples/market/src/models/liquidity.cairo | 56 + examples/market/src/models/market.cairo | 423 +++++ examples/market/src/systems/liquidity.cairo | 128 ++ examples/market/src/systems/trade.cairo | 86 + examples/market/src/tests/setup.cairo | 41 + examples/market/src/tests/trade.cairo | 22 + examples/projectile/Scarb.lock | 22 + examples/projectile/Scarb.toml | 9 + examples/projectile/src/lib.cairo | 245 +++ presets/token/.gitignore | 1 + presets/token/Scarb.lock | 20 + presets/token/Scarb.toml | 29 + presets/token/src/lib.cairo | 17 + presets/token/src/tests/constants.cairo | 60 + presets/token/src/tests/erc1155_tests.cairo | 836 ++++++++++ presets/token/src/tests/erc20_tests.cairo | 546 +++++++ presets/token/src/tests/erc721_tests.cairo | 1453 +++++++++++++++++ presets/token/src/tests/test_erc1155.cairo | 629 +++++++ presets/token/src/tests/test_erc721.cairo | 862 ++++++++++ presets/token/src/tests/utils.cairo | 32 + presets/token/src/token/erc1155.cairo | 5 + presets/token/src/token/erc1155/erc1155.cairo | 439 +++++ .../token/src/token/erc1155/interface.cairo | 62 + presets/token/src/token/erc1155/models.cairo | 33 + presets/token/src/token/erc20.cairo | 331 ++++ presets/token/src/token/erc20_models.cairo | 30 + presets/token/src/token/erc721.cairo | 5 + presets/token/src/token/erc721/erc721.cairo | 443 +++++ .../token/src/token/erc721/interface.cairo | 60 + presets/token/src/token/erc721/models.cairo | 48 + 43 files changed, 7111 insertions(+), 12 deletions(-) rename .github/workflows/{test.yml => crates.yml} (100%) create mode 100644 .github/workflows/examples.yml create mode 100644 examples/market/.gitignore create mode 100644 examples/market/Scarb.lock create mode 100644 examples/market/Scarb.toml create mode 100644 examples/market/src/lib.cairo create mode 100644 examples/market/src/models/cash.cairo create mode 100644 examples/market/src/models/item.cairo create mode 100644 examples/market/src/models/liquidity.cairo create mode 100644 examples/market/src/models/market.cairo create mode 100644 examples/market/src/systems/liquidity.cairo create mode 100644 examples/market/src/systems/trade.cairo create mode 100644 examples/market/src/tests/setup.cairo create mode 100644 examples/market/src/tests/trade.cairo create mode 100644 examples/projectile/Scarb.lock create mode 100644 examples/projectile/Scarb.toml create mode 100644 examples/projectile/src/lib.cairo create mode 100644 presets/token/.gitignore create mode 100644 presets/token/Scarb.lock create mode 100644 presets/token/Scarb.toml create mode 100644 presets/token/src/lib.cairo create mode 100644 presets/token/src/tests/constants.cairo create mode 100644 presets/token/src/tests/erc1155_tests.cairo create mode 100644 presets/token/src/tests/erc20_tests.cairo create mode 100644 presets/token/src/tests/erc721_tests.cairo create mode 100644 presets/token/src/tests/test_erc1155.cairo create mode 100644 presets/token/src/tests/test_erc721.cairo create mode 100644 presets/token/src/tests/utils.cairo create mode 100644 presets/token/src/token/erc1155.cairo create mode 100644 presets/token/src/token/erc1155/erc1155.cairo create mode 100644 presets/token/src/token/erc1155/interface.cairo create mode 100644 presets/token/src/token/erc1155/models.cairo create mode 100644 presets/token/src/token/erc20.cairo create mode 100644 presets/token/src/token/erc20_models.cairo create mode 100644 presets/token/src/token/erc721.cairo create mode 100644 presets/token/src/token/erc721/erc721.cairo create mode 100644 presets/token/src/token/erc721/interface.cairo create mode 100644 presets/token/src/token/erc721/models.cairo diff --git a/.github/workflows/test.yml b/.github/workflows/crates.yml similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/crates.yml diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 00000000..72a04e7b --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,53 @@ +name: Test + +on: [push, pull_request] + +env: + DOJO_VERSION: v0.3.3 + WORKING-DIRECTORY: examples + +jobs: + check: + runs-on: ubuntu-latest + name: Check format + steps: + - uses: actions/checkout@v4 + - uses: software-mansion/setup-scarb@v1 + - name: Format + working-directory: ${{ env.WORKING-DIRECTORY }} + run: scarb fmt --package random --check + shell: bash + + build: + runs-on: ubuntu-latest + name: Build package + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup + - name: Build + working-directory: ${{ env.WORKING-DIRECTORY }} + run: sozo build + shell: bash + + market: + runs-on: ubuntu-latest + name: Test market example + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup + - name: Test + working-directory: ${{ env.WORKING-DIRECTORY }} + run: sozo test + shell: bash + + projectile: + runs-on: ubuntu-latest + name: Test projectile example + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup + - name: Test + working-directory: ${{ env.WORKING-DIRECTORY }} + run: sozo test + shell: bash + \ No newline at end of file diff --git a/Scarb.toml b/Scarb.toml index c32f1f57..3b36f0ca 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -4,10 +4,7 @@ version = "0.0.0" description = "Community-maintained libraries for Cairo" homepage = "https://github.com/dojoengine/origami" members = [ - "crates/algebra", - "crates/defi", - "crates/random", - "crates/security", + "crates/*", ] [workspace.dependencies] diff --git a/crates/algebra/src/lib.cairo b/crates/algebra/src/lib.cairo index 46a1516d..347476c3 100644 --- a/crates/algebra/src/lib.cairo +++ b/crates/algebra/src/lib.cairo @@ -1,3 +1,3 @@ mod vec2; mod vector; -mod matrix; \ No newline at end of file +mod matrix; diff --git a/crates/algebra/src/matrix.cairo b/crates/algebra/src/matrix.cairo index 6612f9d1..5ed219d9 100644 --- a/crates/algebra/src/matrix.cairo +++ b/crates/algebra/src/matrix.cairo @@ -412,4 +412,4 @@ mod tests { assert(inverse.get(2, 1) == -1, 'Matrix: inversion failed'); assert(inverse.get(2, 2) == 1, 'Matrix: inversion failed'); } -} \ No newline at end of file +} diff --git a/crates/algebra/src/vec2.cairo b/crates/algebra/src/vec2.cairo index 116ef2f1..6bee5980 100644 --- a/crates/algebra/src/vec2.cairo +++ b/crates/algebra/src/vec2.cairo @@ -78,22 +78,22 @@ impl Vec2Impl, impl TDrop: Drop> of Vec2Trait { /// Vec2 -> Vec2 #[inline(always)] fn xx(self: Vec2) -> Vec2 { - Vec2 { x: self.x, y: self.x, } + Vec2 { x: self.x, y: self.x, } } #[inline(always)] fn xy(self: Vec2) -> Vec2 { - Vec2 { x: self.x, y: self.y, } + Vec2 { x: self.x, y: self.y, } } #[inline(always)] fn yx(self: Vec2) -> Vec2 { - Vec2 { x: self.y, y: self.x, } + Vec2 { x: self.y, y: self.x, } } #[inline(always)] fn yy(self: Vec2) -> Vec2 { - Vec2 { x: self.y, y: self.y, } + Vec2 { x: self.y, y: self.y, } } } @@ -266,4 +266,4 @@ mod tests { assert(vec2yy.y.mag == 2 * ONE_u128, 'invalid y.mag'); assert(vec2yy.y.sign == true, 'invalid y.sign'); } -} \ No newline at end of file +} diff --git a/crates/algebra/src/vector.cairo b/crates/algebra/src/vector.cairo index 63ddb1e0..9bff622f 100644 --- a/crates/algebra/src/vector.cairo +++ b/crates/algebra/src/vector.cairo @@ -127,4 +127,4 @@ mod tests { let result = vector1.dot(vector2); assert(result == 32, 'Vector: dot product failed'); // 1*4 + 2*5 + 3*6 = 32 } -} \ No newline at end of file +} diff --git a/examples/market/.gitignore b/examples/market/.gitignore new file mode 100644 index 00000000..1de56593 --- /dev/null +++ b/examples/market/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/examples/market/Scarb.lock b/examples/market/Scarb.lock new file mode 100644 index 00000000..f74da0a2 --- /dev/null +++ b/examples/market/Scarb.lock @@ -0,0 +1,27 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "cubit" +version = "1.2.0" +source = "git+https://github.com/influenceth/cubit?rev=b459053#b4590530d5aeae9aabd36740cc2a3d9e6adc5fde" + +[[package]] +name = "dojo" +version = "0.3.3" +source = "git+https://github.com/dojoengine/dojo.git?tag=v0.3.3#3c9f109e667ca5d12739e6553fdb8261378f4ecf" +dependencies = [ + "dojo_plugin", +] + +[[package]] +name = "dojo_plugin" +version = "0.3.3" + +[[package]] +name = "market" +version = "0.0.0" +dependencies = [ + "cubit", + "dojo", +] diff --git a/examples/market/Scarb.toml b/examples/market/Scarb.toml new file mode 100644 index 00000000..85e902a5 --- /dev/null +++ b/examples/market/Scarb.toml @@ -0,0 +1,9 @@ +[package] +name = "market" +version = "0.0.0" +description = "Example of defi crate usage." +homepage = "https://github.com/dojoengine/origami/tree/examples/market" + +[dependencies] +cubit = { git = "https://github.com/influenceth/cubit", rev = "b459053" } +dojo = { git = "https://github.com/dojoengine/dojo.git", tag = "v0.3.3" } \ No newline at end of file diff --git a/examples/market/src/lib.cairo b/examples/market/src/lib.cairo new file mode 100644 index 00000000..c617df4d --- /dev/null +++ b/examples/market/src/lib.cairo @@ -0,0 +1,17 @@ +mod models { + mod cash; + mod item; + mod liquidity; + mod market; +} + +mod systems { + mod liquidity; + mod trade; +} + +#[cfg(test)] +mod tests { + mod setup; + mod trade; +} diff --git a/examples/market/src/models/cash.cairo b/examples/market/src/models/cash.cairo new file mode 100644 index 00000000..d447b317 --- /dev/null +++ b/examples/market/src/models/cash.cairo @@ -0,0 +1,10 @@ +// Starknet imports + +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct Cash { + #[key] + player: ContractAddress, + amount: u128, +} diff --git a/examples/market/src/models/item.cairo b/examples/market/src/models/item.cairo new file mode 100644 index 00000000..7e395443 --- /dev/null +++ b/examples/market/src/models/item.cairo @@ -0,0 +1,12 @@ +// Starknet imports + +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct Item { + #[key] + player: ContractAddress, + #[key] + item_id: u32, + quantity: u128, +} diff --git a/examples/market/src/models/liquidity.cairo b/examples/market/src/models/liquidity.cairo new file mode 100644 index 00000000..8e46f834 --- /dev/null +++ b/examples/market/src/models/liquidity.cairo @@ -0,0 +1,56 @@ +// Starknet imports + +use starknet::ContractAddress; + +// Dojo imports + +use dojo::database::schema::{Struct, Ty, SchemaIntrospection, Member, serialize_member}; + +// External imports + +use cubit::f128::types::fixed::Fixed; + +// Constants + +const SCALING_FACTOR: u128 = 10000; + +impl SchemaIntrospectionFixed of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 2 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(128); + layout.append(1); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Struct( + Struct { + name: 'Fixed', + attrs: array![].span(), + children: array![ + serialize_member( + @Member { name: 'mag', ty: Ty::Primitive('u128'), attrs: array![].span() } + ), + serialize_member( + @Member { name: 'sign', ty: Ty::Primitive('bool'), attrs: array![].span() } + ) + ] + .span() + } + ) + } +} + +#[derive(Model, Copy, Drop, Serde)] +struct Liquidity { + #[key] + player: ContractAddress, + #[key] + item_id: u32, + shares: Fixed, +} diff --git a/examples/market/src/models/market.cairo b/examples/market/src/models/market.cairo new file mode 100644 index 00000000..50369e11 --- /dev/null +++ b/examples/market/src/models/market.cairo @@ -0,0 +1,423 @@ +// External imports + +use cubit::f128::types::fixed::{Fixed, FixedTrait}; + +// Constants + +const SCALING_FACTOR: u128 = 10000; + +#[derive(Model, Copy, Drop, Serde)] +struct Market { + #[key] + item_id: u32, + cash_amount: u128, + item_quantity: u128, +} + +#[generate_trait] +impl MarketImpl of MarketTrait { + fn buy(self: @Market, quantity: u128) -> u128 { + assert(quantity < *self.item_quantity, 'not enough liquidity'); + let (quantity, available, cash) = normalize(quantity, self); + let k = cash * available; + let cost = (k / (available - quantity)) - cash; + cost + } + + fn sell(self: @Market, quantity: u128) -> u128 { + let (quantity, available, cash) = normalize(quantity, self); + let k = cash * available; + let payout = cash - (k / (available + quantity)); + payout + } + + // Get normalized reserve cash amount and item quantity + fn get_reserves(self: @Market) -> (u128, u128) { + let reserve_quantity: u128 = (*self.item_quantity).into() * SCALING_FACTOR; + (*self.cash_amount, reserve_quantity) + } + + // Get the liquidity of the market + // Use cubit fixed point math library to compute the square root of the product of the reserves + fn liquidity(self: @Market) -> Fixed { + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert reserve amount to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // L = sqrt(X * Y) + (reserve_amount * reserve_quantity).sqrt() + } + + // Check if the market has liquidity + fn has_liquidity(self: @Market) -> bool { + *self.cash_amount > 0 || *self.item_quantity > 0 + } + + // Given some amount of cash, return the equivalent/optimal quantity of items + // based on the reserves in the market + fn quote_quantity(self: @Market, amount: u128) -> u128 { + assert(amount > 0, 'insufficient amount'); + assert(self.has_liquidity(), 'insufficient liquidity'); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert amount to fixed point + let amount = FixedTrait::new_unscaled(amount, false); + + // Convert reserve amount and quantity to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // dy = Y * dx / X + let quantity_optimal = (reserve_quantity * amount) / reserve_amount; + + // Convert from fixed point to u128 + let res: u128 = quantity_optimal.try_into().unwrap(); + res + } + + // Given some quantity of items, return the equivalent/optimal amount of cash + // based on the reserves in the market + fn quote_amount(self: @Market, quantity: u128) -> u128 { + assert(quantity > 0, 'insufficient quantity'); + assert(self.has_liquidity(), 'insufficient liquidity'); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert reserve amount and quantity to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // Normalize quantity + let quantity: u128 = quantity.into() * SCALING_FACTOR; + + // Convert quantity to fixed point + let quantity = FixedTrait::new_unscaled(quantity, false); + + // dx = X * dy / Y + let amount_optimal = (reserve_amount * quantity) / reserve_quantity; + + // Convert from fixed point to u128 + amount_optimal.try_into().unwrap() + } + + // Inner function to add liquidity to the market, computes the optimal amount and quantity + // + // Arguments: + // + // amount: The amount of cash to add to the market + // quantity: The quantity of items to add to the market + // + // Returns: + // + // (amount, quantity): The amount of cash and quantity of items added to the market + fn add_liquidity_inner(self: @Market, amount: u128, quantity: u128) -> (u128, u128) { + // If there is no liquidity, then the amount and quantity are the optimal + if !self.has_liquidity() { + // Ensure that the amount and quantity are greater than zero + assert(amount > 0, 'insufficient amount'); + assert(quantity > 0, 'insufficient quantity'); + (amount, quantity) + } else { + // Given the amount, get optimal quantity to add to the market + let quantity_optimal = self.quote_quantity(amount); + if quantity_optimal <= quantity { + // Add the given amount and optimal quantity to the market + (amount, quantity_optimal) + } else { + let amount_optimal = self.quote_amount(quantity); + // Ensure that the optimal amount is less than or equal to the given amount + assert(amount_optimal <= amount, 'insufficient amount'); + (amount_optimal, quantity) + } + } + } + + // Add liquidity to the market, mints shares for the given amount of liquidity provided + // + // Arguments: + // + // amount: The amount of cash to add to the market + // quantity: The quantity of items to add to the market + // + // Returns: + // + // (amount, quantity, shares): The amount of cash and quantity of items added to the market and the shares minted + fn add_liquidity(self: @Market, amount: u128, quantity: u128) -> (u128, u128, Fixed) { + // Compute the amount and quantity to add to the market + let (amount, quantity) = self.add_liquidity_inner(amount, quantity); + // Mint shares for the given amount of liquidity provided + let shares = self.mint_shares(amount, quantity); + (amount, quantity, shares) + } + + // Mint shares for the given amount of liquidity provided + fn mint_shares(self: @Market, amount: u128, quantity: u128) -> Fixed { + // If there is no liquidity, then mint total shares + if !self.has_liquidity() { + let quantity: u128 = quantity.into() * SCALING_FACTOR; + (FixedTrait::new_unscaled(amount, false) * FixedTrait::new_unscaled(quantity, false)) + .sqrt() + } else { + // Convert amount to fixed point + let amount = FixedTrait::new_unscaled(amount, false); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, _) = self.get_reserves(); + + // Convert reserve amount to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + + // Get total liquidity + let liquidity = self.liquidity(); + + // Compute the amount of shares to mint + // S = dx * L/X = dy * L/Y + (amount * liquidity) / reserve_amount + } + } + + // Remove liquidity from the market, return the corresponding amount and quantity payout + // + // Arguments: + // + // shares: The amount of liquidity shares to remove from the market + // + // Returns: + // + // (amount, quantity): The amount of cash and quantity of items removed from the market + fn remove_liquidity(self: @Market, shares: Fixed) -> (u128, u128) { + // Ensure that the market has liquidity + let liquidity = self.liquidity(); + assert(shares <= liquidity, 'insufficient liquidity'); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert reserve amount and quantity to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // Compute the amount and quantity to remove from the market + // dx = S * X / L + let amount = (shares * reserve_amount) / liquidity; + // dy = S * Y / L + let quantity = (shares * reserve_quantity) / liquidity; + + // Convert amount and quantity both from fixed point to u128 and unscaled u128, respectively + (amount.try_into().unwrap(), quantity.try_into().unwrap() / SCALING_FACTOR) + } +} + +fn normalize(quantity: u128, market: @Market) -> (u128, u128, u128) { + let quantity: u128 = quantity.into() * SCALING_FACTOR; + let available: u128 = (*market.item_quantity).into() * SCALING_FACTOR; + (quantity, available, *market.cash_amount) +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::{Market, MarketTrait, SCALING_FACTOR}; + use super::{Fixed, FixedTrait}; + + // Constants + + const TOLERANCE: u128 = 18446744073709550; // 0.001 + + // Helpers + + fn assert_approx_equal(expected: Fixed, actual: Fixed, tolerance: u128) { + let left_bound = expected - FixedTrait::new(tolerance, false); + let right_bound = expected + FixedTrait::new(tolerance, false); + assert(left_bound <= actual && actual <= right_bound, 'Not approx eq'); + } + + #[test] + #[should_panic(expected: ('not enough liquidity',))] + fn test_not_enough_quantity() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 1 + }; // pool 1:1 + let cost = market.buy(10); + } + + #[test] + #[available_gas(100000)] + fn test_market_buy() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let cost = market.buy(5); + assert(cost == SCALING_FACTOR * 1, 'wrong cost'); + } + + #[test] + #[available_gas(100000)] + fn test_market_sell() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let payout = market.sell(5); + assert(payout == 3334, 'wrong payout'); + } + + #[test] + #[available_gas(500000)] + fn test_market_add_liquidity_no_initial() { + // Without initial liquidity + let market = Market { item_id: 1, cash_amount: 0, item_quantity: 0 }; + + // Add liquidity + let (amount, quantity) = (SCALING_FACTOR * 5, 5); // pool 1:1 + let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); + + // Assert that the amount and quantity added are the same as the given amount and quantity + // and that the liquidity shares minted are the same as the entire liquidity + assert(amount_add == amount, 'wrong cash amount'); + assert(quantity_add == quantity, 'wrong item quantity'); + + // Convert amount and quantity to fixed point + let amount = FixedTrait::new_unscaled(amount, false); + let quantity: u128 = quantity.into() * SCALING_FACTOR; + let quantity = FixedTrait::new_unscaled(quantity, false); + assert(liquidity_add == (amount * quantity).sqrt(), 'wrong liquidity'); + } + + #[test] + #[available_gas(600000)] + fn test_market_add_liquidity_optimal() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Add liquidity with the same ratio + let (amount, quantity) = (SCALING_FACTOR * 2, 20); // pool 1:10 + let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); + + // Assert + assert(amount_add == amount, 'wrong cash amount'); + assert(quantity_add == quantity, 'wrong item quantity'); + + // Get expected amount and convert to fixed point + let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount, false); + let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; + let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); + + // Compute the expected liquidity shares + let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); + let final_liquidity = initial_liquidity + liquidity_add; + assert_approx_equal(expected_liquidity, final_liquidity, TOLERANCE); + } + + #[test] + #[available_gas(1000000)] + fn test_market_add_liquidity_not_optimal() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Add liquidity without the same ratio + let (amount, quantity) = (SCALING_FACTOR * 2, 10); // pool 1:5 + + let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); + + // Assert that the amount added is optimal even though the + // amount originally requested was not + let amount_optimal = SCALING_FACTOR * 1; + assert(amount_add == amount_optimal, 'wrong cash amount'); + assert(quantity_add == quantity, 'wrong item quantity'); + + // Get expected amount and convert to fixed point + let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount_add, false); + let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; + let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); + + // Get expecteed liquidity + let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); + + let final_liquidity = initial_liquidity + liquidity_add; + // assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); + } + + #[test] + #[should_panic(expected: ('insufficient amount',))] + fn test_market_add_liquidity_insufficient_amount() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + // Adding 20 items requires (SCALING_FACTOR * 2) cash amount to maintain the ratio + // Therefore this should fail + let (amount_add, quantity_add, liquidity_add) = market + .add_liquidity(SCALING_FACTOR * 1, 20); + } + + #[test] + #[available_gas(1000000)] + fn test_market_remove_liquidity() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 2, item_quantity: 20 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Remove half of the liquidity + let two = FixedTrait::new_unscaled(2, false); + let liquidity_remove = initial_liquidity / two; + + let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); + + // Assert that the amount and quantity removed are half of the initial amount and quantity + assert(amount_remove == SCALING_FACTOR * 1, 'wrong cash amount'); + assert(quantity_remove == 10, 'wrong item quantity'); + + // Get expected amount and convert to fixed point + let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 2 - amount_remove, false); + let expected_quantity: u128 = (20 - quantity_remove).into() * SCALING_FACTOR; + let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); + + // Get expecteed liquidity + let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); + + let final_liquidity = initial_liquidity - liquidity_remove; + // assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); + } + + #[test] + #[should_panic(expected: ('insufficient liquidity',))] + fn test_market_remove_liquidity_no_initial() { + // Without initial liquidity + let market = Market { item_id: 1, cash_amount: 0, item_quantity: 0 }; // pool 1:10 + + // Remove liquidity + let one = FixedTrait::new_unscaled(1, false); + + let (amount_remove, quantity_remove) = market.remove_liquidity(one); + } + + #[test] + #[should_panic(expected: ('insufficient liquidity',))] + fn test_market_remove_liquidity_more_than_available() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 2, item_quantity: 20 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Remove twice of the liquidity + let two = FixedTrait::new_unscaled(2, false); + let liquidity_remove = initial_liquidity * two; + + let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); + } +} diff --git a/examples/market/src/systems/liquidity.cairo b/examples/market/src/systems/liquidity.cairo new file mode 100644 index 00000000..ca6ca349 --- /dev/null +++ b/examples/market/src/systems/liquidity.cairo @@ -0,0 +1,128 @@ +// Dojo imports + +use dojo::world::IWorldDispatcher; + +// Extenal imports + +use cubit::f128::types::fixed::Fixed; + +trait ILiquidity { + fn add( + self: @TContractState, world: IWorldDispatcher, item_id: u32, amount: u128, quantity: u128 + ); + fn remove(self: @TContractState, world: IWorldDispatcher, item_id: u32, shares: Fixed); +} + +#[dojo::contract] +mod Liquidity { + // Internal imports + + use market::models::{ + item::Item, cash::Cash, liquidity::Liquidity, market::{Market, MarketTrait} + }; + + // Local imports + + use super::Fixed; + use super::ILiquidity; + + #[external(v0)] + impl LiquidityImpl of ILiquidity { + fn add( + self: @ContractState, + world: IWorldDispatcher, + item_id: u32, + amount: u128, + quantity: u128 + ) { + let player = starknet::get_caller_address(); + + let item = get!(world, (player, item_id), Item); + let player_quantity = item.quantity; + assert(player_quantity >= quantity, 'not enough items'); + + let player_cash = get!(world, (player), Cash); + assert(amount <= player_cash.amount, 'not enough cash'); + + let market = get!(world, (item_id), Market); + let (cost_cash, cost_quantity, liquidity_shares) = market + .add_liquidity(amount, quantity); + + // update market + set!( + world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount + cost_cash, + item_quantity: market.item_quantity + cost_quantity + }) + ); + + // update player cash + set!(world, (Cash { player: player, amount: player_cash.amount - cost_cash })); + + // update player item + set!( + world, + (Item { + player: player, item_id: item_id, quantity: player_quantity - cost_quantity + }) + ); + + // update player liquidity + let player_liquidity = get!(world, (player, item_id), Liquidity); + set!( + world, + (Liquidity { + player: player, + item_id: item_id, + shares: player_liquidity.shares + liquidity_shares + }) + ); + } + + + fn remove(self: @ContractState, world: IWorldDispatcher, item_id: u32, shares: Fixed) { + let player = starknet::get_caller_address(); + + let player_liquidity = get!(world, (player, item_id), Liquidity); + assert(player_liquidity.shares >= shares, 'not enough shares'); + + let market = get!(world, (item_id), Market); + let (payout_cash, payout_quantity) = market.remove_liquidity(shares); + + // update market + set!( + world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount - payout_cash, + item_quantity: market.item_quantity - payout_quantity + }) + ); + + // update player cash + let player_cash = get!(world, (player), Cash); + set!(world, (Cash { player: player, amount: player_cash.amount + payout_cash })); + + // update player item + let item = get!(world, (player, item_id), Item); + let player_quantity = item.quantity; + set!( + world, + (Item { + player: player, item_id: item_id, quantity: player_quantity + payout_quantity + }) + ); + + // update player liquidity + let player_liquidity = get!(world, (player, item_id), Liquidity); + set!( + world, + (Liquidity { + player: player, item_id: item_id, shares: player_liquidity.shares - shares + }) + ); + } + } +} diff --git a/examples/market/src/systems/trade.cairo b/examples/market/src/systems/trade.cairo new file mode 100644 index 00000000..6bca856b --- /dev/null +++ b/examples/market/src/systems/trade.cairo @@ -0,0 +1,86 @@ +// Dojo imports + +use dojo::world::IWorldDispatcher; + +trait ITrade { + fn buy(self: @TContractState, world: IWorldDispatcher, item_id: u32, quantity: u128); + fn sell(self: @TContractState, world: IWorldDispatcher, item_id: u32, quantity: u128); +} + +#[dojo::contract] +mod Trade { + // Internal imports + + use market::models::{item::Item, cash::Cash, market::{Market, MarketTrait}}; + + // Local imports + + use super::ITrade; + + #[external(v0)] + impl TradeImpl of ITrade { + fn buy(self: @ContractState, world: IWorldDispatcher, item_id: u32, quantity: u128) { + let player = starknet::get_caller_address(); + + let player_cash = get!(world, (player), Cash); + + let market = get!(world, (item_id), Market); + + let cost = market.buy(quantity); + assert(cost <= player_cash.amount, 'not enough cash'); + + // update market + set!( + world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount + cost, + item_quantity: market.item_quantity - quantity, + }) + ); + + // update player cash + set!(world, (Cash { player: player, amount: player_cash.amount - cost })); + + // update player item + let item = get!(world, (player, item_id), Item); + set!( + world, + (Item { player: player, item_id: item_id, quantity: item.quantity + quantity }) + ); + } + + + fn sell(self: @ContractState, world: IWorldDispatcher, item_id: u32, quantity: u128) { + let player = starknet::get_caller_address(); + + let item = get!(world, (player, item_id), Item); + let player_quantity = item.quantity; + assert(player_quantity >= quantity, 'not enough items'); + + let player_cash = get!(world, (player), Cash); + + let market = get!(world, (item_id), Market); + let payout = market.sell(quantity); + + // update market + set!( + world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount - payout, + item_quantity: market.item_quantity + quantity, + }) + ); + + // update player cash + set!(world, (Cash { player: player, amount: player_cash.amount + payout })); + + // update player item + set!( + world, + (Item { player: player, item_id: item_id, quantity: player_quantity - quantity }) + ); + } + } +} diff --git a/examples/market/src/tests/setup.cairo b/examples/market/src/tests/setup.cairo new file mode 100644 index 00000000..be384ca3 --- /dev/null +++ b/examples/market/src/tests/setup.cairo @@ -0,0 +1,41 @@ +// Starknet imports + +use starknet::ContractAddress; + +// Dojo imports + +use dojo::world::{IWorldDispatcherTrait, IWorldDispatcher}; +use dojo::test_utils::{spawn_test_world, deploy_contract}; + +// Internal imports + +use market::models::cash::{cash, Cash}; +use market::models::item::{item, Item}; +use market::models::liquidity::{liquidity, Liquidity}; +use market::models::market::{market as market_model, Market}; +use market::systems::liquidity::{Liquidity as liquidity_actions}; +use market::systems::trade::{Trade as trade_actions}; + +#[derive(Drop)] +struct Systems { + liquidity: ContractAddress, + trade: ContractAddress, +} + +fn spawn_market() -> (IWorldDispatcher, Systems) { + // [Setup] World + let mut models = array::ArrayTrait::new(); + models.append(cash::TEST_CLASS_HASH); + models.append(item::TEST_CLASS_HASH); + models.append(liquidity::TEST_CLASS_HASH); + models.append(market_model::TEST_CLASS_HASH); + let world = spawn_test_world(models); + + // [Setup] Systems + let liquidity_address = deploy_contract(liquidity_actions::TEST_CLASS_HASH, array![].span()); + let trade_address = deploy_contract(trade_actions::TEST_CLASS_HASH, array![].span()); + let systems = Systems { liquidity: liquidity_address, trade: trade_address, }; + + // [Return] + (world, systems) +} diff --git a/examples/market/src/tests/trade.cairo b/examples/market/src/tests/trade.cairo new file mode 100644 index 00000000..873117c5 --- /dev/null +++ b/examples/market/src/tests/trade.cairo @@ -0,0 +1,22 @@ +// Core imports + +use debug::PrintTrait; + +// Dojo imports + +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +// Internal imports + +use market::models::cash::Cash; +use market::models::item::Item; +use market::models::liquidity::Liquidity; +use market::models::market::{Market, MarketTrait}; +use market::tests::{setup, setup::Systems}; + +#[test] +#[available_gas(1_000_000_000)] +fn test_market_spawn() { + // [Setup] + let (world, systems) = setup::spawn_market(); +} diff --git a/examples/projectile/Scarb.lock b/examples/projectile/Scarb.lock new file mode 100644 index 00000000..b518dd7b --- /dev/null +++ b/examples/projectile/Scarb.lock @@ -0,0 +1,22 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "algebra" +version = "0.0.0" +dependencies = [ + "cubit", +] + +[[package]] +name = "cubit" +version = "1.2.0" +source = "git+https://github.com/influenceth/cubit?rev=b459053#b4590530d5aeae9aabd36740cc2a3d9e6adc5fde" + +[[package]] +name = "projectile" +version = "0.0.0" +dependencies = [ + "algebra", + "cubit", +] diff --git a/examples/projectile/Scarb.toml b/examples/projectile/Scarb.toml new file mode 100644 index 00000000..8b1d87a7 --- /dev/null +++ b/examples/projectile/Scarb.toml @@ -0,0 +1,9 @@ +[package] +name = "projectile" +version = "0.0.0" +description = "Example of algebria crate usage." +homepage = "https://github.com/dojoengine/origami/tree/examples/projectile" + +[dependencies] +cubit = { git = "https://github.com/influenceth/cubit", rev = "b459053" } +algebra = { path = "../../crates/algebra" } \ No newline at end of file diff --git a/examples/projectile/src/lib.cairo b/examples/projectile/src/lib.cairo new file mode 100644 index 00000000..6094e546 --- /dev/null +++ b/examples/projectile/src/lib.cairo @@ -0,0 +1,245 @@ +use core::option::OptionTrait; +use array::ArrayTrait; +use debug::PrintTrait; +use array::ArrayTCloneImpl; +use array::SpanTrait; +use clone::Clone; +use traits::PartialOrd; + +use cubit::f128::test::helpers::assert_precise; +use cubit::f128::types::fixed::{Fixed, FixedPartialOrd, FixedTrait, ONE_u128}; +use cubit::f128::math::trig; + +use algebra::vec2::{Vec2, Vec2Trait}; + +fn main() -> (usize, Array::, Array::) { + // to be inputs for #[view] function + // v_0_mag_felt: felt252, theta_0_deg_felt: felt252, x_0_felt: felt252, y_0_felt: felt252 + + // + // Projectile parameters + // + /// Inputs: to be contract inputs for view function `main` + /// Launch velocity magnitude, 0 <= v_0_felt <= 100 + let v_0_mag_felt = 100; + /// Launch angle in degrees, -180 <= theta_0_deg_felt <= 180 + let theta_0_deg_felt = 65; + /// Initial horizontal position, x_min <= x_0_felt <= x_max + let x_0_felt = 0; + /// Initial vertical position, y_min <= y_0_felt <= y_max + let y_0_felt = 0; + /// Convert inputs to signed fixed-point + let v_0_mag = FixedTrait::from_unscaled_felt(v_0_mag_felt); + let theta_0_deg = FixedTrait::from_unscaled_felt(theta_0_deg_felt); + let x_0 = FixedTrait::from_unscaled_felt(x_0_felt); + let y_0 = FixedTrait::from_unscaled_felt(y_0_felt); + + /// Convert theta_0_deg to radians + let theta_0 = deg_to_rad(theta_0_deg); + + // Gravitational acceleration magnitude + let g = FixedTrait::new(98 * ONE_u128 / 10, false); // 9.8 + // Plot parameters + let x_max = FixedTrait::from_unscaled_felt(1000); + let x_min = FixedTrait::from_unscaled_felt(-1000); + let y_max = FixedTrait::from_unscaled_felt(500); + let y_min = FixedTrait::from_unscaled_felt(-500); + // Check that inputs are within required ranges + assert(v_0_mag.mag <= 100 * ONE_u128, 'need v_0_mag_felt <= 100'); + assert(v_0_mag.mag > 0 * ONE_u128, 'need v_0_mag_felt > 0'); + assert(v_0_mag.sign == false, 'need v_0_mag_felt > 0'); + // `theta_0_deg.mag` not exact after conversion, so use 180.0000001 + assert(theta_0_deg.mag <= 180000001 * ONE_u128 / 1000000, '-180 <= theta_0_deg_felt <= 180'); + assert(FixedPartialOrd::le(x_0, x_max), 'need x_0 <= x_max'); + assert(FixedPartialOrd::ge(x_0, x_min), 'need x_0 >= x_min'); + assert(FixedPartialOrd::le(y_0, y_max), 'need y_0 <= y_max'); + assert(FixedPartialOrd::ge(y_0, y_min), 'need y_0 >= y_min'); + // Initial position vector + let r_0 = Vec2Trait::::new(x_0, y_0); + // Initial velocity vector + let v_0 = vec2_from_mag_theta(v_0_mag, theta_0); + + // Time interval between plotted points + let delta_t = FixedTrait::from_unscaled_felt(2); // arbitrary value 2 chosen + + // Tuples to pass to functions + let plot_params = (x_max, x_min, y_max, y_min); + let motion_params = (r_0, v_0, g, delta_t); + let (mut x_s, mut y_s) = fill_position_s(plot_params, motion_params); + (x_s.len(), x_s, y_s) +} + +fn deg_to_rad(theta_deg: Fixed) -> Fixed { + let pi = FixedTrait::new(trig::PI_u128, false); + let one_eighty = FixedTrait::new(180 * ONE_u128, false); + theta_deg * pi / one_eighty +} + +// Creates Fixed type Vec2 from magnitude, theta in radians +fn vec2_from_mag_theta(mag: Fixed, theta: Fixed) -> Vec2 { + let x_comp = mag * trig::cos(theta); // trig::cos works only for Fixed type + let y_comp = mag * trig::sin(theta); // trig::sin works only for Fixed type + Vec2:: { x: x_comp, y: y_comp } +} + +fn fill_position_s( + plot_params: (Fixed, Fixed, Fixed, Fixed), + motion_params: (Vec2, Vec2, Fixed, Fixed) +) -> (Array::, Array::) { + let (x_max, x_min, _y_max, y_min) = plot_params; + let (r_0, v_0, g, delta_t) = motion_params; + let mut x_s = ArrayTrait::::new(); + let mut y_s = ArrayTrait::::new(); + + let one = FixedTrait::new(ONE_u128, false); + let mut n = FixedTrait::new(0, false); + + loop { + // match withdraw_gas() { + // Option::Some(_) => {}, + // Option::None(_) => { + // let mut data = ArrayTrait::new(); + // data.append('Out of gas'); + // panic(data); + // }, + // } + let t = n * delta_t; + // 'n'.print(); + // n.mag.print(); + let x = calc_x(r_0.x, v_0.x, t); + // 'x'.print(); + // x.mag.print(); + // x.sign.print(); + let y = calc_y(r_0.y, v_0.y, g, t); + // 'y'.print(); + // y.mag.print(); + // y.sign.print(); + if x >= x_max || x <= x_min || y <= y_min { + break (); + } + + x_s.append(x); + y_s.append(y); + + n += one; + }; + + (x_s, y_s) +} + +fn calc_x(x_0: Fixed, v_0x: Fixed, t: Fixed) -> Fixed { + x_0 + v_0x * t +} + +fn calc_y(y_0: Fixed, v_0y: Fixed, g: Fixed, t: Fixed) -> Fixed { + let half = FixedTrait::new(5 * ONE_u128 / 10, false); + y_0 + v_0y * t - half * g * t * t +} + +#[test] +#[available_gas(2000000)] +fn test_deg_to_rad() { + let sixty = FixedTrait::new(60 * ONE_u128, false); + let theta = deg_to_rad(sixty); + assert_precise(theta, 19317385221538994246, 'invalid PI/3', Option::None(())); + assert(theta.sign == false, 'invalid sign'); + + let minus_120 = FixedTrait::new(120 * ONE_u128, true); + let theta = deg_to_rad(minus_120); + assert_precise(theta, -38634770443077988493, 'invalid -2*PI/3', Option::None(())); + assert(theta.sign == true, 'invalid sign'); +} + +#[test] +#[available_gas(20000000)] +fn test_vec2_from_mag_theta() { + let mag = FixedTrait::new(100 * ONE_u128, false); + let sixty = FixedTrait::new(60 * ONE_u128, false); + let theta = deg_to_rad(sixty); + let vec2 = vec2_from_mag_theta(mag, theta); + assert_precise(vec2.x, 922337203685477580800, 'invalid vec2.x mag', Option::None(())); // 50 + assert(vec2.x.sign == false, 'invalid vec2.x.sign'); + assert_precise(vec2.y, 1597534898494251510150, 'invalid vec2.y mag', Option::None(())); // 86.6 + assert(vec2.y.sign == false, 'invalid vec2.y.sig'); + + let minus_120 = FixedTrait::new(120 * ONE_u128, true); + let theta = deg_to_rad(minus_120); + let vec2 = vec2_from_mag_theta(mag, theta); + assert_precise(vec2.x, -922337203685477580800, 'invalid vec2.x mag', Option::None(())); // -50 + assert(vec2.x.sign == true, 'invalid vec2.x.sign'); + assert_precise( + vec2.y, -1597534898494251510150, 'invalid vec2.y mag', Option::None(()) + ); // -86.6 + assert(vec2.y.sign == true, 'invalid vec2.y.sig'); +} + +#[test] +#[available_gas(20000000)] +fn test_fill_position_s() { + let v_0_mag = FixedTrait::from_unscaled_felt(100); + let theta_0_deg = FixedTrait::from_unscaled_felt(65); + let theta_0 = deg_to_rad(theta_0_deg); + let x_0 = FixedTrait::from_unscaled_felt(0); + let y_0 = FixedTrait::from_unscaled_felt(0); + + let x_max = FixedTrait::from_unscaled_felt(1000); + let x_min = FixedTrait::from_unscaled_felt(-1000); + let y_max = FixedTrait::from_unscaled_felt(500); + let y_min = FixedTrait::from_unscaled_felt(-500); + + let r_0 = Vec2Trait::::new(x_0, y_0); + let v_0 = vec2_from_mag_theta(v_0_mag, theta_0); + let g = FixedTrait::new(98 * ONE_u128 / 10, false); + let delta_t = FixedTrait::from_unscaled_felt(2); + + let plot_params = (x_max, x_min, y_max, y_min); + let motion_params = (r_0, v_0, g, delta_t); + + let mut position_s: (Array, Array) = fill_position_s(plot_params, motion_params); + + let (x_s, y_s) = position_s; + let length = x_s.len(); + assert(length == 12, 'invalid length'); + + assert_precise( + *x_s[5], 7795930915206679528264, 'invalid x_s[5]', Option::None(()) + ); // 422.61826174069944 + assert(*x_s.at(5).sign == false, 'invalid sign'); + assert_precise( + *y_s[5], 7679523203357457794972, 'invalid y_s[5]', Option::None(()) + ); // 416.3077870366498 + assert(*y_s.at(5).sign == false, 'invalid sign'); + + assert_precise( + *x_s[10], 15591861830413359425462, 'invalid x_s[10]', Option::None(()) + ); // 845.2365234813989, custom precision 1e-6 + assert(*x_s.at(10).sign == false, 'invalid sign'); + assert_precise( + *y_s[10], -2718762785520446838411, 'invalid y_s[10]', Option::None(()) + ); // -147.3844259267005, custom precision 1e-6 + assert(*y_s.at(10).sign == true, 'invalid sign'); +} + +#[test] +#[available_gas(2000000)] +fn test_calc_x() { + let x_0 = FixedTrait::new(100 * ONE_u128, false); + let v_0x = FixedTrait::new(50 * ONE_u128, false); + let t = FixedTrait::new(16 * ONE_u128, false); + let x = calc_x(x_0, v_0x, t); + assert(x.mag == 900 * ONE_u128, 'invalid mag'); + assert(x.sign == false, 'invalid sign'); +} + +#[test] +#[available_gas(2000000)] +fn test_calc_y() { + let y_0 = FixedTrait::new(100 * ONE_u128, false); + let v_0y = FixedTrait::new(50 * ONE_u128, false); + let t = FixedTrait::new(16 * ONE_u128, false); + let g = FixedTrait::new(98 * ONE_u128 / 10, false); + + let y = calc_y(y_0, v_0y, g, t); + assert_precise(y, -6537526099722665092710, 'invalid y', Option::None(())); // -354.4 + assert(y.sign == true, 'invalid sign'); +} \ No newline at end of file diff --git a/presets/token/.gitignore b/presets/token/.gitignore new file mode 100644 index 00000000..1de56593 --- /dev/null +++ b/presets/token/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/presets/token/Scarb.lock b/presets/token/Scarb.lock new file mode 100644 index 00000000..8adc6f7f --- /dev/null +++ b/presets/token/Scarb.lock @@ -0,0 +1,20 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "dojo" +version = "0.3.3" +dependencies = [ + "dojo_plugin", +] + +[[package]] +name = "dojo_erc" +version = "0.3.3" +dependencies = [ + "dojo", +] + +[[package]] +name = "dojo_plugin" +version = "0.3.3" diff --git a/presets/token/Scarb.toml b/presets/token/Scarb.toml new file mode 100644 index 00000000..ef9edac6 --- /dev/null +++ b/presets/token/Scarb.toml @@ -0,0 +1,29 @@ +[package] +cairo-version = "2.3.1" +description = "Implementations of ERC standards for the Dojo framework" +name = "dojo_erc" +version = "0.3.3" + +[cairo] +sierra-replace-ids = true + +[lib] + +[dependencies] +dojo = { path = "../dojo-core" } + +[scripts] +build = "sozo build" +migrate = "sozo migrate" +test = "sozo test" + +# Generate targets/manifest with sozo +[[target.dojo]] + + +[tool.dojo.env] +# Katana +rpc_url = "http://localhost:5050" +account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" +private_key = "0x1800000000300000180000000000030000000000003006001800006600" +world_address = "0x4cb5561a3a2a19d14f67f71a9da59c9194e2bbb44e1774260a111094fbd8f39" \ No newline at end of file diff --git a/presets/token/src/lib.cairo b/presets/token/src/lib.cairo new file mode 100644 index 00000000..537938a7 --- /dev/null +++ b/presets/token/src/lib.cairo @@ -0,0 +1,17 @@ +mod token { + mod erc20; + mod erc20_models; + mod erc721; + mod erc1155; + +} + +#[cfg(test)] +mod tests { + mod constants; + mod utils; + + mod erc20_tests; + mod erc721_tests; + mod erc1155_tests; +} diff --git a/presets/token/src/tests/constants.cairo b/presets/token/src/tests/constants.cairo new file mode 100644 index 00000000..6487fc18 --- /dev/null +++ b/presets/token/src/tests/constants.cairo @@ -0,0 +1,60 @@ +use starknet::ContractAddress; +use starknet::contract_address_const; + +const NAME: felt252 = 'NAME'; +const SYMBOL: felt252 = 'SYMBOL'; +const DECIMALS: u8 = 18_u8; +const SUPPLY: u256 = 2000; +const VALUE: u256 = 300; +const ROLE: felt252 = 'ROLE'; +const OTHER_ROLE: felt252 = 'OTHER_ROLE'; +const URI: felt252 = 'URI'; +const TOKEN_ID: u256 = 21; +const TOKEN_AMOUNT: u256 = 42; +const TOKEN_ID_2: u256 = 2; +const TOKEN_AMOUNT_2: u256 = 69; +const PUBKEY: felt252 = 'PUBKEY'; + +fn ADMIN() -> ContractAddress { + contract_address_const::<'ADMIN'>() +} + +fn AUTHORIZED() -> ContractAddress { + contract_address_const::<'AUTHORIZED'>() +} + +fn ZERO() -> ContractAddress { + contract_address_const::<0>() +} + +fn CALLER() -> ContractAddress { + contract_address_const::<'CALLER'>() +} + +fn OWNER() -> ContractAddress { + contract_address_const::<'OWNER'>() +} + +fn NEW_OWNER() -> ContractAddress { + contract_address_const::<'NEW_OWNER'>() +} + +fn OTHER() -> ContractAddress { + contract_address_const::<'OTHER'>() +} + +fn OTHER_ADMIN() -> ContractAddress { + contract_address_const::<'OTHER_ADMIN'>() +} + +fn SPENDER() -> ContractAddress { + contract_address_const::<'SPENDER'>() +} + +fn RECIPIENT() -> ContractAddress { + contract_address_const::<'RECIPIENT'>() +} + +fn OPERATOR() -> ContractAddress { + contract_address_const::<'OPERATOR'>() +} diff --git a/presets/token/src/tests/erc1155_tests.cairo b/presets/token/src/tests/erc1155_tests.cairo new file mode 100644 index 00000000..6951695b --- /dev/null +++ b/presets/token/src/tests/erc1155_tests.cairo @@ -0,0 +1,836 @@ +use dojo_erc::tests::utils; +use dojo_erc::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID, TOKEN_AMOUNT, + TOKEN_ID_2, TOKEN_AMOUNT_2 +}; + +use dojo_erc::token::erc1155::ERC1155::ERC1155Impl; +use dojo_erc::token::erc1155::ERC1155::ERC1155CamelOnlyImpl; +use dojo_erc::token::erc1155::ERC1155::ERC1155MetadataImpl; +use dojo_erc::token::erc1155::ERC1155::InternalImpl; +use dojo_erc::token::erc1155::ERC1155::WorldInteractionsImpl; +use dojo_erc::token::erc1155::ERC1155::{TransferSingle, TransferBatch, ApprovalForAll}; +use dojo_erc::token::erc1155::ERC1155; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::testing; +use zeroable::Zeroable; +use dojo::test_utils::spawn_test_world; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::token::erc1155::models::{ + ERC1155Meta, erc_1155_meta, ERC1155OperatorApproval, erc_1155_operator_approval, ERC1155Balance, + erc_1155_balance +}; +use dojo_erc::token::erc1155::ERC1155::_worldContractMemberStateTrait; +use debug::PrintTrait; + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC1155::ContractState) { + let world = spawn_test_world( + array![ + erc_1155_meta::TEST_CLASS_HASH, + erc_1155_operator_approval::TEST_CLASS_HASH, + erc_1155_balance::TEST_CLASS_HASH, + ] + ); + let mut state = ERC1155::contract_state_for_testing(); + state._world.write(world.contract_address); + + InternalImpl::_mint(ref state, OWNER(), TOKEN_ID, TOKEN_AMOUNT); + utils::drop_event(ZERO()); + + InternalImpl::_mint(ref state, OWNER(), TOKEN_ID_2, TOKEN_AMOUNT_2); + utils::drop_event(ZERO()); + + (world, state) +} + +fn setup() -> ERC1155::ContractState { + let (world, mut state) = STATE(); + ERC1155::constructor(ref state, world.contract_address, NAME, SYMBOL, URI); + utils::drop_event(ZERO()); + state +} + +// +// initializer & constructor +// + +#[test] +#[available_gas(20000000)] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC1155::constructor(ref state, world.contract_address, NAME, SYMBOL, URI); + + assert(ERC1155MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC1155MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC1155MetadataImpl::uri(@state, 0) == URI, 'Uri should be URI'); +// assert( +// SRC5Impl::supports_interface(@state, erc1155::interface::IERC1155_ID), 'Missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc1155::interface::IERC1155_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + +#[test] +#[available_gas(20000000)] +fn test_initializer() { + let (world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL, URI); + + assert(ERC1155MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC1155MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + + assert(ERC1155Impl::balance_of(@state, OWNER(), 0) == 0, 'Balance should be zero'); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'should be TOKEN_AMOUNT' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'should be TOKEN_AMOUNT_2' + ); +} + + +// +// Getters +// + +#[test] +#[available_gas(20000000)] +fn test_balance_of() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Should return balance' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid account',))] +fn test_balance_of_zero() { + let state = setup(); + ERC1155Impl::balance_of(@state, ZERO(), TOKEN_ID); +} + + +#[test] +#[available_gas(20000000)] +fn test_balance_of_batch() { + let mut state = setup(); + + InternalImpl::_mint(ref state, OTHER(), TOKEN_ID_2, TOKEN_AMOUNT_2); + + let balances = ERC1155Impl::balance_of_batch( + @state, array![OWNER(), OTHER()], array![TOKEN_ID, TOKEN_ID_2] + ); + + assert(*balances.at(0) == TOKEN_AMOUNT, 'Should return TOKEN_AMOUNT'); + assert(*balances.at(1) == TOKEN_AMOUNT_2, 'Should return TOKEN_AMOUNT_2'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid account',))] +fn test_balance_of_batch_zero() { + let state = setup(); + ERC1155Impl::balance_of_batch(@state, array![OTHER(), ZERO()], array![TOKEN_ID_2, TOKEN_ID]); +} + + +// +// set_approval_for_all & _set_approval_for_all +// + +#[test] +#[available_gas(20000000)] +fn test_set_approval_for_all() { + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + + assert(!ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Approval not revoked correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_true() { + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC1155Impl::set_approval_for_all(ref state, OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_false() { + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC1155Impl::set_approval_for_all(ref state, OWNER(), false); +} + +#[test] +#[available_gas(20000000)] +fn test__set_approval_for_all() { + let (world, mut state) = STATE(); + assert(!ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC1155Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_true() { + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_false() { + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), false); +} + + +// +// safe_transfer_from & safeTransferFrom +// + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_from_owner() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155Impl::safe_transfer_from(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_owner() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safe_transfer_from_zero() { + let (world, mut state) = STATE(); + ERC1155Impl::safe_transfer_from( + ref state, ZERO(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safeTransferFrom_zero() { + let (world, mut state) = STATE(); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, ZERO(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safe_transfer_from_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC1155Impl::safe_transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID, TOKEN_AMOUNT, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safeTransferFrom_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, OWNER(), ZERO(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_from_to_owner() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + + testing::set_caller_address(OWNER()); + ERC1155Impl::safe_transfer_from(ref state, OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT, array![]); + assert_event_transfer_single(OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner after' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_safeTransferFrom_to_owner() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + + testing::set_caller_address(OWNER()); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); + assert_event_transfer_single(OWNER(), OWNER(), TOKEN_ID, TOKEN_AMOUNT); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner after' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_approved_for_all() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155Impl::safe_transfer_from(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(50000000)] +fn test_safeTransferFrom_approved_for_all() { + let mut state = setup(); + let id = TOKEN_ID; + let amount = TOKEN_AMOUNT; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, id); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::setApprovalForAll(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, id, amount, array![]); + assert_event_transfer_single(owner, recipient, id, amount); + + assert_state_after_transfer(@state, owner, recipient, id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safe_transfer_from_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC1155Impl::safe_transfer_from( + ref state, OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safeTransferFrom_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC1155CamelOnlyImpl::safeTransferFrom( + ref state, OWNER(), RECIPIENT(), TOKEN_ID, TOKEN_AMOUNT, array![] + ); +} + + +// +// safe_batch_transfer_from & safeBatchTransferFrom +// + +#[test] +#[available_gas(50000000)] +fn test_safe_batch_transfer_from_owner() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155Impl::safe_batch_transfer_from( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(50000000)] +fn test_safeBatchTransferFrom_owner() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safe_batch_transfer_from_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155Impl::safe_batch_transfer_from(ref state, ZERO(), RECIPIENT(), ids, amounts, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: wrong sender',))] +fn test_safeBatchTransferFrom_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, ZERO(), RECIPIENT(), ids, amounts, array![] + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safe_batch_transfer_from_to_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), ZERO(), ids, amounts, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test_safeBatchTransferFrom_to_zero() { + let (world, mut state) = STATE(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), ZERO(), ids, amounts, array![]); +} + +#[test] +#[available_gas(50000000)] +fn test_safe_batch_transfer_from_to_owner() { + let mut state = setup(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner before1' + ); + + testing::set_caller_address(OWNER()); + ERC1155Impl::safe_batch_transfer_from( + ref state, OWNER(), OWNER(), ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(OWNER(), OWNER(), ids, amounts); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner after1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner after2' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_safeBatchTransferFrom_to_owner() { + let mut state = setup(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner before1' + ); + + testing::set_caller_address(OWNER()); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, OWNER(), OWNER(), ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(OWNER(), OWNER(), ids, amounts); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner after1' + ); + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner after2' + ); +} + +#[test] +#[available_gas(50000000)] +fn test_batch_transfer_from_approved_for_all() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155Impl::safe_batch_transfer_from( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(50000000)] +fn test_safeBatchTransferFrom_approved_for_all() { + let mut state = setup(); + let owner = OWNER(); + let recipient = RECIPIENT(); + + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + assert_state_before_batch_transfer(@state, owner, recipient); + + testing::set_caller_address(owner); + ERC1155CamelOnlyImpl::setApprovalForAll(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, owner, recipient, ids.clone(), amounts.clone(), array![] + ); + assert_event_transfer_batch(owner, recipient, ids, amounts); + + assert_state_after_batch_transfer(@state, owner, recipient); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safe_batch_transfer_from_unauthorized() { + let mut state = setup(); + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + testing::set_caller_address(OTHER()); + ERC1155Impl::safe_batch_transfer_from(ref state, OWNER(), RECIPIENT(), ids, amounts, array![]); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: unauthorized caller',))] +fn test_safeBatchTransferFrom_unauthorized() { + let mut state = setup(); + let ids = array![TOKEN_ID, TOKEN_ID_2]; + let amounts = array![TOKEN_AMOUNT, TOKEN_AMOUNT_2]; + + testing::set_caller_address(OTHER()); + ERC1155CamelOnlyImpl::safeBatchTransferFrom( + ref state, OWNER(), RECIPIENT(), ids, amounts, array![] + ); +} + +// +// _mint +// + +#[test] +#[available_gas(20000000)] +fn test__mint() { + let (world, mut state) = STATE(); + let recipient = RECIPIENT(); + + assert( + ERC1155Impl::balance_of(@state, recipient, TOKEN_ID_2) == 0, 'Balance of recipient before' + ); + + InternalImpl::_mint(ref state, recipient, TOKEN_ID_2, TOKEN_AMOUNT_2); + assert_event_transfer_single(ZERO(), recipient, TOKEN_ID_2, TOKEN_AMOUNT_2); + + assert( + ERC1155Impl::balance_of(@state, recipient, TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of recipient after' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: invalid receiver',))] +fn test__mint_to_zero() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, ZERO(), TOKEN_ID, TOKEN_AMOUNT); +} + + +// +// _burn +// + +#[test] +#[available_gas(20000000)] +fn test__burn() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + + testing::set_caller_address(OWNER()); + InternalImpl::_burn(ref state, TOKEN_ID, 2); + assert_event_transfer_single(OWNER(), ZERO(), TOKEN_ID, 2); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT - 2, + 'Balance of owner after' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC1155: insufficient balance',))] +fn test__burn_more_than_balance() { + let mut state = setup(); + + assert( + ERC1155Impl::balance_of(@state, OWNER(), TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of owner before' + ); + InternalImpl::_burn(ref state, TOKEN_ID, TOKEN_AMOUNT + 1); +} + + +// +// Helpers +// + +fn assert_state_before_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress, id: u256, +) { + assert(ERC1155Impl::balance_of(state, owner, id) == TOKEN_AMOUNT, 'Balance of owner before'); + assert(ERC1155Impl::balance_of(state, recipient, id) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress, id: u256 +) { + assert(ERC1155Impl::balance_of(state, owner, id) == 0, 'Balance of owner after'); + assert( + ERC1155Impl::balance_of(state, recipient, id) == TOKEN_AMOUNT, 'Balance of recipient after' + ); +} + +fn assert_state_before_batch_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress +) { + assert( + ERC1155Impl::balance_of(state, owner, TOKEN_ID) == TOKEN_AMOUNT, 'Balance of owner before1' + ); + assert( + ERC1155Impl::balance_of(state, owner, TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of owner before2' + ); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID) == 0, 'Balance of recipient before1' + ); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID_2) == 0, 'Balance of recipient before2' + ); +} + +fn assert_state_after_batch_transfer( + state: @ERC1155::ContractState, owner: ContractAddress, recipient: ContractAddress +) { + assert(ERC1155Impl::balance_of(state, owner, TOKEN_ID) == 0, 'Balance of owner after1'); + assert(ERC1155Impl::balance_of(state, owner, TOKEN_ID_2) == 0, 'Balance of owner after2'); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID) == TOKEN_AMOUNT, + 'Balance of recipient after1' + ); + assert( + ERC1155Impl::balance_of(state, recipient, TOKEN_ID_2) == TOKEN_AMOUNT_2, + 'Balance of recipient after2' + ); +} + + +// +// events +// + +fn assert_event_approval_for_all( + owner: ContractAddress, operator: ContractAddress, approved: bool +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.operator == operator, 'Invalid `operator`'); + assert(event.approved == approved, 'Invalid `approved`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer_single( + from: ContractAddress, to: ContractAddress, id: u256, amount: u256 +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.id == id, 'Invalid `id`'); + assert(event.value == amount, 'Invalid `amount`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer_batch( + from: ContractAddress, to: ContractAddress, ids: Array, amounts: Array +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.ids.len() == event.values.len(), 'Invalid array length'); + + let mut i = 0; + + loop { + if i == event.ids.len() { + break; + } + + assert(event.ids.at(i) == ids.at(i), 'Invalid `id`'); + assert(event.values.at(i) == amounts.at(i), 'Invalid `id`'); + + i += 1; + }; + + utils::assert_no_events_left(ZERO()); +} + diff --git a/presets/token/src/tests/erc20_tests.cairo b/presets/token/src/tests/erc20_tests.cairo new file mode 100644 index 00000000..669cfc7c --- /dev/null +++ b/presets/token/src/tests/erc20_tests.cairo @@ -0,0 +1,546 @@ +use integer::BoundedInt; +use integer::u256; +use integer::u256_from_felt252; +use dojo_erc::tests::utils; +use dojo_erc::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, NAME, SYMBOL, DECIMALS, SUPPLY, VALUE +}; +use dojo_erc::token::erc20::ERC20::Approval; +use dojo_erc::token::erc20::ERC20::ERC20Impl; +use dojo_erc::token::erc20::ERC20::InternalImpl; +use dojo_erc::token::erc20::ERC20::Transfer; +use dojo_erc::token::erc20::ERC20; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::testing; +use zeroable::Zeroable; +use dojo::test_utils::spawn_test_world; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::token::erc20_models::{ + ERC20Allowance, erc_20_allowance, ERC20Balance, erc_20_balance, ERC20Meta, erc_20_meta +}; +use dojo_erc::token::erc20::ERC20::_worldContractMemberStateTrait; +use debug::PrintTrait; + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC20::ContractState) { + let world = spawn_test_world( + array![ + erc_20_allowance::TEST_CLASS_HASH, + erc_20_balance::TEST_CLASS_HASH, + erc_20_meta::TEST_CLASS_HASH, + ] + ); + let mut state = ERC20::contract_state_for_testing(); + state._world.write(world.contract_address); + (world, state) +} + +fn setup() -> ERC20::ContractState { + let (world, mut state) = STATE(); + ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); + utils::drop_event(ZERO()); + state +} + +// +// initializer & constructor +// + +#[test] +#[available_gas(25000000)] +fn test_initializer() { + let (world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL); + + assert(ERC20Impl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC20Impl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC20Impl::decimals(@state) == DECIMALS, 'Decimals should be 18'); + assert(ERC20Impl::total_supply(@state) == 0, 'Supply should eq 0'); +} + + +#[test] +#[available_gas(25000000)] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); + + assert_only_event_transfer(ZERO(), OWNER(), SUPPLY); + + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq inital_supply'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq inital_supply'); + assert(ERC20Impl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC20Impl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC20Impl::decimals(@state) == DECIMALS, 'Decimals should be 18'); +} + +// +// Getters +// + +#[test] +#[available_gas(25000000)] +fn test_total_supply() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), SUPPLY); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq SUPPLY'); +} + +#[test] +#[available_gas(25000000)] +fn test_balance_of() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), SUPPLY); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq SUPPLY'); +} + + +#[test] +#[available_gas(25000000)] +fn test_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Should eq VALUE'); +} + +// +// approve & _approve +// + +#[test] +#[available_gas(25000000)] +fn test_approve() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + assert(ERC20Impl::approve(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test_approve_from_zero() { + let mut state = setup(); + ERC20Impl::approve(ref state, SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test_approve_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +fn test__approve() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + InternalImpl::_approve(ref state, OWNER(), SPENDER(), VALUE); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test__approve_from_zero() { + let mut state = setup(); + InternalImpl::_approve(ref state, Zeroable::zero(), SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test__approve_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + InternalImpl::_approve(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +// +// transfer & _transfer +// + +#[test] +#[available_gas(25000000)] +fn test_transfer() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + assert(ERC20Impl::transfer(ref state, RECIPIENT(), VALUE), 'Should return true'); + + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq VALUE'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - VALUE'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(25000000)] +fn test__transfer() { + let mut state = setup(); + + InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), VALUE); + + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test__transfer_not_enough_balance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + + let balance_plus_one = SUPPLY + 1; + InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), balance_plus_one); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: transfer from 0',))] +fn test__transfer_from_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, Zeroable::zero(), RECIPIENT(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test__transfer_to_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +// +// transfer_from +// + +#[test] +#[available_gas(30000000)] +fn test_transfer_from() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + testing::set_caller_address(SPENDER()); + assert(ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), VALUE), 'Should return true'); + + assert_event_approval(OWNER(), SPENDER(), 0); + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Should eq amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq suppy - amount'); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == 0, 'Should eq 0'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(25000000)] +fn test_transfer_from_doesnt_consume_infinite_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), BoundedInt::max()); + + testing::set_caller_address(SPENDER()); + ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), VALUE); + + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == BoundedInt::max(), + 'Allowance should not change' + ); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_transfer_from_greater_than_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + testing::set_caller_address(SPENDER()); + let allowance_plus_one = VALUE + 1; + ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), allowance_plus_one); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test_transfer_from_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + testing::set_caller_address(SPENDER()); + ERC20Impl::transfer_from(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_transfer_from_from_zero_address() { + let mut state = setup(); + ERC20Impl::transfer_from(ref state, Zeroable::zero(), RECIPIENT(), VALUE); +} + +// +// increase_allowance & increaseAllowance +// + +#[test] +#[available_gas(25000000)] +fn test_increase_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::increase_allowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE * 2); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test_increase_allowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::increase_allowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test_increase_allowance_from_zero_address() { + let mut state = setup(); + ERC20::increase_allowance(ref state, SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +fn test_increaseAllowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::increaseAllowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), 2 * VALUE); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test_increaseAllowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::increaseAllowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test_increaseAllowance_from_zero_address() { + let mut state = setup(); + ERC20::increaseAllowance(ref state, SPENDER(), VALUE); +} + +// +// decrease_allowance & decreaseAllowance +// + +#[test] +#[available_gas(25000000)] +fn test_decrease_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::decrease_allowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), 0); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decrease_allowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::decrease_allowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decrease_allowance_from_zero_address() { + let mut state = setup(); + ERC20::decrease_allowance(ref state, SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +fn test_decreaseAllowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::decreaseAllowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), 0); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decreaseAllowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::decreaseAllowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decreaseAllowance_from_zero_address() { + let mut state = setup(); + ERC20::decreaseAllowance(ref state, SPENDER(), VALUE); +} + +// +// _spend_allowance +// + +#[test] +#[available_gas(25000000)] +fn test__spend_allowance_not_unlimited() { + let mut state = setup(); + + InternalImpl::_approve(ref state, OWNER(), SPENDER(), SUPPLY); + utils::drop_event(ZERO()); + + InternalImpl::_spend_allowance(ref state, OWNER(), SPENDER(), VALUE); + + assert_only_event_approval(OWNER(), SPENDER(), SUPPLY - VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == SUPPLY - VALUE, + 'Should eq supply - amount' + ); +} + +#[test] +#[available_gas(25000000)] +fn test__spend_allowance_unlimited() { + let mut state = setup(); + InternalImpl::_approve(ref state, OWNER(), SPENDER(), BoundedInt::max()); + + let max_minus_one: u256 = BoundedInt::max() - 1; + InternalImpl::_spend_allowance(ref state, OWNER(), SPENDER(), max_minus_one); + + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == BoundedInt::max(), + 'Allowance should not change' + ); +} + +// +// _mint +// + +#[test] +#[available_gas(25000000)] +fn test__mint() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), VALUE); + assert_only_event_transfer(ZERO(), OWNER(), VALUE); + assert(ERC20Impl::balance_of(@state, OWNER()) == VALUE, 'Should eq amount'); + assert(ERC20Impl::total_supply(@state) == VALUE, 'Should eq total supply'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: mint to 0',))] +fn test__mint_to_zero() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, Zeroable::zero(), VALUE); +} + +// +// _burn +// + +#[test] +#[available_gas(25000000)] +fn test__burn() { + let mut state = setup(); + InternalImpl::_burn(ref state, OWNER(), VALUE); + + assert_only_event_transfer(OWNER(), ZERO(), VALUE); + assert(ERC20Impl::total_supply(@state) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: burn from 0',))] +fn test__burn_from_zero() { + let mut state = setup(); + InternalImpl::_burn(ref state, Zeroable::zero(), VALUE); +} + +// +// Helpers +// + +fn assert_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.spender == spender, 'Invalid `spender`'); + assert(event.value == value, 'Invalid `value`'); +} + +fn assert_only_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { + assert_event_approval(owner, spender, value); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.value == value, 'Invalid `value`'); +} + +fn assert_only_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { + assert_event_transfer(from, to, value); + utils::assert_no_events_left(ZERO()); +} diff --git a/presets/token/src/tests/erc721_tests.cairo b/presets/token/src/tests/erc721_tests.cairo new file mode 100644 index 00000000..415db0ff --- /dev/null +++ b/presets/token/src/tests/erc721_tests.cairo @@ -0,0 +1,1453 @@ +use integer::u256; +use integer::u256_from_felt252; +use dojo_erc::tests::utils; +use dojo_erc::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID +}; + +use dojo_erc::token::erc721::ERC721::ERC721Impl; +use dojo_erc::token::erc721::ERC721::ERC721CamelOnlyImpl; +use dojo_erc::token::erc721::ERC721::ERC721MetadataImpl; +use dojo_erc::token::erc721::ERC721::InternalImpl; +use dojo_erc::token::erc721::ERC721::WorldInteractionsImpl; +use dojo_erc::token::erc721::ERC721::{Approval, ApprovalForAll, Transfer}; +use dojo_erc::token::erc721::ERC721; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::testing; +use zeroable::Zeroable; +use dojo::test_utils::spawn_test_world; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::token::erc721::models::{ + ERC721Meta, erc_721_meta, ERC721OperatorApproval, erc_721_operator_approval, ERC721Owner, + erc_721_owner, ERC721Balance, erc_721_balance, ERC721TokenApproval, erc_721_token_approval +}; +use dojo_erc::token::erc721::ERC721::_worldContractMemberStateTrait; +use debug::PrintTrait; + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC721::ContractState) { + let world = spawn_test_world( + array![ + erc_721_meta::TEST_CLASS_HASH, + erc_721_operator_approval::TEST_CLASS_HASH, + erc_721_owner::TEST_CLASS_HASH, + erc_721_balance::TEST_CLASS_HASH, + erc_721_token_approval::TEST_CLASS_HASH, + ] + ); + let mut state = ERC721::contract_state_for_testing(); + state._world.write(world.contract_address); + (world, state) +} + +fn setup() -> ERC721::ContractState { + let (world, mut state) = STATE(); + ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); + utils::drop_event(ZERO()); + state +} + +// fn setup_receiver() -> ContractAddress { +// utils::deploy(ERC721Receiver::TEST_CLASS_HASH, array![]) +// } + +// fn setup_camel_receiver() -> ContractAddress { +// utils::deploy(CamelERC721ReceiverMock::TEST_CLASS_HASH, array![]) +// } + +// fn setup_account() -> ContractAddress { +// let mut calldata = array![PUBKEY]; +// utils::deploy(Account::TEST_CLASS_HASH, calldata) +// } + +// fn setup_camel_account() -> ContractAddress { +// let mut calldata = array![PUBKEY]; +// utils::deploy(CamelAccountMock::TEST_CLASS_HASH, calldata) +// } + +// +// initializer & constructor +// + +#[test] +#[available_gas(20000000)] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); + + assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance should be one'); + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'OWNER should be owner'); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + +#[test] +#[available_gas(10000000)] +fn test_initializer() { + let (world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL, URI); + + assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + + assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance should be zero'); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + + +// +// Getters +// + +#[test] +#[available_gas(20000000)] +fn test_balance_of() { + let state = setup(); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Should return balance'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid account',))] +fn test_balance_of_zero() { + let state = setup(); + ERC721Impl::balance_of(@state, ZERO()); +} + +#[test] +#[available_gas(20000000)] +fn test_owner_of() { + let state = setup(); + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Should return owner'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_owner_of_non_minted() { + let state = setup(); + ERC721Impl::owner_of(@state, u256_from_felt252(7)); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_token_uri_non_minted() { + let state = setup(); + ERC721MetadataImpl::token_uri(@state, u256_from_felt252(7)); +} + +#[test] +#[available_gas(20000000)] +fn test_get_approved() { + let mut state = setup(); + let spender = SPENDER(); + let token_id = TOKEN_ID; + + assert(ERC721Impl::get_approved(@state, token_id) == ZERO(), 'Should return non-approval'); + InternalImpl::_approve(ref state, spender, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == spender, 'Should return approval'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_get_approved_nonexistent() { + let mut state = setup(); + ERC721Impl::get_approved(@state, u256_from_felt252(7)); +} + +#[test] +#[available_gas(20000000)] +fn test__exists() { + let (world, mut state) = STATE(); + let token_id = TOKEN_ID; + + assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' + ); + + InternalImpl::_mint(ref state, RECIPIENT(), token_id); + + assert(InternalImpl::_exists(@state, token_id), 'Token should exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == RECIPIENT(), + 'Invalid owner' + ); + + InternalImpl::_burn(ref state, token_id); + + assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' + ); +} + + +// +// approve & _approve +// + +#[test] +#[available_gas(20000000)] +fn test_approve_from_owner() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +fn test_approve_from_operator() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_approve_from_unauthorized() { + let mut state = setup(); + + testing::set_caller_address(OTHER()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: approval to owner',))] +fn test_approve_to_owner() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::approve(ref state, OWNER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_approve_nonexistent() { + // let mut state = STATE(); + let (world, mut state) = STATE(); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +fn test__approve() { + let mut state = setup(); + InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: approval to owner',))] +fn test__approve_to_owner() { + let mut state = setup(); + InternalImpl::_approve(ref state, OWNER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__approve_nonexistent() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); +} + +// +// set_approval_for_all & _set_approval_for_all +// + +#[test] +#[available_gas(20000000)] +fn test_set_approval_for_all() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + + assert(!ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Approval not revoked correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_true() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_false() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OWNER(), false); +} + +#[test] +#[available_gas(20000000)] +fn test__set_approval_for_all() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + assert(!ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_true() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_false() { + // let mut state = STATE(); + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), false); +} + + +// +// transfer_from & transferFrom +// + +#[test] +#[available_gas(60000000)] +fn test_transfer_from_owner() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + // set approval to check reset + InternalImpl::_approve(ref state, OTHER(), token_id); + utils::drop_event(ZERO()); + + assert_state_before_transfer(@state, owner, recipient, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); + + testing::set_caller_address(owner); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_owner() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + // set approval to check reset + InternalImpl::_approve(ref state, OTHER(), token_id); + utils::drop_event(ZERO()); + + assert_state_before_transfer(@state, owner, recipient, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); + + testing::set_caller_address(owner); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_transfer_from_nonexistent() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + ERC721Impl::transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_transferFrom_nonexistent() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + ERC721CamelOnlyImpl::transferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test_transfer_from_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC721Impl::transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test_transferFrom_to_zero() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_to_owner() { + let mut state = setup(); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + + testing::set_caller_address(OWNER()); + ERC721Impl::transfer_from(ref state, OWNER(), OWNER(), TOKEN_ID); + assert_event_transfer(OWNER(), OWNER(), TOKEN_ID); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_to_owner() { + let mut state = setup(); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + + testing::set_caller_address(OWNER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), OWNER(), TOKEN_ID); + assert_event_transfer( OWNER(), OWNER(), TOKEN_ID); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_approved() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::approve(ref state, OPERATOR(), token_id); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_approved() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + assert_state_before_transfer(@state,owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::approve(ref state, OPERATOR(), token_id); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state,owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_approved_for_all() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_approved_for_all() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state,owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state,owner, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_transfer_from_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC721Impl::transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_transferFrom_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID); +} + +// // +// // safe_transfer_from & safeTransferFrom +// // + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_account() { +// let mut state = setup(); +// let account = setup_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_account() { +// let mut state = setup(); +// let account = setup_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_account_camel() { +// let mut state = setup(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_account_camel() { +// let mut state = setup(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_receiver() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_receiver() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_receiver_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_receiver_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safe_transfer_from_to_receiver_failure() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safeTransferFrom_to_receiver_failure() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safe_transfer_from_to_receiver_failure_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safeTransferFrom_to_receiver_failure_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test_safe_transfer_from_to_non_receiver() { +// let mut state = setup(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, recipient, token_id, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test_safeTransferFrom_to_non_receiver() { +// let mut state = setup(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, token_id, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test_safe_transfer_from_nonexistent() { +// let mut state = STATE(); +// ERC721Impl::safe_transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test_safeTransferFrom_nonexistent() { +// let mut state = STATE(); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test_safe_transfer_from_to_zero() { +// let mut state = setup(); +// testing::set_caller_address(OWNER()); +// ERC721Impl::safe_transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test_safeTransferFrom_to_zero() { +// let mut state = setup(); +// testing::set_caller_address(OWNER()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_owner() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_owner() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_owner_camel() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_camel_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_owner_camel() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_camel_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved_for_all() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved_for_all() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved_for_all_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved_for_all_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: unauthorized caller',))] +// fn test_safe_transfer_from_unauthorized() { +// let mut state = setup(); +// testing::set_caller_address(OTHER()); +// ERC721Impl::safe_transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: unauthorized caller',))] +// fn test_safeTransferFrom_unauthorized() { +// let mut state = setup(); +// testing::set_caller_address(OTHER()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// +// _transfer +// + +#[test] +#[available_gas(50000000)] +fn test__transfer() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, token_id); + + InternalImpl::_transfer(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__transfer_nonexistent() { + let (world, mut state) = STATE(); + InternalImpl::_transfer(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test__transfer_to_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: wrong sender',))] +fn test__transfer_from_invalid_owner() { + let mut state = setup(); + InternalImpl::_transfer(ref state, RECIPIENT(), OWNER(), TOKEN_ID); +} + +// +// _mint +// + +#[test] +#[available_gas(20000000)] +fn test__mint() { + let (world, mut state) = STATE(); + let recipient = RECIPIENT(); + let token_id = TOKEN_ID; + + assert_state_before_mint(@state, recipient); + InternalImpl::_mint(ref state, recipient, TOKEN_ID); + assert_event_transfer(ZERO(), recipient, token_id); + + assert_state_after_mint(@state, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test__mint_to_zero() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: token already minted',))] +fn test__mint_already_exist() { + let mut state = setup(); + InternalImpl::_mint(ref state, RECIPIENT(), TOKEN_ID); +} + +// // +// // _safe_mint +// // + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_receiver() { +// let mut state = STATE(); +// let recipient = setup_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_event_transfer(ZERO(), recipient, token_id); + +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_receiver_camel() { +// let mut state = STATE(); +// let recipient = setup_camel_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_event_transfer(ZERO(), recipient, token_id); + +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_account() { +// let mut state = STATE(); +// let account = setup_account(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,account); +// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); +// assert_event_transfer(ZERO(), account, token_id); + +// assert_state_after_mint(@state,account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_account_camel() { +// let mut state = STATE(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,account); +// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); +// assert_event_transfer(ZERO(), account, token_id); + +// assert_state_after_mint(@state,account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test__safe_mint_to_non_receiver() { +// let mut state = STATE(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe mint failed',))] +// fn test__safe_mint_to_receiver_failure() { +// let mut state = STATE(); +// let recipient = setup_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe mint failed',))] +// fn test__safe_mint_to_receiver_failure_camel() { +// let mut state = STATE(); +// let recipient = setup_camel_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test__safe_mint_to_zero() { +// let mut state = STATE(); +// InternalImpl::_safe_mint(ref state, ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: token already minted',))] +// fn test__safe_mint_already_exist() { +// let mut state = setup(); +// InternalImpl::_safe_mint(ref state, RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// +// _burn +// + +#[test] +#[available_gas(25000000)] +fn test__burn() { + let mut state = setup(); + + InternalImpl::_approve(ref state, OTHER(), TOKEN_ID); + utils::drop_event(ZERO()); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + assert(ERC721Impl::get_approved(@state, TOKEN_ID) == OTHER(), 'Approval before'); + + InternalImpl::_burn(ref state, TOKEN_ID); + assert_event_transfer(OWNER(), ZERO(), TOKEN_ID); + + assert( + WorldInteractionsImpl::get_owner_of(@state, TOKEN_ID).address == ZERO(), 'Ownership after' + ); + assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance of owner after'); + assert( + WorldInteractionsImpl::get_token_approval(@state, TOKEN_ID).address == ZERO(), + 'Approval after' + ); +} + +#[test] +#[available_gas(30000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__burn_nonexistent() { + let (mut world, mut state) = STATE(); + InternalImpl::_burn(ref state, TOKEN_ID); +} + +// +// _set_token_uri +// + +// #[test] +// #[available_gas(20000000)] +// fn test__set_token_uri() { +// let mut state = setup(); + +// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == 0, 'URI should be 0'); +// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); +// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == URI, 'URI should be set'); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test__set_token_uri_nonexistent() { +// let mut state = STATE(); +// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); +// } + +// +// Helpers +// + +fn assert_state_before_transfer( + state: @ERC721::ContractState, + owner: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == owner, 'Ownership before'); + assert(ERC721Impl::balance_of(state, owner) == 1, 'Balance of owner before'); + assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_transfer( + state: @ERC721::ContractState, + owner: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); + assert(ERC721Impl::balance_of(state, owner) == 0, 'Balance of owner after'); + assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); + assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval not implicitly reset'); +} + +fn assert_state_before_mint(state: @ERC721::ContractState, recipient: ContractAddress) { + assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_mint( + state: @ERC721::ContractState, recipient: ContractAddress, token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); + assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); + assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval implicitly set'); +} + +fn assert_event_approval_for_all( + owner: ContractAddress, operator: ContractAddress, approved: bool +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.operator == operator, 'Invalid `operator`'); + assert(event.approved == approved, 'Invalid `approved`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_approval(owner: ContractAddress, approved: ContractAddress, token_id: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.approved == approved, 'Invalid `approved`'); + assert(event.token_id == token_id, 'Invalid `token_id`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer(from: ContractAddress, to: ContractAddress, token_id: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.token_id == token_id, 'Invalid `token_id`'); + utils::assert_no_events_left(ZERO()); +} diff --git a/presets/token/src/tests/test_erc1155.cairo b/presets/token/src/tests/test_erc1155.cairo new file mode 100644 index 00000000..5ce73b82 --- /dev/null +++ b/presets/token/src/tests/test_erc1155.cairo @@ -0,0 +1,629 @@ +use zeroable::Zeroable; +use traits::{Into, Default, IndexView}; +use option::OptionTrait; +use array::ArrayTrait; +use serde::Serde; +use starknet::ContractAddress; +use starknet::testing::set_contract_address; + +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::tests::test_utils::impersonate; +use dojo_erc::tests::test_erc1155_utils::{ + spawn_world, deploy_erc1155, deploy_default, deploy_testcase1, ZERO, USER1, USER2, DEPLOYER, + PROXY +}; + +use dojo_erc::erc165::interface::IERC165_ID; +use dojo_erc::erc1155::interface::{ + IERC1155A, IERC1155ADispatcher, IERC1155ADispatcherTrait, IERC1155_ID, IERC1155_METADATA_ID, + IERC1155_RECEIVER_ID +}; + +use dojo_erc::erc1155::erc1155::ERC1155::{Event, TransferSingle, TransferBatch, ApprovalForAll}; + + +#[test] +#[available_gas(30000000)] +fn test_deploy() { + let world = spawn_world(DEPLOYER()); + let erc1155_address = deploy_erc1155(world, DEPLOYER(), 'uri', 'seed-42'); + let erc1155 = IERC1155ADispatcher { contract_address: erc1155_address }; + assert(erc1155.owner() == DEPLOYER(), 'invalid owner'); +} + +#[test] +#[available_gas(30000000)] +fn test_deploy_default() { + let (world, erc1155) = deploy_default(); + assert(erc1155.owner() == DEPLOYER(), 'invalid owner'); +} + + +// +// supports_interface +// + +#[test] +#[available_gas(30000000)] +fn test_should_support_interfaces() { + let (world, erc1155) = deploy_default(); + + assert(erc1155.supports_interface(IERC165_ID) == true, 'should support erc165'); + assert(erc1155.supports_interface(IERC1155_ID) == true, 'should support erc1155'); + assert( + erc1155.supports_interface(IERC1155_METADATA_ID) == true, 'should support erc1155_metadata' + ); +} + +// +// uri +// + +#[test] +#[available_gas(30000000)] +fn test_uri() { + let (world, erc1155) = deploy_default(); + assert(erc1155.uri(64) == 'uri', 'invalid uri'); +} + + +// +// behaves like an ERC1155 +// + +// +// balance_of +// +#[test] +#[available_gas(30000000)] +#[should_panic(expected: ('ERC1155: invalid owner address', 'ENTRYPOINT_FAILED',))] +fn test_balance_of_zero_address() { + //reverts when queried about the zero address + + let (world, erc1155) = deploy_default(); + erc1155.balance_of(ZERO(), 0); // should panic +} + +#[test] +#[available_gas(30000000)] +fn test_balance_of_empty_balance() { + // when accounts don't own tokens + // returns zero for given addresses + let (world, erc1155) = deploy_default(); + assert(erc1155.balance_of(USER1(), 0) == 0, 'should be 0'); + assert(erc1155.balance_of(USER1(), 69) == 0, 'should be 0'); + assert(erc1155.balance_of(USER2(), 0) == 0, 'should be 0'); +} + +#[test] +#[available_gas(30000000)] +fn test_balance_with_tokens() { + // when accounts own some tokens + // returns the amount of tokens owned by the given addresses + let (world, erc1155) = deploy_default(); + + erc1155.mint(USER1(), 0, 1, array![]); + erc1155.mint(USER1(), 69, 42, array![]); + erc1155.mint(USER2(), 69, 5, array![]); + + assert(erc1155.balance_of(USER1(), 0) == 1, 'should be 1'); + assert(erc1155.balance_of(USER1(), 69) == 42, 'should be 42'); + assert(erc1155.balance_of(USER2(), 69) == 5, 'should be 5'); +} + +// +// balance_of_batch +// + +#[test] +#[available_gas(30000000)] +#[should_panic(expected: ('ERC1155: invalid length', 'ENTRYPOINT_FAILED',))] +fn test_balance_of_batch_with_invalid_input() { + // reverts when input arrays don't match up + let (world, erc1155) = deploy_default(); + erc1155.balance_of_batch(array![USER1(), USER2()], array![0]); + erc1155.balance_of_batch(array![USER1()], array![0, 1, 2]); +} + +#[test] +#[available_gas(30000000)] +#[should_panic(expected: ('ERC1155: invalid owner address', 'ENTRYPOINT_FAILED',))] +fn test_balance_of_batch_address_zero() { + // reverts when input arrays don't match up + let (world, erc1155) = deploy_default(); + erc1155.balance_of_batch(array![USER1(), ZERO()], array![0, 1]); +} + +#[test] +#[available_gas(30000000)] +fn test_balance_of_batch_empty_account() { + // when accounts don't own tokens + // returns zeros for each account + let (world, erc1155) = deploy_default(); + let balances = erc1155.balance_of_batch(array![USER1(), USER1(), USER1()], array![0, 1, 5]); + let bals = @balances; + assert(balances.len() == 3, 'should be 3'); + assert(bals[0] == @0_u256, 'should be 0'); + assert(bals[1] == @0_u256, 'should be 0'); + assert(bals[2] == @0_u256, 'should be 0'); +} + +#[test] +#[available_gas(30000000)] +fn test_balance_of_batch_with_tokens() { + // when accounts own some tokens + // returns amounts owned by each account in order passed + let (world, erc1155) = deploy_default(); + + erc1155.mint(USER1(), 0, 1, array![]); + erc1155.mint(USER1(), 69, 42, array![]); + erc1155.mint(USER2(), 69, 2, array![]); + + let balances = erc1155.balance_of_batch(array![USER1(), USER1(), USER2()], array![0, 69, 69]); + let bals = @balances; + assert(balances.len() == 3, 'should be 3'); + assert(bals[0] == @1_u256, 'should be 1'); + assert(bals[1] == @42_u256, 'should be 42'); + assert(bals[2] == @2_u256, 'should be 2'); +} + +#[test] +#[available_gas(30000000)] +fn test_balance_of_batch_with_tokens_2() { + // when accounts own some tokens + // returns multiple times the balance of the same address when asked + let (world, erc1155) = deploy_default(); + + erc1155.mint(USER1(), 0, 1, array![]); + erc1155.mint(USER2(), 69, 2, array![]); + + let balances = erc1155.balance_of_batch(array![USER1(), USER2(), USER1()], array![0, 69, 0]); + let bals = @balances; + assert(balances.len() == 3, 'should be 3'); + assert(bals[0] == @1_u256, 'should be 1'); + assert(bals[1] == @2_u256, 'should be 2'); + assert(bals[2] == @1_u256, 'should be 1'); +} + + +// +// balance_of_batch +// + +#[test] +#[available_gas(30000000)] +fn test_set_approval_for_all() { + // sets approval status which can be queried via is_approved_for_all + let (world, erc1155) = deploy_default(); + impersonate(USER1()); + + erc1155.set_approval_for_all(PROXY(), true); + assert(erc1155.is_approved_for_all(USER1(), PROXY()) == true, 'should be true'); +} + +#[test] +#[available_gas(30000000)] +fn test_set_approval_for_all_emit_event() { + // set_approval_for_all emits ApprovalForAll event + let (world, erc1155) = deploy_default(); + impersonate(USER1()); + + erc1155.set_approval_for_all(PROXY(), true); + + // ApprovalForAll + assert( + @starknet::testing::pop_log(erc1155.contract_address) + .unwrap() == @Event::ApprovalForAll( + ApprovalForAll { owner: USER1(), operator: PROXY(), approved: true } + ), + 'invalid ApprovalForAll event' + ); +} + + +#[test] +#[available_gas(30000000)] +fn test_set_unset_approval_for_all() { + // sets approval status which can be queried via is_approved_for_all + let (world, erc1155) = deploy_default(); + impersonate(USER1()); + + erc1155.set_approval_for_all(PROXY(), true); + assert(erc1155.is_approved_for_all(USER1(), PROXY()) == true, 'should be true'); + erc1155.set_approval_for_all(PROXY(), false); + assert(erc1155.is_approved_for_all(USER1(), PROXY()) == false, 'should be false'); +} + +#[test] +#[available_gas(30000000)] +#[should_panic()] +fn test_set_approval_for_all_on_self() { + // reverts if attempting to approve self as an operator + let (world, erc1155) = deploy_default(); + impersonate(USER1()); + + erc1155.set_approval_for_all(USER1(), true); // should panic +} + +// +// safe_transfer_from +// + +#[test] +#[available_gas(30000000)] +#[should_panic()] +fn test_safe_transfer_from_more_than_balance() { + // reverts when transferring more than balance + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + erc1155.safe_transfer_from(USER1(), USER2(), 1, 999, array![]); // should panic +} + +#[test] +#[available_gas(30000000)] +#[should_panic()] +fn test_safe_transfer_to_zero() { + // reverts when transferring to zero address + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + erc1155.safe_transfer_from(USER1(), ZERO(), 1, 1, array![]); // should panic +} + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_debit_sender() { + // debits transferred balance from sender + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + let balance_before = erc1155.balance_of(USER1(), 1); + erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); + let balance_after = erc1155.balance_of(USER1(), 1); + + assert(balance_after == balance_before - 1, 'invalid balance after'); +} + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_credit_receiver() { + // credits transferred balance to receiver + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + let balance_before = erc1155.balance_of(USER2(), 1); + erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); + let balance_after = erc1155.balance_of(USER2(), 1); + + assert(balance_after == balance_before + 1, 'invalid balance after'); +} + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_preserve_existing_balances() { + // preserves existing balances which are not transferred by multiTokenHolder + let (world, erc1155) = deploy_testcase1(); + + // impersonate user1 + impersonate(USER1()); + + let balance_before_2 = erc1155.balance_of(USER2(), 2); + let balance_before_3 = erc1155.balance_of(USER2(), 3); + erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); + let balance_after_2 = erc1155.balance_of(USER2(), 2); + let balance_after_3 = erc1155.balance_of(USER2(), 3); + + assert(balance_after_2 == balance_before_2, 'should be equal'); + assert(balance_after_3 == balance_before_3, 'should be equal'); +} + +#[test] +#[available_gas(30000000)] +#[should_panic()] +fn test_safe_transfer_from_unapproved_operator() { + // when called by an operator on behalf of the multiTokenHolder + // when operator is not approved by multiTokenHolder + + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER2()); + + erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); // should panic +} + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_from_approved_operator() { + // when called by an operator on behalf of the multiTokenHolder + // when operator is approved by multiTokenHolder + let (world, erc1155) = deploy_testcase1(); + + impersonate(PROXY()); + + let balance_before = erc1155.balance_of(USER1(), 1); + erc1155.safe_transfer_from(USER1(), USER2(), 1, 2, array![]); + let balance_after = erc1155.balance_of(USER1(), 1); + + assert(balance_after == balance_before - 2, 'invalid balance'); +} + +#[test] +#[available_gas(50000000)] +fn test_safe_transfer_from_approved_operator_preserve_operator_balance() { + // when called by an operator on behalf of the multiTokenHolder + // preserves operator's balances not involved in the transfer + let (world, erc1155) = deploy_testcase1(); + + impersonate(PROXY()); + + let balance_before_1 = erc1155.balance_of(PROXY(), 1); + let balance_before_2 = erc1155.balance_of(PROXY(), 2); + let balance_before_3 = erc1155.balance_of(PROXY(), 3); + erc1155.safe_transfer_from(USER1(), USER2(), 1, 2, array![]); + let balance_after_1 = erc1155.balance_of(PROXY(), 1); + let balance_after_2 = erc1155.balance_of(PROXY(), 2); + let balance_after_3 = erc1155.balance_of(PROXY(), 3); + + assert(balance_before_1 == balance_after_1, 'should be equal'); + assert(balance_before_2 == balance_after_2, 'should be equal'); + assert(balance_before_3 == balance_after_3, 'should be equal'); +} + + +#[test] +#[available_gas(50000000)] +#[should_panic] +fn test_safe_transfer_from_zero_address() { + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + erc1155.safe_transfer_from(ZERO(), USER1(), 1, 1, array![]); +} + +// +// safe_batch_transfer_from +// + +#[test] +#[available_gas(50000000)] +#[should_panic] +fn test_safe_batch_transfer_from_more_than_balance() { + // reverts when transferring amount more than any of balances + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + erc1155 + .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 999, 1], array![]); +} + + +#[test] +#[available_gas(50000000)] +#[should_panic] +fn test_safe_batch_transfer_from_mismatching_array_len() { + // reverts when ids array length doesn't match amounts array length + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 1], array![]); +} + + +#[test] +#[available_gas(50000000)] +#[should_panic] +fn test_safe_batch_transfer_from_to_zero_address() { + // reverts when transferring to zero address + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + erc1155.safe_batch_transfer_from(USER1(), ZERO(), array![1, 2], array![1, 1], array![]); +} + + +#[test] +#[available_gas(60000000)] +fn test_safe_batch_transfer_from_debits_sender() { + // debits transferred balances from sender + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + let balance_before_1 = erc1155.balance_of(USER1(), 1); + let balance_before_2 = erc1155.balance_of(USER1(), 2); + let balance_before_3 = erc1155.balance_of(USER1(), 3); + erc1155 + .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); + let balance_after_1 = erc1155.balance_of(USER1(), 1); + let balance_after_2 = erc1155.balance_of(USER1(), 2); + let balance_after_3 = erc1155.balance_of(USER1(), 3); + + assert(balance_before_1 - 1 == balance_after_1, 'invalid balance'); + assert(balance_before_2 - 10 == balance_after_2, 'invalid balance'); + assert(balance_before_3 - 20 == balance_after_3, 'invalid balance'); +} + + +#[test] +#[available_gas(60000000)] +fn test_safe_batch_transfer_from_credits_recipient() { + // credits transferred balances to receiver + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + let balance_before_1 = erc1155.balance_of(USER2(), 1); + let balance_before_2 = erc1155.balance_of(USER2(), 2); + let balance_before_3 = erc1155.balance_of(USER2(), 3); + erc1155 + .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); + let balance_after_1 = erc1155.balance_of(USER2(), 1); + let balance_after_2 = erc1155.balance_of(USER2(), 2); + let balance_after_3 = erc1155.balance_of(USER2(), 3); + + assert(balance_before_1 + 1 == balance_after_1, 'invalid balance'); + assert(balance_before_2 + 10 == balance_after_2, 'invalid balance'); + assert(balance_before_1 + 20 == balance_after_3, 'invalid balance'); +} + + +#[test] +#[available_gas(50000000)] +#[should_panic] +fn test_safe_batch_transfer_from_unapproved_operator() { + // when called by an operator on behalf of the multiTokenHolder + // when operator is not approved by multiTokenHolder + + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER2()); + + erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2], array![1, 10], array![]); +} + +#[test] +#[available_gas(60000000)] +fn test_safe_batch_transfer_from_approved_operator_preserve_operator_balance() { + // when called by an operator on behalf of the multiTokenHolder + // preserves operator's balances not involved in the transfer + + let (world, erc1155) = deploy_testcase1(); + + impersonate(PROXY()); + + let balance_before_1 = erc1155.balance_of(PROXY(), 1); + let balance_before_2 = erc1155.balance_of(PROXY(), 2); + let balance_before_3 = erc1155.balance_of(PROXY(), 3); + + erc1155 + .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); + + let balance_after_1 = erc1155.balance_of(PROXY(), 1); + let balance_after_2 = erc1155.balance_of(PROXY(), 2); + let balance_after_3 = erc1155.balance_of(PROXY(), 3); + + assert(balance_before_1 == balance_after_1, 'should be equal'); + assert(balance_before_2 == balance_after_2, 'should be equal'); + assert(balance_before_3 == balance_after_3, 'should be equal'); +} + +#[test] +#[available_gas(50000000)] +#[should_panic] +fn test_safe_batch_transfer_from_zero_address() { + let (world, erc1155) = deploy_testcase1(); + + impersonate(USER1()); + + erc1155.safe_batch_transfer_from(ZERO(), USER1(), array![1, 2], array![1, 1], array![]); +} + + +#[test] +#[available_gas(50000000)] +fn test_safe_batch_transfer_emit_transfer_batch_event() { + let (world, erc1155) = deploy_default(); + + // user1 token_id 1 x 10 + erc1155.mint(USER1(), 1, 10, array![]); + // user1 token_id 2 x 20 + erc1155.mint(USER1(), 2, 20, array![]); + + impersonate(USER1()); + + erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2], array![1, 10], array![]); + + let _: Event = starknet::testing::pop_log(erc1155.contract_address) + .unwrap(); // unpop erc1155.mint(USER1(), 1, 10, array![]); + let _: Event = starknet::testing::pop_log(erc1155.contract_address) + .unwrap(); // unpop erc1155.mint(USER1(), 2, 20, array![]); + + // TransferBatch + assert( + @starknet::testing::pop_log(erc1155.contract_address) + .unwrap() == @Event::TransferBatch( + TransferBatch { + operator: USER1(), + from: USER1(), + to: USER2(), + ids: array![1, 2], + values: array![1, 10] + } + ), + 'invalid TransferBatch event' + ); +} + + +// +// burn +// + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_burn_non_existing_token_id() { + //reverts when burning a non-existent token id + let (world, erc1155) = deploy_default(); + + impersonate(USER1()); + erc1155.burn(USER1(), 69, 1); // should panic +} + + +#[test] +#[available_gas(90000000)] +fn test_burn_emit_transfer_single_event() { + // burn should emit event + let (world, erc1155) = deploy_default(); + + erc1155.mint(USER1(), 69, 5, array![]); + assert(erc1155.balance_of(USER1(), 69) == 5, 'invalid balance'); + + impersonate(USER1()); + + erc1155.burn(USER1(), 69, 1); + assert(erc1155.balance_of(USER1(), 69) == 4, 'invalid balance'); + + let _: Event = starknet::testing::pop_log(erc1155.contract_address) + .unwrap(); // unpop erc1155.mint(USER1(), 69,5,array![]) + + // TransferSingle + assert( + @starknet::testing::pop_log(erc1155.contract_address) + .unwrap() == @Event::TransferSingle( + TransferSingle { operator: USER1(), from: USER1(), to: ZERO(), id: 69, value: 1 } + ), + 'invalid TransferSingle event' + ); +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_burn_more_than_owned() { + // reverts when burning more tokens than owned + let (world, erc1155) = deploy_default(); + erc1155.mint(USER1(), 69, 10, array![]); + + impersonate(USER1()); + + erc1155.burn(USER1(), 69, 1); + erc1155.burn(USER1(), 69, 10); // should panic +} +// TODO : to be continued + +// TODO : add test if we support IERC1155Receiver + + diff --git a/presets/token/src/tests/test_erc721.cairo b/presets/token/src/tests/test_erc721.cairo new file mode 100644 index 00000000..fc23abc4 --- /dev/null +++ b/presets/token/src/tests/test_erc721.cairo @@ -0,0 +1,862 @@ +use core::zeroable::Zeroable; +use core::traits::{Into, Default}; +use array::ArrayTrait; +use serde::Serde; +use starknet::ContractAddress; +use starknet::testing::set_contract_address; +use option::OptionTrait; + +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::tests::test_utils::impersonate; +use dojo_erc::tests::test_erc721_utils::{ + spawn_world, deploy_erc721, deploy_default, deploy_testcase1, USER1, USER2, USER3, DEPLOYER, + ZERO, PROXY +}; + + +use dojo_erc::erc165::interface::IERC165_ID; +use dojo_erc::erc721::interface::{ + IERC721, IERC721ADispatcher, IERC721ADispatcherTrait, IERC721_ID, IERC721_METADATA_ID +}; +use dojo_erc::erc721::erc721::ERC721::{Event, Transfer, Approval, ApprovalForAll}; +// actually it's possible to mint -> burn -> mint -> ... +// todo : add Minted component to keep track of minted ids + +#[test] +#[available_gas(30000000)] +fn test_deploy() { + let world = spawn_world(DEPLOYER()); + let erc721_address = deploy_erc721(world, DEPLOYER(), 'name', 'symbol', 'uri', 'seed-42'); + let erc721 = IERC721ADispatcher { contract_address: erc721_address }; + + assert(erc721.owner() == DEPLOYER(), 'invalid owner'); + assert(erc721.name() == 'name', 'invalid name'); + assert(erc721.symbol() == 'symbol', 'invalid symbol'); +} + + +#[test] +#[available_gas(30000000)] +fn test_deploy_default() { + let (world, erc721) = deploy_default(); + assert(erc721.name() == 'name', 'invalid name'); +} + +// +// supports_interface +// + +#[test] +#[available_gas(30000000)] +fn test_should_support_interfaces() { + let (world, erc721) = deploy_default(); + + assert(erc721.supports_interface(IERC165_ID) == true, 'should support erc165'); + assert(erc721.supports_interface(IERC721_ID) == true, 'should support erc721'); + assert( + erc721.supports_interface(IERC721_METADATA_ID) == true, 'should support erc721_metadata' + ); +} + + +// +// behaves like an ERC721 +// + +// +// balance_of +// + +use debug::PrintTrait; + +#[test] +#[available_gas(60000000)] +fn test_balance_of_with_tokens() { + // returns the amount of tokens owned by the given address + + let (world, erc721) = deploy_testcase1(); + assert(erc721.balance_of(USER1()) == 3, 'should be 3'); + assert(erc721.balance_of(PROXY()) == 4, 'should be 4'); +} + +#[test] +#[available_gas(60000000)] +fn test_balance_of_with_no_tokens() { + // when the given address does not own any tokens + + let (world, erc721) = deploy_testcase1(); + assert(erc721.balance_of(USER3()) == 0, 'should be 0'); +} + + +#[test] +#[available_gas(50000000)] +#[should_panic] +fn test_balance_of_zero_address() { + // when querying the zero address + + let (world, erc721) = deploy_testcase1(); + erc721.balance_of(ZERO()); +} + +// +// owner_of +// + +#[test] +#[available_gas(90000000)] +fn test_owner_of_existing_id() { + // when the given token ID was tracked by this token = for existing id + + let (world, erc721) = deploy_testcase1(); + assert(erc721.owner_of(1) == USER1(), 'should be user1'); + assert(erc721.owner_of(2) == USER1(), 'should be user1'); + assert(erc721.owner_of(3) == USER1(), 'should be user1'); + + assert(erc721.owner_of(10) == PROXY(), 'should be proxy'); + assert(erc721.owner_of(11) == PROXY(), 'should be proxy'); + assert(erc721.owner_of(12) == PROXY(), 'should be proxy'); + assert(erc721.owner_of(13) == PROXY(), 'should be proxy'); +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_owner_of_non_existing_id() { + // when the given token ID was not tracked by this token = non existing id + + let (world, erc721) = deploy_testcase1(); + let owner_of_0 = erc721.owner_of(0); // should panic +} + +// +// transfers +// + +#[test] +#[available_gas(90000000)] +fn test_transfer_ownership() { + // transfers the ownership of the given token ID to the given address + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + let owner_of_1 = erc721.owner_of(1); + // transfer token_id 1 to user2 + erc721.transfer(USER2(), 1); + assert(erc721.owner_of(1) == USER2(), 'invalid owner'); +} + +#[test] +#[available_gas(90000000)] +fn test_transfer_event() { + // emits a Transfer event + + let (world, erc721) = deploy_default(); + + // mint + erc721.mint(USER1(), 42); + + impersonate(USER1()); + + // transfer token_id 1 to user2 + erc721.transfer(USER2(), 42); + + impersonate(USER2()); + erc721.burn(42); + + // mint + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Transfer(Transfer { from: ZERO(), to: USER1(), token_id: 42 }), + 'invalid Transfer event' + ); + // transfer + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Transfer(Transfer { from: USER1(), to: USER2(), token_id: 42 }), + 'invalid Transfer event' + ); + // burn + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Transfer(Transfer { from: USER2(), to: ZERO(), token_id: 42 }), + 'invalid Transfer event' + ); +} + + +#[test] +#[available_gas(90000000)] +fn test_transfer_clear_approval() { + // clears the approval for the token ID + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + erc721.approve(PROXY(), 1); + assert(erc721.get_approved(1) == PROXY(), 'should be proxy'); + + // transfer token_id 1 to user2 + erc721.transfer(USER2(), 1); + assert(erc721.get_approved(1).is_zero(), 'should be zero'); +} + +#[test] +#[available_gas(90000000)] +fn test_transfer_adjusts_owners_balances() { + // adjusts owners balances + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + let balance_user1_before = erc721.balance_of(USER1()); + let balance_user2_before = erc721.balance_of(USER2()); + + // transfer token_id 1 to user2 + erc721.transfer(USER2(), 1); + + let balance_user1_after = erc721.balance_of(USER1()); + let balance_user2_after = erc721.balance_of(USER2()); + + assert(balance_user1_after == balance_user1_before - 1, 'invalid user1 balance'); + assert(balance_user2_after == balance_user2_before + 1, 'invalid user2 balance'); +} + + +#[test] +#[available_gas(90000000)] +fn test_transfer_from_approved() { + // when called by the approved individual + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + //user1 approve user2 for token_id 2 + erc721.approve(USER2(), 2); + + impersonate(USER2()); + + erc721.transfer_from(USER1(), USER2(), 2); + assert(erc721.owner_of(2) == USER2(), 'invalid owner'); +} + +#[test] +#[available_gas(90000000)] +fn test_transfer_from_approved_operator() { + // when called by the operator + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + //user1 set_approval_for_all for proxy + erc721.set_approval_for_all(PROXY(), true); + + impersonate(PROXY()); + + erc721.transfer_from(USER1(), USER2(), 2); + assert(erc721.owner_of(2) == USER2(), 'invalid owner'); +} + +#[test] +#[available_gas(90000000)] +fn test_transfer_from_owner_without_approved() { + // when called by the owner without an approved user + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + erc721.approve(ZERO(), 2); + + erc721.transfer_from(USER1(), USER2(), 2); + assert(erc721.owner_of(2) == USER2(), 'invalid owner'); +} + + +#[test] +#[available_gas(90000000)] +fn test_transfer_to_owner() { + // when sent to the owner + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + let balance_before = erc721.balance_of(USER1()); + + assert(erc721.owner_of(3) == USER1(), 'invalid owner'); + erc721.transfer(USER1(), 3); + + // keeps ownership of the token + assert(erc721.owner_of(3) == USER1(), 'invalid owner'); + + // clears the approval for the token ID + assert(erc721.get_approved(3) == ZERO(), 'invalid approved'); + + //emits only a transfer event : cumbersome to test with pop_log + + //keeps the owner balance + let balance_after = erc721.balance_of(USER1()); + assert(balance_before == balance_after, 'invalid balance') +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_transfer_when_previous_owner_is_incorrect() { + // when the address of the previous owner is incorrect + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + //user2 owner token_id 10 + erc721.transfer_from(USER1(), PROXY(), 10); // should panic +} + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_transfer_when_sender_not_authorized() { + // when the sender is not authorized for the token id + let (world, erc721) = deploy_testcase1(); + + impersonate(PROXY()); + + //proxy is not authorized for USER2 + erc721.transfer_from(USER2(), PROXY(), 20); // should panic +} + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_transfer_when_token_id_doesnt_exists() { + // when the sender is not authorized for the token id + let (world, erc721) = deploy_testcase1(); + + impersonate(PROXY()); + + //proxy is authorized for USER1 but token_id 50 doesnt exists + erc721.transfer_from(USER1(), PROXY(), 50); // should panic +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_transfer_to_address_zero() { + // when the address to transfer the token to is the zero address + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + erc721.transfer(ZERO(), 1); // should panic +} + +// +// approval +// + +// when clearing approval + +#[test] +#[available_gas(90000000)] +fn test_approval_when_clearing_with_prior_approval() { + // -when there was a prior approval + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 42); + + impersonate(USER1()); + + erc721.approve(PROXY(), 42); + + //revoke approve + erc721.approve(ZERO(), 42); + + // clears approval for the token + assert(erc721.get_approved(42) == ZERO(), 'invalid approved'); + + // emits an approval event + let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop approve PROXY + + // approve ZERO + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Approval(Approval { owner: USER1(), to: ZERO(), token_id: 42 }), + 'invalid Approval event' + ); +} + +#[test] +#[available_gas(90000000)] +fn test_approval_when_clearing_without_prior_approval() { + // when clearing approval + // -when there was no prior approval + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 42); + + impersonate(USER1()); + + //revoke approve + erc721.approve(ZERO(), 42); + + // updates approval for the token + assert(erc721.get_approved(42) == ZERO(), 'invalid approved'); + + let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint + + // approve ZERO + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Approval(Approval { owner: USER1(), to: ZERO(), token_id: 42 }), + 'invalid Approval event' + ); +} + + +// when approving a non-zero address + +#[test] +#[available_gas(90000000)] +fn test_approval_non_zero_address_with_prior_approval() { + // -when there was a prior approval + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 42); + + impersonate(USER1()); + erc721.approve(PROXY(), 42); + + // user1 approves user3 + erc721.approve(USER3(), 42); + + // set approval for the token + assert(erc721.get_approved(42) == USER3(), 'invalid approved'); + + // emits an approval event + let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop approve PROXY + + // approve USER3 + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER3(), token_id: 42 }), + 'invalid Approval event' + ); +} + +#[test] +#[available_gas(90000000)] +fn test_approval_non_zero_address_with_no_prior_approval() { + // -when there was no prior approval + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 42); + + impersonate(USER1()); + + // user1 approves user3 + erc721.approve(USER3(), 42); + + // set approval for the token + assert(erc721.get_approved(42) == USER3(), 'invalid approved'); + + // emits an approval event + let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint + + // approve USER3 + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER3(), token_id: 42 }), + 'invalid Approval event' + ); +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_approval_self_approve() { + // when the address that receives the approval is the owner + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 42); + + impersonate(USER1()); + + // user1 approves user1 + erc721.approve(USER1(), 42); // should panic +} + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_approval_not_owned() { + // when the sender does not own the given token ID + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + // user1 approves user2 for token 20 + erc721.approve(USER2(), 20); // should panic +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_approval_from_approved_sender() { + // when the sender is approved for the given token ID + + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + // user1 approve user3 + erc721.approve(USER3(), 1); + + impersonate(USER3()); + + // (ERC721: approve caller is not token owner or approved for all) + erc721.approve(USER2(), 1); // should panic +} + + +#[test] +#[available_gas(90000000)] +fn test_approval_from_approved_operator() { + // when the sender is an operator + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 50); + + impersonate(USER1()); + + erc721.set_approval_for_all(PROXY(), true); + + impersonate(PROXY()); + + // proxy approves user2 for token 20 + erc721.approve(USER2(), 50); + + assert(erc721.get_approved(50) == USER2(), 'invalid approval'); + + let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop set_approval_for_all + + // approve + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER2(), token_id: 50 }), + 'invalid Approval event' + ); +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_approval_unexisting_id() { + // when the given token ID does not exist + let (world, erc721) = deploy_testcase1(); + + impersonate(USER1()); + + // user1 approve user3 + erc721.approve(USER3(), 69); // should panic +} + +// +// approval_for_all +// + +#[test] +#[available_gas(90000000)] +fn test_approval_for_all_operator_is_not_owner_no_operator_approval() { + // when the operator willing to approve is not the owner + // -when there is no operator approval set by the sender + let (world, erc721) = deploy_default(); + + impersonate(USER2()); + + // user2 set_approval_for_all PROXY + erc721.set_approval_for_all(PROXY(), true); + + assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); + + // ApproveForAll + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::ApprovalForAll( + ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } + ), + 'invalid ApprovalForAll event' + ); +} + +#[test] +#[available_gas(90000000)] +fn test_approval_for_all_operator_is_not_owner_from_not_approved() { + // when the operator willing to approve is not the owner + // -when the operator was set as not approved + let (world, erc721) = deploy_default(); + + impersonate(USER2()); + + erc721.set_approval_for_all(PROXY(), false); + + // user2 set_approval_for_all PROXY + erc721.set_approval_for_all(PROXY(), true); + + assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); + + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop set_approval_for_all(PROXY(), false) + + // ApproveForAll + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::ApprovalForAll( + ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } + ), + 'invalid ApprovalForAll event' + ); +} + +#[test] +#[available_gas(90000000)] +fn test_approval_for_all_operator_is_not_owner_can_unset_approval_for_all() { + // when the operator willing to approve is not the owner + // can unset the operator approval + let (world, erc721) = deploy_default(); + + impersonate(USER2()); + + erc721.set_approval_for_all(PROXY(), false); + erc721.set_approval_for_all(PROXY(), true); + assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); + erc721.set_approval_for_all(PROXY(), false); + assert(erc721.is_approved_for_all(USER2(), PROXY()) == false, 'invalid is_approved_for_all'); + + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop set_approval_for_all(PROXY(), false) + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop set_approval_for_all(PROXY(), true) + + // ApproveForAll + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::ApprovalForAll( + ApprovalForAll { owner: USER2(), operator: PROXY(), approved: false } + ), + 'invalid ApprovalForAll event' + ); +} + +#[test] +#[available_gas(90000000)] +fn test_approval_for_all_operator_with_operator_already_approved() { + // when the operator willing to approve is not the owner + // when the operator was already approved + let (world, erc721) = deploy_default(); + + impersonate(USER2()); + + erc721.set_approval_for_all(PROXY(), true); + assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); + erc721.set_approval_for_all(PROXY(), true); + assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); + + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop set_approval_for_all(PROXY(), true) + + // ApproveForAll + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::ApprovalForAll( + ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } + ), + 'invalid ApprovalForAll event' + ); +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_approval_for_all_with_owner_as_operator() { + // when the operator is the owner + + let (world, erc721) = deploy_default(); + + impersonate(USER1()); + + erc721.set_approval_for_all(USER1(), true); // should panic +} + + +// +// get_approved +// + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_get_approved_unexisting_token() { + let (world, erc721) = deploy_default(); + + erc721.get_approved(420); // should panic +} + + +#[test] +#[available_gas(90000000)] +fn test_get_approved_with_existing_token() { + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 420); + assert(erc721.get_approved(420) == ZERO(), 'invalid get_approved'); +} + + +#[test] +#[available_gas(90000000)] +fn test_get_approved_with_existing_token_and_approval() { + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 420); + + impersonate(USER1()); + + erc721.approve(PROXY(), 420); + assert(erc721.get_approved(420) == PROXY(), 'invalid get_approved'); +} + +// +// mint +// + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_mint_to_address_zero() { + // reverts with a null destination address + + let (world, erc721) = deploy_default(); + + erc721.mint(ZERO(), 69); // should panic +} + + +#[test] +#[available_gas(90000000)] +fn test_mint() { + // reverts with a null destination address + + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 69); + + assert(erc721.balance_of(USER1()) == 1, 'invalid balance'); + + // Transfer + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Transfer(Transfer { from: ZERO(), to: USER1(), token_id: 69 }), + 'invalid Transfer event' + ); +} + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_mint_existing_token_id() { + // reverts with a null destination address + + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 69); + erc721.mint(USER1(), 69); //should panic +} + + +// +// burn +// + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_burn_non_existing_token_id() { + //reverts when burning a non-existent token id + let (world, erc721) = deploy_default(); + erc721.burn(69); // should panic +} + + +#[test] +#[available_gas(90000000)] +fn test_burn_emit_events() { + // burn should emit event + let (world, erc721) = deploy_default(); + + erc721.mint(USER1(), 69); + assert(erc721.balance_of(USER1()) == 1, 'invalid balance'); + + impersonate(USER1()); + + erc721.burn(69); + assert(erc721.balance_of(USER1()) == 0, 'invalid balance'); + + let _: Event = starknet::testing::pop_log(erc721.contract_address) + .unwrap(); // unpop erc721.mint(USER1(), 69) + + // Transfer + assert( + @starknet::testing::pop_log(erc721.contract_address) + .unwrap() == @Event::Transfer(Transfer { from: USER1(), to: ZERO(), token_id: 69 }), + 'invalid Transfer event' + ); +} + + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_burn_same_id_twice() { + // reverts when burning a token id that has been deleted + let (world, erc721) = deploy_default(); + erc721.mint(USER1(), 69); + erc721.burn(69); + erc721.burn(69); // should panic +} + +// +// token_uri +// + +#[test] +#[available_gas(90000000)] +#[should_panic] +fn test_token_uri_for_non_existing_token_id() { + // reverts when queried for non existent token id + let (world, erc721) = deploy_default(); + erc721.token_uri(1234); // should panic +} + diff --git a/presets/token/src/tests/utils.cairo b/presets/token/src/tests/utils.cairo new file mode 100644 index 00000000..99e95669 --- /dev/null +++ b/presets/token/src/tests/utils.cairo @@ -0,0 +1,32 @@ +use core::result::ResultTrait; +use starknet::class_hash::Felt252TryIntoClassHash; +use starknet::ContractAddress; +use starknet::testing; + +fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAddress { + let (address, _) = starknet::deploy_syscall( + contract_class_hash.try_into().unwrap(), 0, calldata.span(), false + ) + .unwrap(); + address +} + +/// Pop the earliest unpopped logged event for the contract as the requested type +/// and checks there's no more data left on the event, preventing unaccounted params. +/// Indexed event members are currently not supported, so they are ignored. +fn pop_log, impl TEvent: starknet::Event>( + address: ContractAddress +) -> Option { + let (mut keys, mut data) = testing::pop_log_raw(address)?; + let ret = starknet::Event::deserialize(ref keys, ref data); + assert(data.is_empty(), 'Event has extra data'); + ret +} + +fn assert_no_events_left(address: ContractAddress) { + assert(testing::pop_log_raw(address).is_none(), 'Events remaining on queue'); +} + +fn drop_event(address: ContractAddress) { + testing::pop_log_raw(address); +} diff --git a/presets/token/src/token/erc1155.cairo b/presets/token/src/token/erc1155.cairo new file mode 100644 index 00000000..af50183e --- /dev/null +++ b/presets/token/src/token/erc1155.cairo @@ -0,0 +1,5 @@ +mod erc1155; +mod models; +mod interface; + +use erc1155::ERC1155; \ No newline at end of file diff --git a/presets/token/src/token/erc1155/erc1155.cairo b/presets/token/src/token/erc1155/erc1155.cairo new file mode 100644 index 00000000..26ba7f23 --- /dev/null +++ b/presets/token/src/token/erc1155/erc1155.cairo @@ -0,0 +1,439 @@ +#[starknet::contract] +mod ERC1155 { + use dojo_erc::token::erc1155::models::{ + ERC1155Meta, ERC1155OperatorApproval, ERC1155Balance + }; + use dojo_erc::token::erc1155::interface; + use dojo_erc::token::erc1155::interface::{IERC1155, IERC1155CamelOnly}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use array::ArrayTCloneImpl; + use zeroable::Zeroable; + use debug::PrintTrait; + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Clone, Drop, starknet::Event)] + enum Event { + TransferSingle: TransferSingle, + TransferBatch: TransferBatch, + ApprovalForAll: ApprovalForAll + } + + #[derive(Clone, Drop, starknet::Event)] + struct TransferSingle { + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + id: u256, + value: u256 + } + + #[derive(Clone, Drop, starknet::Event)] + struct TransferBatch { + operator: ContractAddress, + from: ContractAddress, + to: ContractAddress, + ids: Array, + values: Array + } + + #[derive(Clone, Drop, starknet::Event)] + struct ApprovalForAll { + owner: ContractAddress, + operator: ContractAddress, + approved: bool + } + + mod Errors { + const INVALID_TOKEN_ID: felt252 = 'ERC1155: invalid token ID'; + const INVALID_ACCOUNT: felt252 = 'ERC1155: invalid account'; + const UNAUTHORIZED: felt252 = 'ERC1155: unauthorized caller'; + const SELF_APPROVAL: felt252 = 'ERC1155: self approval'; + const INVALID_RECEIVER: felt252 = 'ERC1155: invalid receiver'; + const WRONG_SENDER: felt252 = 'ERC1155: wrong sender'; + const SAFE_MINT_FAILED: felt252 = 'ERC1155: safe mint failed'; + const SAFE_TRANSFER_FAILED: felt252 = 'ERC1155: safe transfer failed'; + const INVALID_ARRAY_LENGTH: felt252 = 'ERC1155: invalid array length'; + const INSUFFICIENT_BALANCE: felt252 = 'ERC1155: insufficient balance'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, + ) { + self._world.write(world); + self.initializer(name, symbol, base_uri); + } + + // + // External + // + + // #[external(v0)] + // impl SRC5Impl of ISRC5 { + // fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) + // } + // } + + // #[external(v0)] + // impl SRC5CamelImpl of ISRC5Camel { + // fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId) + // } + // } + + #[external(v0)] + impl ERC1155MetadataImpl of interface::IERC1155Metadata { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn uri(self: @ContractState, token_id: u256) -> felt252 { + //assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + // TODO : concat with id + self.get_uri(token_id) + } + } + + + #[external(v0)] + impl ERC1155Impl of interface::IERC1155 { + fn balance_of(self: @ContractState, account: ContractAddress, id: u256) -> u256 { + assert(account.is_non_zero(), Errors::INVALID_ACCOUNT); + self.get_balance(account, id).amount + } + + fn balance_of_batch( + self: @ContractState, accounts: Array, ids: Array + ) -> Array { + assert(ids.len() == accounts.len(), Errors::INVALID_ARRAY_LENGTH); + + let mut batch_balances = array![]; + let mut index = 0; + loop { + if index == ids.len() { + break batch_balances.clone(); + } + batch_balances.append(self.balance_of(*accounts.at(index), *ids.at(index))); + index += 1; + } + } + + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { + self._set_approval_for_all(get_caller_address(), operator, approved) + } + + fn is_approved_for_all( + self: @ContractState, account: ContractAddress, operator: ContractAddress + ) -> bool { + self.get_operator_approval(account, operator).approved + } + + fn safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + assert(from.is_non_zero(), Errors::WRONG_SENDER); + assert( + self._is_approved_for_all_or_owner(from, get_caller_address()), Errors::UNAUTHORIZED + ); + + self._safe_transfer_from(from, to, id, amount, data); + } + + fn safe_batch_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + assert(from.is_non_zero(), Errors::WRONG_SENDER); + assert( + self._is_approved_for_all_or_owner(from, get_caller_address()), Errors::UNAUTHORIZED + ); + + self._safe_batch_transfer_from(from, to, ids, amounts, data); + } + } + + #[external(v0)] + impl ERC1155CamelOnlyImpl of interface::IERC1155CamelOnly { + fn balanceOf(self: @ContractState, account: ContractAddress, id: u256) -> u256 { + ERC1155Impl::balance_of(self, account, id) + } + + fn balanceOfBatch( + self: @ContractState, accounts: Array, ids: Array + ) -> Array { + ERC1155Impl::balance_of_batch(self, accounts, ids) + } + + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { + ERC1155Impl::set_approval_for_all(ref self, operator, approved); + } + fn isApprovedForAll( + self: @ContractState, account: ContractAddress, operator: ContractAddress + ) -> bool { + ERC1155Impl::is_approved_for_all(self, account, operator) + } + fn safeTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ) { + ERC1155Impl::safe_transfer_from(ref self, from, to, id, amount, data); + } + fn safeBatchTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ) { + ERC1155Impl::safe_batch_transfer_from(ref self, from, to, ids, amounts, data); + } + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC1155Meta { + get!(self.world(), get_contract_address(), ERC1155Meta) + } + + fn get_uri(self: @ContractState, token_id: u256) -> felt252 { + // TODO : concat with id when we have string type + self.get_meta().base_uri + } + + fn get_balance(self: @ContractState, account: ContractAddress, id: u256) -> ERC1155Balance { + get!(self.world(), (get_contract_address(), account, id), ERC1155Balance) + } + + fn get_operator_approval( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> ERC1155OperatorApproval { + get!(self.world(), (get_contract_address(), owner, operator), ERC1155OperatorApproval) + } + + fn set_operator_approval( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + set!( + self.world(), + ERC1155OperatorApproval { token: get_contract_address(), owner, operator, approved } + ); + self.emit_event(ApprovalForAll { owner, operator, approved }); + } + + fn set_balance(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) { + set!( + self.world(), ERC1155Balance { token: get_contract_address(), account, id, amount } + ); + } + + fn update_balances( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + ) { + self.set_balance(from, id, self.get_balance(from, id).amount - amount); + self.set_balance(to, id, self.get_balance(to, id).amount + amount); + } + + fn emit_event< + S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SClone: Clone + >( + ref self: ContractState, event: S + ) { + self.emit(event.clone()); + emit!(self.world(), event); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { + let meta = ERC1155Meta { token: get_contract_address(), name, symbol, base_uri }; + set!(self.world(), (meta)); + // let mut unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::InternalImpl::register_interface(ref unsafe_state, interface::IERC721_ID); + // src5::SRC5::InternalImpl::register_interface( + // ref unsafe_state, interface::IERC721_METADATA_ID + // ); + } + + fn _is_approved_for_all_or_owner( + self: @ContractState, from: ContractAddress, caller: ContractAddress + ) -> bool { + caller == from || self.is_approved_for_all(from, caller) + } + + fn _set_approval_for_all( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + assert(owner != operator, Errors::SELF_APPROVAL); + self.set_operator_approval(owner, operator, approved); + } + + fn _safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ) { + self.update_balances(from, to, id, amount); + // assert( + // _check_on_erc1155_received(from, to, id, data), Errors::SAFE_TRANSFER_FAILED + // ); + + self + .emit_event( + TransferSingle { operator: get_caller_address(), from, to, id, value: amount } + ); + } + + fn _safe_batch_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ) { + assert(ids.len() == amounts.len(), Errors::INVALID_ARRAY_LENGTH); + + let mut ids_span = ids.span(); + let mut amounts_span = amounts.span(); + + loop { + if ids_span.len() == 0 { + break (); + } + let id = *ids_span.pop_front().unwrap(); + let amount = *amounts_span.pop_front().unwrap(); + self.update_balances(from, to, id, amount); + // assert( + // _check_on_erc1155_received(from, to, id, data), Errors::SAFE_TRANSFER_FAILED + // ); + }; + + self + .emit_event( + TransferBatch { operator: get_caller_address(), from, to, ids, values: amounts } + ); + } + + fn _mint(ref self: ContractState, to: ContractAddress, id: u256, amount: u256) { + assert(to.is_non_zero(), Errors::INVALID_RECEIVER); + + self.set_balance(to, id, self.get_balance(to, id).amount + amount); + + self + .emit_event( + TransferSingle { + operator: get_caller_address(), + from: Zeroable::zero(), + to, + id, + value: amount + } + ); + } + + fn _burn(ref self: ContractState, id: u256, amount: u256) { + let caller = get_caller_address(); + assert(self.get_balance(caller, id).amount >= amount, Errors::INSUFFICIENT_BALANCE); + + self.set_balance(caller, id, self.get_balance(caller, id).amount - amount); + + self + .emit_event( + TransferSingle { + operator: get_caller_address(), + from: caller, + to: Zeroable::zero(), + id, + value: amount + } + ); + } + + fn _safe_mint( + ref self: ContractState, + to: ContractAddress, + id: u256, + amount: u256, + data: Span + ) { + self._mint(to, id, amount); + // assert( + // _check_on_erc1155_received(Zeroable::zero(), to, id, data), + // Errors::SAFE_MINT_FAILED + // ); + } + } +//#[internal] +// fn _check_on_erc1155_received( +// from: ContractAddress, to: ContractAddress, token_id: u256, data: Span +// ) -> bool { +// if (DualCaseSRC5 { contract_address: to } +// .supports_interface(interface::IERC1155_RECEIVER_ID)) { +// DualCaseERC1155Receiver { contract_address: to } +// .on_erc1155_received( +// get_caller_address(), from, token_id, data +// ) == interface::IERC1155_RECEIVER_ID +// } else { +// DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) +// } +// } + +} diff --git a/presets/token/src/token/erc1155/interface.cairo b/presets/token/src/token/erc1155/interface.cairo new file mode 100644 index 00000000..ecce8fb9 --- /dev/null +++ b/presets/token/src/token/erc1155/interface.cairo @@ -0,0 +1,62 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IERC1155 { + fn balance_of(self: @TState, account: ContractAddress, id: u256) -> u256; + fn balance_of_batch( + self: @TState, accounts: Array, ids: Array + ) -> Array; + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + fn is_approved_for_all( + self: @TState, account: ContractAddress, operator: ContractAddress + ) -> bool; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ); + fn safe_batch_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ); +} + +#[starknet::interface] +trait IERC1155CamelOnly { + fn balanceOf(self: @TState, account: ContractAddress, id: u256) -> u256; + fn balanceOfBatch( + self: @TState, accounts: Array, ids: Array + ) -> Array; + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + fn isApprovedForAll(self: @TState, account: ContractAddress, operator: ContractAddress) -> bool; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + id: u256, + amount: u256, + data: Array + ); + fn safeBatchTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + ids: Array, + amounts: Array, + data: Array + ); +} + +#[starknet::interface] +trait IERC1155Metadata { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn uri(self: @TState, token_id: u256) -> felt252; +} diff --git a/presets/token/src/token/erc1155/models.cairo b/presets/token/src/token/erc1155/models.cairo new file mode 100644 index 00000000..fa021aa9 --- /dev/null +++ b/presets/token/src/token/erc1155/models.cairo @@ -0,0 +1,33 @@ +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct ERC1155Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC1155OperatorApproval { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + operator: ContractAddress, + approved: bool +} + + +#[derive(Model, Copy, Drop, Serde)] +struct ERC1155Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + #[key] + id: u256, + amount: u256 +} \ No newline at end of file diff --git a/presets/token/src/token/erc20.cairo b/presets/token/src/token/erc20.cairo new file mode 100644 index 00000000..238e05a1 --- /dev/null +++ b/presets/token/src/token/erc20.cairo @@ -0,0 +1,331 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +trait IERC20CamelOnly { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} + +#[starknet::contract] +mod ERC20 { + use dojo_erc::token::erc20_models::{ERC20Allowance, ERC20Balance, ERC20Meta}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use integer::BoundedInt; + use super::IERC20; + use super::IERC20CamelOnly; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use zeroable::Zeroable; + use debug::PrintTrait; + + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Copy, Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + } + + #[derive(Copy, Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256 + } + + mod Errors { + const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; + const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; + const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; + const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; + const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; + const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress + ) { + self._world.write(world); + self.initializer(name, symbol); + self._mint(recipient, initial_supply); + } + + // + // External + // + + #[external(v0)] + impl ERC20Impl of IERC20 { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn decimals(self: @ContractState) -> u8 { + 18 + } + + fn total_supply(self: @ContractState) -> u256 { + self.get_meta().total_supply + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self.get_balance(account).amount + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.get_allowance(owner, spender).amount + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + self._transfer(sender, recipient, amount); + true + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + let caller = get_caller_address(); + self._spend_allowance(sender, caller, amount); + self._transfer(sender, recipient, amount); + true + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let owner = get_caller_address(); + self + .set_allowance( + ERC20Allowance { token: get_contract_address(), owner, spender, amount } + ); + true + } + } + + #[external(v0)] + impl ERC20CamelOnlyImpl of IERC20CamelOnly { + fn totalSupply(self: @ContractState) -> u256 { + ERC20Impl::total_supply(self) + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC20Impl::balance_of(self, account) + } + + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + ERC20Impl::transfer_from(ref self, sender, recipient, amount) + } + } + + #[external(v0)] + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) -> bool { + self.update_allowance(get_caller_address(), spender, 0, added_value); + true + } + + #[external(v0)] + fn increaseAllowance( + ref self: ContractState, spender: ContractAddress, addedValue: u256 + ) -> bool { + increase_allowance(ref self, spender, addedValue) + } + + #[external(v0)] + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) -> bool { + self.update_allowance(get_caller_address(), spender, subtracted_value, 0); + true + } + + #[external(v0)] + fn decreaseAllowance( + ref self: ContractState, spender: ContractAddress, subtractedValue: u256 + ) -> bool { + decrease_allowance(ref self, spender, subtractedValue) + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC20Meta { + get!(self.world(), get_contract_address(), ERC20Meta) + } + + // Helper function to update total_supply model + fn update_total_supply(ref self: ContractState, subtract: u256, add: u256) { + let mut meta = self.get_meta(); + // adding and subtracting is fewer steps than if + meta.total_supply = meta.total_supply - subtract; + meta.total_supply = meta.total_supply + add; + set!(self.world(), (meta)); + } + + // Helper function for balance model + fn get_balance(self: @ContractState, account: ContractAddress) -> ERC20Balance { + get!(self.world(), (get_contract_address(), account), ERC20Balance) + } + + fn update_balance( + ref self: ContractState, account: ContractAddress, subtract: u256, add: u256 + ) { + let mut balance: ERC20Balance = self.get_balance(account); + // adding and subtracting is fewer steps than if + balance.amount = balance.amount - subtract; + balance.amount = balance.amount + add; + set!(self.world(), (balance)); + } + + // Helper function for allowance model + fn get_allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress, + ) -> ERC20Allowance { + get!(self.world(), (get_contract_address(), owner, spender), ERC20Allowance) + } + + fn update_allowance( + ref self: ContractState, + owner: ContractAddress, + spender: ContractAddress, + subtract: u256, + add: u256 + ) { + let mut allowance = self.get_allowance(owner, spender); + // adding and subtracting is fewer steps than if + allowance.amount = allowance.amount - subtract; + allowance.amount = allowance.amount + add; + self.set_allowance(allowance); + } + + fn set_allowance(ref self: ContractState, allowance: ERC20Allowance) { + assert(!allowance.owner.is_zero(), Errors::APPROVE_FROM_ZERO); + assert(!allowance.spender.is_zero(), Errors::APPROVE_TO_ZERO); + set!(self.world(), (allowance)); + self + .emit_event( + Approval { + owner: allowance.owner, spender: allowance.spender, value: allowance.amount + } + ); + } + + fn emit_event< + S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SCopy: Copy + >( + ref self: ContractState, event: S + ) { + self.emit(event); + emit!(self.world(), event); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252) { + let meta = ERC20Meta { token: get_contract_address(), name, symbol, total_supply: 0 }; + set!(self.world(), (meta)); + } + + fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); + self.update_total_supply(0, amount); + self.update_balance(recipient, 0, amount); + self.emit_event(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); + } + + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { + assert(!account.is_zero(), Errors::BURN_FROM_ZERO); + self.update_total_supply(amount, 0); + self.update_balance(account, amount, 0); + self.emit_event(Transfer { from: account, to: Zeroable::zero(), value: amount }); + } + + fn _approve( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + self + .set_allowance( + ERC20Allowance { token: get_contract_address(), owner, spender, amount } + ); + } + + fn _transfer( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); + assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); + self.update_balance(sender, amount, 0); + self.update_balance(recipient, 0, amount); + self.emit_event(Transfer { from: sender, to: recipient, value: amount }); + } + + fn _spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + let current_allowance = self.get_allowance(owner, spender).amount; + if current_allowance != BoundedInt::max() { + self.update_allowance(owner, spender, amount, 0); + } + } + } +} diff --git a/presets/token/src/token/erc20_models.cairo b/presets/token/src/token/erc20_models.cairo new file mode 100644 index 00000000..ab68718d --- /dev/null +++ b/presets/token/src/token/erc20_models.cairo @@ -0,0 +1,30 @@ +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + amount: u256, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20Allowance { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + amount: u256, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + total_supply: u256, +} diff --git a/presets/token/src/token/erc721.cairo b/presets/token/src/token/erc721.cairo new file mode 100644 index 00000000..15b96462 --- /dev/null +++ b/presets/token/src/token/erc721.cairo @@ -0,0 +1,5 @@ +mod erc721; +mod models; +mod interface; + +use erc721::ERC721; diff --git a/presets/token/src/token/erc721/erc721.cairo b/presets/token/src/token/erc721/erc721.cairo new file mode 100644 index 00000000..ffd7915d --- /dev/null +++ b/presets/token/src/token/erc721/erc721.cairo @@ -0,0 +1,443 @@ +#[starknet::contract] +mod ERC721 { + use dojo_erc::token::erc721::models::{ + ERC721Meta, ERC721OperatorApproval, ERC721Owner, ERC721Balance, ERC721TokenApproval + }; + use dojo_erc::token::erc721::interface; + use dojo_erc::token::erc721::interface::{IERC721, IERC721CamelOnly}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use integer::BoundedInt; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use zeroable::Zeroable; + use debug::PrintTrait; + + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Copy, Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + ApprovalForAll: ApprovalForAll + } + + #[derive(Copy, Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + token_id: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + approved: ContractAddress, + token_id: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct ApprovalForAll { + owner: ContractAddress, + operator: ContractAddress, + approved: bool + } + + mod Errors { + const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; + const INVALID_ACCOUNT: felt252 = 'ERC721: invalid account'; + const UNAUTHORIZED: felt252 = 'ERC721: unauthorized caller'; + const APPROVAL_TO_OWNER: felt252 = 'ERC721: approval to owner'; + const SELF_APPROVAL: felt252 = 'ERC721: self approval'; + const INVALID_RECEIVER: felt252 = 'ERC721: invalid receiver'; + const ALREADY_MINTED: felt252 = 'ERC721: token already minted'; + const WRONG_SENDER: felt252 = 'ERC721: wrong sender'; + const SAFE_MINT_FAILED: felt252 = 'ERC721: safe mint failed'; + const SAFE_TRANSFER_FAILED: felt252 = 'ERC721: safe transfer failed'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, + recipient: ContractAddress, + token_id: u256 + ) { + self._world.write(world); + self.initializer(name, symbol, base_uri); + self._mint(recipient, token_id); + } + + // + // External + // + + // #[external(v0)] + // impl SRC5Impl of ISRC5 { + // fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) + // } + // } + + // #[external(v0)] + // impl SRC5CamelImpl of ISRC5Camel { + // fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId) + // } + // } + + #[external(v0)] + impl ERC721MetadataImpl of interface::IERC721Metadata { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn token_uri(self: @ContractState, token_id: u256) -> felt252 { + assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + // TODO : concat with id + self.get_uri(token_id) + } + } + + #[external(v0)] + impl ERC721MetadataCamelOnlyImpl of interface::IERC721MetadataCamelOnly { + fn tokenURI(self: @ContractState, tokenId: u256) -> felt252 { + assert(self._exists(tokenId), Errors::INVALID_TOKEN_ID); + self.get_uri(tokenId) + } + } + + #[external(v0)] + impl ERC721Impl of interface::IERC721 { + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + assert(account.is_non_zero(), Errors::INVALID_ACCOUNT); + self.get_balance(account).amount + } + + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + self._owner_of(token_id) + } + + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { + assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + self.get_token_approval(token_id).address + } + + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + self.get_operator_approval(owner, operator).approved + } + + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self._owner_of(token_id); + + let caller = get_caller_address(); + assert( + owner == caller || ERC721Impl::is_approved_for_all(@self, owner, caller), + Errors::UNAUTHORIZED + ); + self._approve(to, token_id); + } + + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { + self._set_approval_for_all(get_caller_address(), operator, approved) + } + + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED + ); + self._transfer(from, to, token_id); + } + + fn safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED + ); + self._safe_transfer(from, to, token_id, data); + } + } + + #[external(v0)] + impl ERC721CamelOnlyImpl of interface::IERC721CamelOnly { + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC721Impl::balance_of(self, account) + } + + fn ownerOf(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721Impl::owner_of(self, tokenId) + } + + fn getApproved(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721Impl::get_approved(self, tokenId) + } + + fn isApprovedForAll( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + ERC721Impl::is_approved_for_all(self, owner, operator) + } + + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { + ERC721Impl::set_approval_for_all(ref self, operator, approved) + } + + fn transferFrom( + ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256 + ) { + ERC721Impl::transfer_from(ref self, from, to, tokenId) + } + + fn safeTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ) { + ERC721Impl::safe_transfer_from(ref self, from, to, tokenId, data) + } + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC721Meta { + get!(self.world(), get_contract_address(), ERC721Meta) + } + + fn get_uri(self: @ContractState, token_id: u256) -> felt252 { + // TODO : concat with id when we have string type + self.get_meta().base_uri + } + + fn get_balance(self: @ContractState, account: ContractAddress) -> ERC721Balance { + get!(self.world(), (get_contract_address(), account), ERC721Balance) + } + + fn get_owner_of(self: @ContractState, token_id: u256) -> ERC721Owner { + get!(self.world(), (get_contract_address(), token_id), ERC721Owner) + } + + fn get_token_approval(self: @ContractState, token_id: u256) -> ERC721TokenApproval { + get!(self.world(), (get_contract_address(), token_id), ERC721TokenApproval) + } + + fn get_operator_approval( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> ERC721OperatorApproval { + get!(self.world(), (get_contract_address(), owner, operator), ERC721OperatorApproval) + } + + fn set_token_approval( + ref self: ContractState, + owner: ContractAddress, + to: ContractAddress, + token_id: u256, + emit: bool + ) { + set!( + self.world(), + ERC721TokenApproval { token: get_contract_address(), token_id, address: to, } + ); + if emit { + self.emit_event(Approval { owner, approved: to, token_id }); + } + } + + fn set_operator_approval( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + set!( + self.world(), + ERC721OperatorApproval { token: get_contract_address(), owner, operator, approved } + ); + self.emit_event(ApprovalForAll { owner, operator, approved }); + } + + fn set_balance(ref self: ContractState, account: ContractAddress, amount: u256) { + set!(self.world(), ERC721Balance { token: get_contract_address(), account, amount }); + } + + fn set_owner(ref self: ContractState, token_id: u256, address: ContractAddress) { + set!(self.world(), ERC721Owner { token: get_contract_address(), token_id, address }); + } + + fn emit_event< + S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SCopy: Copy + >( + ref self: ContractState, event: S + ) { + self.emit(event); + emit!(self.world(), event); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { + let meta = ERC721Meta { token: get_contract_address(), name, symbol, base_uri }; + set!(self.world(), (meta)); + // let mut unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::InternalImpl::register_interface(ref unsafe_state, interface::IERC721_ID); + // src5::SRC5::InternalImpl::register_interface( + // ref unsafe_state, interface::IERC721_METADATA_ID + // ); + } + + fn _owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + let owner = self.get_owner_of(token_id).address; + match owner.is_zero() { + bool::False(()) => owner, + bool::True(()) => panic_with_felt252(Errors::INVALID_TOKEN_ID) + } + } + + fn _exists(self: @ContractState, token_id: u256) -> bool { + let owner = self.get_owner_of(token_id).address; + owner.is_non_zero() + } + + fn _is_approved_or_owner( + self: @ContractState, spender: ContractAddress, token_id: u256 + ) -> bool { + let owner = self._owner_of(token_id); + let is_approved_for_all = ERC721Impl::is_approved_for_all(self, owner, spender); + owner == spender + || is_approved_for_all + || spender == ERC721Impl::get_approved(self, token_id) + } + + fn _approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self._owner_of(token_id); + assert(owner != to, Errors::APPROVAL_TO_OWNER); + + self.set_token_approval(owner, to, token_id, true); + } + + fn _set_approval_for_all( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + assert(owner != operator, Errors::SELF_APPROVAL); + self.set_operator_approval(owner, operator, approved); + } + + fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + assert(!to.is_zero(), Errors::INVALID_RECEIVER); + assert(!self._exists(token_id), Errors::ALREADY_MINTED); + + self.set_balance(to, self.get_balance(to).amount + 1); + self.set_owner(token_id, to); + + self.emit_event(Transfer { from: Zeroable::zero(), to, token_id }); + } + + fn _transfer( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert(!to.is_zero(), Errors::INVALID_RECEIVER); + let owner = self._owner_of(token_id); + assert(from == owner, Errors::WRONG_SENDER); + + // Implicit clear approvals, no need to emit an event + self.set_token_approval(owner, Zeroable::zero(), token_id, false); + + self.set_balance(from, self.get_balance(from).amount - 1); + self.set_balance(to, self.get_balance(to).amount + 1); + self.set_owner(token_id, to); + + self.emit_event(Transfer { from, to, token_id }); + } + + fn _burn(ref self: ContractState, token_id: u256) { + let owner = self._owner_of(token_id); + + // Implicit clear approvals, no need to emit an event + self.set_token_approval(owner, Zeroable::zero(), token_id, false); + + self.set_balance(owner, self.get_balance(owner).amount - 1); + self.set_owner(token_id, Zeroable::zero()); + + self.emit_event(Transfer { from: owner, to: Zeroable::zero(), token_id }); + } + + fn _safe_mint( + ref self: ContractState, to: ContractAddress, token_id: u256, data: Span + ) { + self._mint(to, token_id); + // assert( + // _check_on_erc721_received(Zeroable::zero(), to, token_id, data), + // Errors::SAFE_MINT_FAILED + // ); + } + + fn _safe_transfer( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ) { + self._transfer(from, to, token_id); + // assert( + // _check_on_erc721_received(from, to, token_id, data), Errors::SAFE_TRANSFER_FAILED + // ); + } + // fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { + // assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + // self._token_uri.write(token_id, token_uri) + // } + } + +//#[internal] +// fn _check_on_erc721_received( +// from: ContractAddress, to: ContractAddress, token_id: u256, data: Span +// ) -> bool { +// if (DualCaseSRC5 { contract_address: to } +// .supports_interface(interface::IERC721_RECEIVER_ID)) { +// DualCaseERC721Receiver { contract_address: to } +// .on_erc721_received( +// get_caller_address(), from, token_id, data +// ) == interface::IERC721_RECEIVER_ID +// } else { +// DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) +// } +// } +} diff --git a/presets/token/src/token/erc721/interface.cairo b/presets/token/src/token/erc721/interface.cairo new file mode 100644 index 00000000..730ad2e0 --- /dev/null +++ b/presets/token/src/token/erc721/interface.cairo @@ -0,0 +1,60 @@ +use starknet::ContractAddress; + +const IERC721_ID: felt252 = 0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943; +const IERC721_METADATA_ID: felt252 = + 0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd; +const IERC721_RECEIVER_ID: felt252 = + 0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc; + +#[starknet::interface] +trait IERC721 { + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn owner_of(self: @TState, token_id: u256) -> ContractAddress; + fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn approve(ref self: TState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + fn get_approved(self: @TState, token_id: u256) -> ContractAddress; + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; +} + +#[starknet::interface] +trait IERC721CamelOnly { + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn ownerOf(self: @TState, tokenId: u256) -> ContractAddress; + fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256); + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; +} + +// +// IERC721Metadata +// + +#[starknet::interface] +trait IERC721Metadata { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn token_uri(self: @TState, token_id: u256) -> felt252; +} + +#[starknet::interface] +trait IERC721MetadataCamelOnly { + fn tokenURI(self: @TState, tokenId: u256) -> felt252; +} diff --git a/presets/token/src/token/erc721/models.cairo b/presets/token/src/token/erc721/models.cairo new file mode 100644 index 00000000..1bc4bcad --- /dev/null +++ b/presets/token/src/token/erc721/models.cairo @@ -0,0 +1,48 @@ +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721OperatorApproval { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + operator: ContractAddress, + approved: bool +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721Owner { + #[key] + token: ContractAddress, + #[key] + token_id: u256, + address: ContractAddress +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + amount: u256, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721TokenApproval { + #[key] + token: ContractAddress, + #[key] + token_id: u256, + address: ContractAddress, +} \ No newline at end of file From 6e5de3531975b80fb30e3b2495b6ffd000cc3cc5 Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 5 Nov 2023 15:11:09 +0100 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=91=B7=20Update=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/{crates.yml => ci.yml} | 33 ++- .github/workflows/examples.yml | 53 ----- Scarb.lock | 16 ++ Scarb.toml | 1 + examples/projectile/src/lib.cairo | 252 ++++++++++++----------- 5 files changed, 179 insertions(+), 176 deletions(-) rename .github/workflows/{crates.yml => ci.yml} (66%) delete mode 100644 .github/workflows/examples.yml diff --git a/.github/workflows/crates.yml b/.github/workflows/ci.yml similarity index 66% rename from .github/workflows/crates.yml rename to .github/workflows/ci.yml index b2f52c9b..da448bef 100644 --- a/.github/workflows/crates.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Test +name: Check on: [push, pull_request] @@ -13,10 +13,11 @@ jobs: - uses: actions/checkout@v4 - uses: software-mansion/setup-scarb@v1 - name: Format - run: scarb fmt --package random --check + run: scarb fmt --check shell: bash build: + needs: check runs-on: ubuntu-latest name: Build package steps: @@ -25,8 +26,9 @@ jobs: - name: Build run: sozo build shell: bash - + algebra: + needs: [check, build] runs-on: ubuntu-latest name: Test algebra crate steps: @@ -37,6 +39,7 @@ jobs: shell: bash defi: + needs: [check, build] runs-on: ubuntu-latest name: Test defi crate steps: @@ -47,6 +50,7 @@ jobs: shell: bash random: + needs: [check, build] runs-on: ubuntu-latest name: Test random crate steps: @@ -57,6 +61,7 @@ jobs: shell: bash security: + needs: [check, build] runs-on: ubuntu-latest name: Test security crate steps: @@ -64,4 +69,26 @@ jobs: - uses: ./.github/actions/setup - name: Test run: sozo test -f security + shell: bash + + market: + needs: [check, build] + runs-on: ubuntu-latest + name: Test market example + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup + - name: Test + run: sozo test -f market + shell: bash + + projectile: + needs: [check, build] + runs-on: ubuntu-latest + name: Test projectile example + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup + - name: Test + run: sozo test -f projectile shell: bash \ No newline at end of file diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml deleted file mode 100644 index 72a04e7b..00000000 --- a/.github/workflows/examples.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Test - -on: [push, pull_request] - -env: - DOJO_VERSION: v0.3.3 - WORKING-DIRECTORY: examples - -jobs: - check: - runs-on: ubuntu-latest - name: Check format - steps: - - uses: actions/checkout@v4 - - uses: software-mansion/setup-scarb@v1 - - name: Format - working-directory: ${{ env.WORKING-DIRECTORY }} - run: scarb fmt --package random --check - shell: bash - - build: - runs-on: ubuntu-latest - name: Build package - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup - - name: Build - working-directory: ${{ env.WORKING-DIRECTORY }} - run: sozo build - shell: bash - - market: - runs-on: ubuntu-latest - name: Test market example - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup - - name: Test - working-directory: ${{ env.WORKING-DIRECTORY }} - run: sozo test - shell: bash - - projectile: - runs-on: ubuntu-latest - name: Test projectile example - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup - - name: Test - working-directory: ${{ env.WORKING-DIRECTORY }} - run: sozo test - shell: bash - \ No newline at end of file diff --git a/Scarb.lock b/Scarb.lock index 508ef783..4df3bf57 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -32,6 +32,22 @@ dependencies = [ name = "dojo_plugin" version = "0.3.3" +[[package]] +name = "market" +version = "0.0.0" +dependencies = [ + "cubit", + "dojo", +] + +[[package]] +name = "projectile" +version = "0.0.0" +dependencies = [ + "algebra", + "cubit", +] + [[package]] name = "random" version = "0.0.0" diff --git a/Scarb.toml b/Scarb.toml index 3b36f0ca..a3157149 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -5,6 +5,7 @@ description = "Community-maintained libraries for Cairo" homepage = "https://github.com/dojoengine/origami" members = [ "crates/*", + "examples/*", ] [workspace.dependencies] diff --git a/examples/projectile/src/lib.cairo b/examples/projectile/src/lib.cairo index 6094e546..1926d4ef 100644 --- a/examples/projectile/src/lib.cairo +++ b/examples/projectile/src/lib.cairo @@ -1,15 +1,10 @@ -use core::option::OptionTrait; -use array::ArrayTrait; -use debug::PrintTrait; -use array::ArrayTCloneImpl; -use array::SpanTrait; -use clone::Clone; -use traits::PartialOrd; - -use cubit::f128::test::helpers::assert_precise; -use cubit::f128::types::fixed::{Fixed, FixedPartialOrd, FixedTrait, ONE_u128}; +// External imports + +use cubit::f128::types::fixed::{Fixed, FixedTrait, ONE_u128}; use cubit::f128::math::trig; +// Internal imports + use algebra::vec2::{Vec2, Vec2Trait}; fn main() -> (usize, Array::, Array::) { @@ -50,10 +45,10 @@ fn main() -> (usize, Array::, Array::) { assert(v_0_mag.sign == false, 'need v_0_mag_felt > 0'); // `theta_0_deg.mag` not exact after conversion, so use 180.0000001 assert(theta_0_deg.mag <= 180000001 * ONE_u128 / 1000000, '-180 <= theta_0_deg_felt <= 180'); - assert(FixedPartialOrd::le(x_0, x_max), 'need x_0 <= x_max'); - assert(FixedPartialOrd::ge(x_0, x_min), 'need x_0 >= x_min'); - assert(FixedPartialOrd::le(y_0, y_max), 'need y_0 <= y_max'); - assert(FixedPartialOrd::ge(y_0, y_min), 'need y_0 >= y_min'); + assert(x_0 <= x_max, 'need x_0 <= x_max'); + assert(x_0 >= x_min, 'need x_0 >= x_min'); + assert(y_0 <= y_max, 'need y_0 <= y_max'); + assert(y_0 >= y_min, 'need y_0 >= y_min'); // Initial position vector let r_0 = Vec2Trait::::new(x_0, y_0); // Initial velocity vector @@ -136,110 +131,127 @@ fn calc_y(y_0: Fixed, v_0y: Fixed, g: Fixed, t: Fixed) -> Fixed { y_0 + v_0y * t - half * g * t * t } -#[test] -#[available_gas(2000000)] -fn test_deg_to_rad() { - let sixty = FixedTrait::new(60 * ONE_u128, false); - let theta = deg_to_rad(sixty); - assert_precise(theta, 19317385221538994246, 'invalid PI/3', Option::None(())); - assert(theta.sign == false, 'invalid sign'); - - let minus_120 = FixedTrait::new(120 * ONE_u128, true); - let theta = deg_to_rad(minus_120); - assert_precise(theta, -38634770443077988493, 'invalid -2*PI/3', Option::None(())); - assert(theta.sign == true, 'invalid sign'); -} - -#[test] -#[available_gas(20000000)] -fn test_vec2_from_mag_theta() { - let mag = FixedTrait::new(100 * ONE_u128, false); - let sixty = FixedTrait::new(60 * ONE_u128, false); - let theta = deg_to_rad(sixty); - let vec2 = vec2_from_mag_theta(mag, theta); - assert_precise(vec2.x, 922337203685477580800, 'invalid vec2.x mag', Option::None(())); // 50 - assert(vec2.x.sign == false, 'invalid vec2.x.sign'); - assert_precise(vec2.y, 1597534898494251510150, 'invalid vec2.y mag', Option::None(())); // 86.6 - assert(vec2.y.sign == false, 'invalid vec2.y.sig'); - - let minus_120 = FixedTrait::new(120 * ONE_u128, true); - let theta = deg_to_rad(minus_120); - let vec2 = vec2_from_mag_theta(mag, theta); - assert_precise(vec2.x, -922337203685477580800, 'invalid vec2.x mag', Option::None(())); // -50 - assert(vec2.x.sign == true, 'invalid vec2.x.sign'); - assert_precise( - vec2.y, -1597534898494251510150, 'invalid vec2.y mag', Option::None(()) - ); // -86.6 - assert(vec2.y.sign == true, 'invalid vec2.y.sig'); -} - -#[test] -#[available_gas(20000000)] -fn test_fill_position_s() { - let v_0_mag = FixedTrait::from_unscaled_felt(100); - let theta_0_deg = FixedTrait::from_unscaled_felt(65); - let theta_0 = deg_to_rad(theta_0_deg); - let x_0 = FixedTrait::from_unscaled_felt(0); - let y_0 = FixedTrait::from_unscaled_felt(0); - - let x_max = FixedTrait::from_unscaled_felt(1000); - let x_min = FixedTrait::from_unscaled_felt(-1000); - let y_max = FixedTrait::from_unscaled_felt(500); - let y_min = FixedTrait::from_unscaled_felt(-500); - - let r_0 = Vec2Trait::::new(x_0, y_0); - let v_0 = vec2_from_mag_theta(v_0_mag, theta_0); - let g = FixedTrait::new(98 * ONE_u128 / 10, false); - let delta_t = FixedTrait::from_unscaled_felt(2); - - let plot_params = (x_max, x_min, y_max, y_min); - let motion_params = (r_0, v_0, g, delta_t); - - let mut position_s: (Array, Array) = fill_position_s(plot_params, motion_params); - - let (x_s, y_s) = position_s; - let length = x_s.len(); - assert(length == 12, 'invalid length'); - - assert_precise( - *x_s[5], 7795930915206679528264, 'invalid x_s[5]', Option::None(()) - ); // 422.61826174069944 - assert(*x_s.at(5).sign == false, 'invalid sign'); - assert_precise( - *y_s[5], 7679523203357457794972, 'invalid y_s[5]', Option::None(()) - ); // 416.3077870366498 - assert(*y_s.at(5).sign == false, 'invalid sign'); - - assert_precise( - *x_s[10], 15591861830413359425462, 'invalid x_s[10]', Option::None(()) - ); // 845.2365234813989, custom precision 1e-6 - assert(*x_s.at(10).sign == false, 'invalid sign'); - assert_precise( - *y_s[10], -2718762785520446838411, 'invalid y_s[10]', Option::None(()) - ); // -147.3844259267005, custom precision 1e-6 - assert(*y_s.at(10).sign == true, 'invalid sign'); -} - -#[test] -#[available_gas(2000000)] -fn test_calc_x() { - let x_0 = FixedTrait::new(100 * ONE_u128, false); - let v_0x = FixedTrait::new(50 * ONE_u128, false); - let t = FixedTrait::new(16 * ONE_u128, false); - let x = calc_x(x_0, v_0x, t); - assert(x.mag == 900 * ONE_u128, 'invalid mag'); - assert(x.sign == false, 'invalid sign'); +#[cfg(test)] +mod tests { + // External imports + + use cubit::f128::test::helpers::assert_precise; + + // Local imports + + use super::{deg_to_rad, fill_position_s, vec2_from_mag_theta, calc_x, calc_y}; + use super::{Vec2Trait, Fixed, FixedTrait, ONE_u128}; + #[test] + #[available_gas(2000000)] + fn test_deg_to_rad() { + let sixty = FixedTrait::new(60 * ONE_u128, false); + let theta = deg_to_rad(sixty); + assert_precise(theta, 19317385221538994246, 'invalid PI/3', Option::None(())); + assert(theta.sign == false, 'invalid sign'); + + let minus_120 = FixedTrait::new(120 * ONE_u128, true); + let theta = deg_to_rad(minus_120); + assert_precise(theta, -38634770443077988493, 'invalid -2*PI/3', Option::None(())); + assert(theta.sign == true, 'invalid sign'); + } + + #[test] + #[available_gas(20000000)] + fn test_vec2_from_mag_theta() { + let mag = FixedTrait::new(100 * ONE_u128, false); + let sixty = FixedTrait::new(60 * ONE_u128, false); + let theta = deg_to_rad(sixty); + let vec2 = vec2_from_mag_theta(mag, theta); + assert_precise(vec2.x, 922337203685477580800, 'invalid vec2.x mag', Option::None(())); // 50 + assert(vec2.x.sign == false, 'invalid vec2.x.sign'); + assert_precise( + vec2.y, 1597534898494251510150, 'invalid vec2.y mag', Option::None(()) + ); // 86.6 + assert(vec2.y.sign == false, 'invalid vec2.y.sig'); + + let minus_120 = FixedTrait::new(120 * ONE_u128, true); + let theta = deg_to_rad(minus_120); + let vec2 = vec2_from_mag_theta(mag, theta); + assert_precise( + vec2.x, -922337203685477580800, 'invalid vec2.x mag', Option::None(()) + ); // -50 + assert(vec2.x.sign == true, 'invalid vec2.x.sign'); + assert_precise( + vec2.y, -1597534898494251510150, 'invalid vec2.y mag', Option::None(()) + ); // -86.6 + assert(vec2.y.sign == true, 'invalid vec2.y.sig'); + } + + #[test] + #[available_gas(20000000)] + fn test_fill_position_s() { + let v_0_mag = FixedTrait::from_unscaled_felt(100); + let theta_0_deg = FixedTrait::from_unscaled_felt(65); + let theta_0 = deg_to_rad(theta_0_deg); + let x_0 = FixedTrait::from_unscaled_felt(0); + let y_0 = FixedTrait::from_unscaled_felt(0); + + let x_max = FixedTrait::from_unscaled_felt(1000); + let x_min = FixedTrait::from_unscaled_felt(-1000); + let y_max = FixedTrait::from_unscaled_felt(500); + let y_min = FixedTrait::from_unscaled_felt(-500); + + let r_0 = Vec2Trait::::new(x_0, y_0); + let v_0 = vec2_from_mag_theta(v_0_mag, theta_0); + let g = FixedTrait::new(98 * ONE_u128 / 10, false); + let delta_t = FixedTrait::from_unscaled_felt(2); + + let plot_params = (x_max, x_min, y_max, y_min); + let motion_params = (r_0, v_0, g, delta_t); + + let mut position_s: (Array, Array) = fill_position_s( + plot_params, motion_params + ); + + let (x_s, y_s) = position_s; + let length = x_s.len(); + assert(length == 12, 'invalid length'); + + assert_precise( + *x_s[5], 7795930915206679528264, 'invalid x_s[5]', Option::None(()) + ); // 422.61826174069944 + assert(*x_s.at(5).sign == false, 'invalid sign'); + assert_precise( + *y_s[5], 7679523203357457794972, 'invalid y_s[5]', Option::None(()) + ); // 416.3077870366498 + assert(*y_s.at(5).sign == false, 'invalid sign'); + + assert_precise( + *x_s[10], 15591861830413359425462, 'invalid x_s[10]', Option::None(()) + ); // 845.2365234813989, custom precision 1e-6 + assert(*x_s.at(10).sign == false, 'invalid sign'); + assert_precise( + *y_s[10], -2718762785520446838411, 'invalid y_s[10]', Option::None(()) + ); // -147.3844259267005, custom precision 1e-6 + assert(*y_s.at(10).sign == true, 'invalid sign'); + } + + #[test] + #[available_gas(2000000)] + fn test_calc_x() { + let x_0 = FixedTrait::new(100 * ONE_u128, false); + let v_0x = FixedTrait::new(50 * ONE_u128, false); + let t = FixedTrait::new(16 * ONE_u128, false); + let x = calc_x(x_0, v_0x, t); + assert(x.mag == 900 * ONE_u128, 'invalid mag'); + assert(x.sign == false, 'invalid sign'); + } + + #[test] + #[available_gas(2000000)] + fn test_calc_y() { + let y_0 = FixedTrait::new(100 * ONE_u128, false); + let v_0y = FixedTrait::new(50 * ONE_u128, false); + let t = FixedTrait::new(16 * ONE_u128, false); + let g = FixedTrait::new(98 * ONE_u128 / 10, false); + + let y = calc_y(y_0, v_0y, g, t); + assert_precise(y, -6537526099722665092710, 'invalid y', Option::None(())); // -354.4 + assert(y.sign == true, 'invalid sign'); + } } - -#[test] -#[available_gas(2000000)] -fn test_calc_y() { - let y_0 = FixedTrait::new(100 * ONE_u128, false); - let v_0y = FixedTrait::new(50 * ONE_u128, false); - let t = FixedTrait::new(16 * ONE_u128, false); - let g = FixedTrait::new(98 * ONE_u128 / 10, false); - - let y = calc_y(y_0, v_0y, g, t); - assert_precise(y, -6537526099722665092710, 'invalid y', Option::None(())); // -354.4 - assert(y.sign == true, 'invalid sign'); -} \ No newline at end of file From 5a75e159485b1da85c54f9863e6c3ce95fc868c3 Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 5 Nov 2023 15:15:58 +0100 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=91=B7=20Update=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da448bef..36d683d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,8 @@ name: Check on: [push, pull_request] env: - DOJO_VERSION: v0.3.3 + DOJO_VERSION: v0.3.3 + SCARB_VERSION: v2.3.1 jobs: check: @@ -12,6 +13,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: software-mansion/setup-scarb@v1 + with: + scarb-version: ${{ env.SCARB_VERSION }} - name: Format run: scarb fmt --check shell: bash From dec6a747bc2c30009dc72ba4cff767c76638af3d Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 5 Nov 2023 15:25:53 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=94=A7=20Improve=20config=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/market/Scarb.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/market/Scarb.toml b/examples/market/Scarb.toml index 85e902a5..2b89bdbe 100644 --- a/examples/market/Scarb.toml +++ b/examples/market/Scarb.toml @@ -6,4 +6,6 @@ homepage = "https://github.com/dojoengine/origami/tree/examples/market" [dependencies] cubit = { git = "https://github.com/influenceth/cubit", rev = "b459053" } -dojo = { git = "https://github.com/dojoengine/dojo.git", tag = "v0.3.3" } \ No newline at end of file +dojo.workspace = true + +[[target.dojo]] \ No newline at end of file From 98c1f48648f18da266bbc12f19fe775aef950298 Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 5 Nov 2023 17:25:04 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=9A=A7=20Upgrade=20dojo=20to=20latest?= =?UTF-8?q?=20tag=20and=20WIP=20on=20presets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- Scarb.toml | 2 +- presets/{token => }/.gitignore | 0 presets/Scarb.lock | 27 ++++ presets/Scarb.toml | 9 ++ .../src/token => src/erc1155}/erc1155.cairo | 0 .../erc1155}/erc1155/erc1155.cairo | 0 .../erc1155}/erc1155/interface.cairo | 0 .../erc1155}/erc1155/models.cairo | 0 .../erc1155/tests.cairo} | 0 .../src/token => src/erc20}/erc20.cairo | 31 +--- .../erc20/models.cairo} | 14 +- .../erc20/tests.cairo} | 0 .../src/token => src}/erc721/erc721.cairo | 133 ++++++++---------- .../src/token => src}/erc721/models.cairo | 2 + .../erc721/tests.cairo} | 41 ++++-- presets/src/lib.cairo | 13 ++ presets/{token => }/src/tests/constants.cairo | 0 .../{token => }/src/tests/test_erc1155.cairo | 0 .../{token => }/src/tests/test_erc721.cairo | 0 presets/{token => }/src/tests/utils.cairo | 0 presets/token/Scarb.lock | 20 --- presets/token/Scarb.toml | 29 ---- presets/token/src/lib.cairo | 17 --- presets/token/src/token/erc721.cairo | 5 - .../token/src/token/erc721/interface.cairo | 60 -------- 26 files changed, 147 insertions(+), 258 deletions(-) rename presets/{token => }/.gitignore (100%) create mode 100644 presets/Scarb.lock create mode 100644 presets/Scarb.toml rename presets/{token/src/token => src/erc1155}/erc1155.cairo (100%) rename presets/{token/src/token => src/erc1155}/erc1155/erc1155.cairo (100%) rename presets/{token/src/token => src/erc1155}/erc1155/interface.cairo (100%) rename presets/{token/src/token => src/erc1155}/erc1155/models.cairo (100%) rename presets/{token/src/tests/erc1155_tests.cairo => src/erc1155/tests.cairo} (100%) rename presets/{token/src/token => src/erc20}/erc20.cairo (90%) rename presets/{token/src/token/erc20_models.cairo => src/erc20/models.cairo} (59%) rename presets/{token/src/tests/erc20_tests.cairo => src/erc20/tests.cairo} (100%) rename presets/{token/src/token => src}/erc721/erc721.cairo (79%) rename presets/{token/src/token => src}/erc721/models.cairo (97%) rename presets/{token/src/tests/erc721_tests.cairo => src/erc721/tests.cairo} (98%) create mode 100644 presets/src/lib.cairo rename presets/{token => }/src/tests/constants.cairo (100%) rename presets/{token => }/src/tests/test_erc1155.cairo (100%) rename presets/{token => }/src/tests/test_erc721.cairo (100%) rename presets/{token => }/src/tests/utils.cairo (100%) delete mode 100644 presets/token/Scarb.lock delete mode 100644 presets/token/Scarb.toml delete mode 100644 presets/token/src/lib.cairo delete mode 100644 presets/token/src/token/erc721.cairo delete mode 100644 presets/token/src/token/erc721/interface.cairo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36d683d3..61e4d579 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Check on: [push, pull_request] env: - DOJO_VERSION: v0.3.3 + DOJO_VERSION: v0.3.4 SCARB_VERSION: v2.3.1 jobs: diff --git a/Scarb.toml b/Scarb.toml index a3157149..bdb31d56 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -9,4 +9,4 @@ members = [ ] [workspace.dependencies] -dojo = { git = "https://github.com/dojoengine/dojo.git", tag = "v0.3.3" } \ No newline at end of file +dojo = { git = "https://github.com/dojoengine/dojo.git", tag = "v0.3.4" } \ No newline at end of file diff --git a/presets/token/.gitignore b/presets/.gitignore similarity index 100% rename from presets/token/.gitignore rename to presets/.gitignore diff --git a/presets/Scarb.lock b/presets/Scarb.lock new file mode 100644 index 00000000..3a767aec --- /dev/null +++ b/presets/Scarb.lock @@ -0,0 +1,27 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "dojo" +version = "0.3.4" +source = "git+https://github.com/dojoengine/dojo.git?rev=d62ec8a#d62ec8a522f92d0a0ccf226c6e929bb918b31128" +dependencies = [ + "dojo_plugin", +] + +[[package]] +name = "dojo_plugin" +version = "0.3.4" + +[[package]] +name = "openzeppelin" +version = "0.8.0-beta.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=4eafccf#4eafccf107f98d975b20cbb6544b2a998e5f403f" + +[[package]] +name = "presets" +version = "0.0.0" +dependencies = [ + "dojo", + "openzeppelin", +] diff --git a/presets/Scarb.toml b/presets/Scarb.toml new file mode 100644 index 00000000..67dbbe95 --- /dev/null +++ b/presets/Scarb.toml @@ -0,0 +1,9 @@ +[package] +name = "presets" +version = "0.0.0" +description = "Implementations of ERC standards for the Dojo framework." +homepage = "https://github.com/dojoengine/origami/tree/presets" + +[dependencies] +dojo = { git = "https://github.com/dojoengine/dojo.git", rev = "d62ec8a" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "4eafccf" } \ No newline at end of file diff --git a/presets/token/src/token/erc1155.cairo b/presets/src/erc1155/erc1155.cairo similarity index 100% rename from presets/token/src/token/erc1155.cairo rename to presets/src/erc1155/erc1155.cairo diff --git a/presets/token/src/token/erc1155/erc1155.cairo b/presets/src/erc1155/erc1155/erc1155.cairo similarity index 100% rename from presets/token/src/token/erc1155/erc1155.cairo rename to presets/src/erc1155/erc1155/erc1155.cairo diff --git a/presets/token/src/token/erc1155/interface.cairo b/presets/src/erc1155/erc1155/interface.cairo similarity index 100% rename from presets/token/src/token/erc1155/interface.cairo rename to presets/src/erc1155/erc1155/interface.cairo diff --git a/presets/token/src/token/erc1155/models.cairo b/presets/src/erc1155/erc1155/models.cairo similarity index 100% rename from presets/token/src/token/erc1155/models.cairo rename to presets/src/erc1155/erc1155/models.cairo diff --git a/presets/token/src/tests/erc1155_tests.cairo b/presets/src/erc1155/tests.cairo similarity index 100% rename from presets/token/src/tests/erc1155_tests.cairo rename to presets/src/erc1155/tests.cairo diff --git a/presets/token/src/token/erc20.cairo b/presets/src/erc20/erc20.cairo similarity index 90% rename from presets/token/src/token/erc20.cairo rename to presets/src/erc20/erc20.cairo index 238e05a1..d21be836 100644 --- a/presets/token/src/token/erc20.cairo +++ b/presets/src/erc20/erc20.cairo @@ -1,40 +1,21 @@ -use starknet::ContractAddress; - -#[starknet::interface] -trait IERC20 { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn decimals(self: @TState) -> u8; - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} +// External imports -trait IERC20CamelOnly { - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} +use openzeppelin::token::erc20::interface; #[starknet::contract] mod ERC20 { use dojo_erc::token::erc20_models::{ERC20Allowance, ERC20Balance, ERC20Meta}; use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; use integer::BoundedInt; - use super::IERC20; - use super::IERC20CamelOnly; use starknet::ContractAddress; use starknet::{get_caller_address, get_contract_address}; use zeroable::Zeroable; use debug::PrintTrait; + // Local imports + + use super::interface::{IERC20, IERC20CamelOnly}; + #[storage] struct Storage { diff --git a/presets/token/src/token/erc20_models.cairo b/presets/src/erc20/models.cairo similarity index 59% rename from presets/token/src/token/erc20_models.cairo rename to presets/src/erc20/models.cairo index ab68718d..f19cda5c 100644 --- a/presets/token/src/token/erc20_models.cairo +++ b/presets/src/erc20/models.cairo @@ -1,29 +1,27 @@ -use starknet::ContractAddress; - #[derive(Model, Copy, Drop, Serde)] struct ERC20Balance { #[key] - token: ContractAddress, + token: starknet::ContractAddress, #[key] - account: ContractAddress, + account: starknet::ContractAddress, amount: u256, } #[derive(Model, Copy, Drop, Serde)] struct ERC20Allowance { #[key] - token: ContractAddress, + token: starknet::ContractAddress, #[key] - owner: ContractAddress, + owner: starknet::ContractAddress, #[key] - spender: ContractAddress, + spender: starknet::ContractAddress, amount: u256, } #[derive(Model, Copy, Drop, Serde)] struct ERC20Meta { #[key] - token: ContractAddress, + token: starknet::ContractAddress, name: felt252, symbol: felt252, total_supply: u256, diff --git a/presets/token/src/tests/erc20_tests.cairo b/presets/src/erc20/tests.cairo similarity index 100% rename from presets/token/src/tests/erc20_tests.cairo rename to presets/src/erc20/tests.cairo diff --git a/presets/token/src/token/erc721/erc721.cairo b/presets/src/erc721/erc721.cairo similarity index 79% rename from presets/token/src/token/erc721/erc721.cairo rename to presets/src/erc721/erc721.cairo index ffd7915d..57f84720 100644 --- a/presets/token/src/token/erc721/erc721.cairo +++ b/presets/src/erc721/erc721.cairo @@ -1,29 +1,71 @@ +// External imports + +use openzeppelin::token::erc721::interface; + #[starknet::contract] mod ERC721 { - use dojo_erc::token::erc721::models::{ - ERC721Meta, ERC721OperatorApproval, ERC721Owner, ERC721Balance, ERC721TokenApproval - }; - use dojo_erc::token::erc721::interface; - use dojo_erc::token::erc721::interface::{IERC721, IERC721CamelOnly}; - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + // Core imports + use integer::BoundedInt; + use zeroable::Zeroable; + + // Starknet imports + use starknet::ContractAddress; use starknet::{get_caller_address, get_contract_address}; - use zeroable::Zeroable; - use debug::PrintTrait; + // Dojo imports + + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + + // External imports + + use openzeppelin::token::erc721::erc721::ERC721; + use openzeppelin::introspection::interface::{ISRC5, ISRC5Camel}; + use openzeppelin::introspection::src5::SRC5Component; + + // Internal imports + + use presets::erc721::models::{ + ERC721Meta, + ERC721OperatorApproval, + ERC721Owner, + ERC721Balance, + ERC721TokenApproval, + }; + + // Local imports + + use super::interface; + + // Components + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + #[abi(embed_v0)] + impl SRC5CamelImpl = SRC5Component::SRC5CamelImpl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + impl SRC5EventCopy of Copy {} + + // Storage #[storage] struct Storage { _world: ContractAddress, + #[substorage(v0)] + src5: SRC5Component::Storage } + // Events + #[event] #[derive(Copy, Drop, starknet::Event)] enum Event { Transfer: Transfer, Approval: Approval, - ApprovalForAll: ApprovalForAll + ApprovalForAll: ApprovalForAll, + SRC5Event: SRC5Component::Event, } #[derive(Copy, Drop, starknet::Event)] @@ -56,8 +98,6 @@ mod ERC721 { const INVALID_RECEIVER: felt252 = 'ERC721: invalid receiver'; const ALREADY_MINTED: felt252 = 'ERC721: token already minted'; const WRONG_SENDER: felt252 = 'ERC721: wrong sender'; - const SAFE_MINT_FAILED: felt252 = 'ERC721: safe mint failed'; - const SAFE_TRANSFER_FAILED: felt252 = 'ERC721: safe transfer failed'; } #[constructor] @@ -79,22 +119,6 @@ mod ERC721 { // External // - // #[external(v0)] - // impl SRC5Impl of ISRC5 { - // fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) - // } - // } - - // #[external(v0)] - // impl SRC5CamelImpl of ISRC5Camel { - // fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { - // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId) - // } - // } - #[external(v0)] impl ERC721MetadataImpl of interface::IERC721Metadata { fn name(self: @ContractState) -> felt252 { @@ -115,8 +139,7 @@ mod ERC721 { #[external(v0)] impl ERC721MetadataCamelOnlyImpl of interface::IERC721MetadataCamelOnly { fn tokenURI(self: @ContractState, tokenId: u256) -> felt252 { - assert(self._exists(tokenId), Errors::INVALID_TOKEN_ID); - self.get_uri(tokenId) + self.token_uri(tokenId) } } @@ -178,7 +201,8 @@ mod ERC721 { assert( self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED ); - self._safe_transfer(from, to, token_id, data); + // TODO: move to real safe transfer when support of SRC6 is enabled + self.transfer_from(from, to, token_id); } } @@ -312,11 +336,8 @@ mod ERC721 { fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { let meta = ERC721Meta { token: get_contract_address(), name, symbol, base_uri }; set!(self.world(), (meta)); - // let mut unsafe_state = src5::SRC5::unsafe_new_contract_state(); - // src5::SRC5::InternalImpl::register_interface(ref unsafe_state, interface::IERC721_ID); - // src5::SRC5::InternalImpl::register_interface( - // ref unsafe_state, interface::IERC721_METADATA_ID - // ); + self.src5.register_interface(interface::IERC721_ID); + self.src5.register_interface(interface::IERC721_METADATA_ID); } fn _owner_of(self: @ContractState, token_id: u256) -> ContractAddress { @@ -397,47 +418,5 @@ mod ERC721 { self.emit_event(Transfer { from: owner, to: Zeroable::zero(), token_id }); } - - fn _safe_mint( - ref self: ContractState, to: ContractAddress, token_id: u256, data: Span - ) { - self._mint(to, token_id); - // assert( - // _check_on_erc721_received(Zeroable::zero(), to, token_id, data), - // Errors::SAFE_MINT_FAILED - // ); - } - - fn _safe_transfer( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ) { - self._transfer(from, to, token_id); - // assert( - // _check_on_erc721_received(from, to, token_id, data), Errors::SAFE_TRANSFER_FAILED - // ); - } - // fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { - // assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - // self._token_uri.write(token_id, token_uri) - // } } - -//#[internal] -// fn _check_on_erc721_received( -// from: ContractAddress, to: ContractAddress, token_id: u256, data: Span -// ) -> bool { -// if (DualCaseSRC5 { contract_address: to } -// .supports_interface(interface::IERC721_RECEIVER_ID)) { -// DualCaseERC721Receiver { contract_address: to } -// .on_erc721_received( -// get_caller_address(), from, token_id, data -// ) == interface::IERC721_RECEIVER_ID -// } else { -// DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) -// } -// } } diff --git a/presets/token/src/token/erc721/models.cairo b/presets/src/erc721/models.cairo similarity index 97% rename from presets/token/src/token/erc721/models.cairo rename to presets/src/erc721/models.cairo index 1bc4bcad..eec7ec9c 100644 --- a/presets/token/src/token/erc721/models.cairo +++ b/presets/src/erc721/models.cairo @@ -1,3 +1,5 @@ +// Starknet imports + use starknet::ContractAddress; #[derive(Model, Copy, Drop, Serde)] diff --git a/presets/token/src/tests/erc721_tests.cairo b/presets/src/erc721/tests.cairo similarity index 98% rename from presets/token/src/tests/erc721_tests.cairo rename to presets/src/erc721/tests.cairo index 415db0ff..cdeef6be 100644 --- a/presets/token/src/tests/erc721_tests.cairo +++ b/presets/src/erc721/tests.cairo @@ -1,30 +1,41 @@ +// Core imports + use integer::u256; use integer::u256_from_felt252; -use dojo_erc::tests::utils; -use dojo_erc::tests::constants::{ - ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID -}; +use zeroable::Zeroable; +use debug::PrintTrait; + +// Starknet imports -use dojo_erc::token::erc721::ERC721::ERC721Impl; -use dojo_erc::token::erc721::ERC721::ERC721CamelOnlyImpl; -use dojo_erc::token::erc721::ERC721::ERC721MetadataImpl; -use dojo_erc::token::erc721::ERC721::InternalImpl; -use dojo_erc::token::erc721::ERC721::WorldInteractionsImpl; -use dojo_erc::token::erc721::ERC721::{Approval, ApprovalForAll, Transfer}; -use dojo_erc::token::erc721::ERC721; use starknet::ContractAddress; use starknet::contract_address_const; use starknet::testing; -use zeroable::Zeroable; + +// Dojo imports + use dojo::test_utils::spawn_test_world; use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; -use dojo_erc::token::erc721::models::{ +// External imports + +// Internal imports + +use presets::tests::utils; +use presets::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID +}; +use presets::erc721::ERC721::ERC721Impl; +use presets::erc721::ERC721::ERC721CamelOnlyImpl; +use presets::erc721::ERC721::ERC721MetadataImpl; +use presets::erc721::ERC721::InternalImpl; +use presets::erc721::ERC721::WorldInteractionsImpl; +use presets::erc721::ERC721::{Approval, ApprovalForAll, Transfer}; +use presets::erc721::ERC721; +use presets::erc721::models::{ ERC721Meta, erc_721_meta, ERC721OperatorApproval, erc_721_operator_approval, ERC721Owner, erc_721_owner, ERC721Balance, erc_721_balance, ERC721TokenApproval, erc_721_token_approval }; -use dojo_erc::token::erc721::ERC721::_worldContractMemberStateTrait; -use debug::PrintTrait; +use presets::erc721::ERC721::_worldContractMemberStateTrait; // // Setup diff --git a/presets/src/lib.cairo b/presets/src/lib.cairo new file mode 100644 index 00000000..95234237 --- /dev/null +++ b/presets/src/lib.cairo @@ -0,0 +1,13 @@ +mod erc721 { + mod models; + mod erc721; + use erc721::ERC721; + #[cfg(test)] + mod tests; +} + +#[cfg(test)] +mod tests { + mod constants; + mod utils; +} \ No newline at end of file diff --git a/presets/token/src/tests/constants.cairo b/presets/src/tests/constants.cairo similarity index 100% rename from presets/token/src/tests/constants.cairo rename to presets/src/tests/constants.cairo diff --git a/presets/token/src/tests/test_erc1155.cairo b/presets/src/tests/test_erc1155.cairo similarity index 100% rename from presets/token/src/tests/test_erc1155.cairo rename to presets/src/tests/test_erc1155.cairo diff --git a/presets/token/src/tests/test_erc721.cairo b/presets/src/tests/test_erc721.cairo similarity index 100% rename from presets/token/src/tests/test_erc721.cairo rename to presets/src/tests/test_erc721.cairo diff --git a/presets/token/src/tests/utils.cairo b/presets/src/tests/utils.cairo similarity index 100% rename from presets/token/src/tests/utils.cairo rename to presets/src/tests/utils.cairo diff --git a/presets/token/Scarb.lock b/presets/token/Scarb.lock deleted file mode 100644 index 8adc6f7f..00000000 --- a/presets/token/Scarb.lock +++ /dev/null @@ -1,20 +0,0 @@ -# Code generated by scarb DO NOT EDIT. -version = 1 - -[[package]] -name = "dojo" -version = "0.3.3" -dependencies = [ - "dojo_plugin", -] - -[[package]] -name = "dojo_erc" -version = "0.3.3" -dependencies = [ - "dojo", -] - -[[package]] -name = "dojo_plugin" -version = "0.3.3" diff --git a/presets/token/Scarb.toml b/presets/token/Scarb.toml deleted file mode 100644 index ef9edac6..00000000 --- a/presets/token/Scarb.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -cairo-version = "2.3.1" -description = "Implementations of ERC standards for the Dojo framework" -name = "dojo_erc" -version = "0.3.3" - -[cairo] -sierra-replace-ids = true - -[lib] - -[dependencies] -dojo = { path = "../dojo-core" } - -[scripts] -build = "sozo build" -migrate = "sozo migrate" -test = "sozo test" - -# Generate targets/manifest with sozo -[[target.dojo]] - - -[tool.dojo.env] -# Katana -rpc_url = "http://localhost:5050" -account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" -private_key = "0x1800000000300000180000000000030000000000003006001800006600" -world_address = "0x4cb5561a3a2a19d14f67f71a9da59c9194e2bbb44e1774260a111094fbd8f39" \ No newline at end of file diff --git a/presets/token/src/lib.cairo b/presets/token/src/lib.cairo deleted file mode 100644 index 537938a7..00000000 --- a/presets/token/src/lib.cairo +++ /dev/null @@ -1,17 +0,0 @@ -mod token { - mod erc20; - mod erc20_models; - mod erc721; - mod erc1155; - -} - -#[cfg(test)] -mod tests { - mod constants; - mod utils; - - mod erc20_tests; - mod erc721_tests; - mod erc1155_tests; -} diff --git a/presets/token/src/token/erc721.cairo b/presets/token/src/token/erc721.cairo deleted file mode 100644 index 15b96462..00000000 --- a/presets/token/src/token/erc721.cairo +++ /dev/null @@ -1,5 +0,0 @@ -mod erc721; -mod models; -mod interface; - -use erc721::ERC721; diff --git a/presets/token/src/token/erc721/interface.cairo b/presets/token/src/token/erc721/interface.cairo deleted file mode 100644 index 730ad2e0..00000000 --- a/presets/token/src/token/erc721/interface.cairo +++ /dev/null @@ -1,60 +0,0 @@ -use starknet::ContractAddress; - -const IERC721_ID: felt252 = 0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943; -const IERC721_METADATA_ID: felt252 = - 0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd; -const IERC721_RECEIVER_ID: felt252 = - 0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc; - -#[starknet::interface] -trait IERC721 { - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn owner_of(self: @TState, token_id: u256) -> ContractAddress; - fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); - fn safe_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); - fn approve(ref self: TState, to: ContractAddress, token_id: u256); - fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); - fn get_approved(self: @TState, token_id: u256) -> ContractAddress; - fn is_approved_for_all( - self: @TState, owner: ContractAddress, operator: ContractAddress - ) -> bool; -} - -#[starknet::interface] -trait IERC721CamelOnly { - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn ownerOf(self: @TState, tokenId: u256) -> ContractAddress; - fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256); - fn safeTransferFrom( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - data: Span - ); - fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); - fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; - fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; -} - -// -// IERC721Metadata -// - -#[starknet::interface] -trait IERC721Metadata { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn token_uri(self: @TState, token_id: u256) -> felt252; -} - -#[starknet::interface] -trait IERC721MetadataCamelOnly { - fn tokenURI(self: @TState, tokenId: u256) -> felt252; -} From 06c85bef7d04d20e3942e44d3769ba3b3c7b7e5a Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 5 Nov 2023 17:28:44 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=91=B7=20Rename=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61e4d579..4227c071 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Check +name: Origami CI on: [push, pull_request] From af37f0284d26dbb357c86201db8047266adfe0d1 Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 5 Nov 2023 17:30:40 +0100 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=92=9A=20Fix=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Scarb.lock | 6 +++--- examples/market/Scarb.toml | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Scarb.lock b/Scarb.lock index 4df3bf57..052220f8 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -22,15 +22,15 @@ dependencies = [ [[package]] name = "dojo" -version = "0.3.3" -source = "git+https://github.com/dojoengine/dojo.git?tag=v0.3.3#3c9f109e667ca5d12739e6553fdb8261378f4ecf" +version = "0.3.4" +source = "git+https://github.com/dojoengine/dojo.git?tag=v0.3.4#a3140d88b08b79c5ff2261c1db81bafe80b5cc91" dependencies = [ "dojo_plugin", ] [[package]] name = "dojo_plugin" -version = "0.3.3" +version = "0.3.4" [[package]] name = "market" diff --git a/examples/market/Scarb.toml b/examples/market/Scarb.toml index 2b89bdbe..5fc2f482 100644 --- a/examples/market/Scarb.toml +++ b/examples/market/Scarb.toml @@ -6,6 +6,4 @@ homepage = "https://github.com/dojoengine/origami/tree/examples/market" [dependencies] cubit = { git = "https://github.com/influenceth/cubit", rev = "b459053" } -dojo.workspace = true - -[[target.dojo]] \ No newline at end of file +dojo.workspace = true \ No newline at end of file