diff --git a/src/Jobs/ClaimBeam.php b/src/Jobs/ClaimBeam.php index 7d9ddec..beb7f86 100644 --- a/src/Jobs/ClaimBeam.php +++ b/src/Jobs/ClaimBeam.php @@ -8,6 +8,7 @@ use Enjin\Platform\Beam\Models\BeamScan; use Enjin\Platform\Beam\Services\BatchService; use Enjin\Platform\Beam\Services\BeamService; +use Enjin\Platform\Models\Collection; use Enjin\Platform\Services\Database\WalletService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Cache\LockTimeoutException; @@ -101,9 +102,13 @@ public function failed(Throwable $exception): void */ protected function claimQuery(array $data): Builder { + $collection = Collection::where('collection_chain_id', Arr::get($data, 'beam.collection_chain_id')) + ->first(['id', 'collection_chain_id', 'is_frozen']); + return BeamClaim::where('beam_id', $data['beam']['id']) ->claimable() ->when($data['code'], fn ($query) => $query->withSingleUseCode($data['code'])) + ->when($collection?->is_frozen, fn ($query) => $query->where('type', BeamType::MINT_ON_DEMAND->name)) ->unless($data['code'], fn ($query) => $query->inRandomOrder()); } diff --git a/src/Listeners/PauseBeam.php b/src/Listeners/PauseBeam.php index da77db3..98bf43e 100644 --- a/src/Listeners/PauseBeam.php +++ b/src/Listeners/PauseBeam.php @@ -2,7 +2,9 @@ namespace Enjin\Platform\Beam\Listeners; +use Enjin\Platform\Beam\Enums\BeamType; use Enjin\Platform\Beam\Models\Beam; +use Enjin\Platform\Beam\Models\BeamClaim; use Enjin\Platform\Beam\Services\BeamService; use Enjin\Platform\Events\PlatformBroadcastEvent; use Illuminate\Contracts\Queue\ShouldQueue; @@ -15,15 +17,22 @@ class PauseBeam implements ShouldQueue */ public function handle(PlatformBroadcastEvent $event): void { - Beam::where('collection_chain_id', $event->broadcastData['collectionId']) - ->get() - ->each(function ($beam) { - $beam->update(['flags_mask' => BeamService::getFlagsValue( - collect(array_merge($beam->flags ?? [], ['PAUSED'])) - ->unique() - ->map(fn ($flag) => ['flag' => $flag, 'enabled' => true])->all() - )]); - Log::info("Pausing beam {$beam->code} cause the collection {$beam->collection_chain_id} was paused."); - }); + foreach (Beam::where('collection_chain_id', $event->broadcastData['collectionId'])->get() as $beam) { + if (BeamClaim::claimable() + ->where('type', BeamType::MINT_ON_DEMAND->name) + ->where('beam_id', $beam->id) + ->exists() + ) { + continue; + } + + $beam->update(['flags_mask' => BeamService::getFlagsValue( + collect(array_merge($beam->flags ?? [], ['PAUSED'])) + ->unique() + ->map(fn ($flag) => ['flag' => $flag, 'enabled' => true])->all() + )]); + + Log::info("Pausing beam {$beam->code} cause the collection {$beam->collection_chain_id} was paused."); + } } } diff --git a/src/Rules/CanClaim.php b/src/Rules/CanClaim.php index e9284fa..bdac486 100644 --- a/src/Rules/CanClaim.php +++ b/src/Rules/CanClaim.php @@ -3,6 +3,9 @@ namespace Enjin\Platform\Beam\Rules; use Closure; +use Enjin\Platform\Beam\Enums\BeamType; +use Enjin\Platform\Beam\Models\Beam; +use Enjin\Platform\Beam\Models\BeamClaim; use Enjin\Platform\Beam\Services\BeamService; use Enjin\Platform\Rules\Traits\HasDataAwareRule; use Illuminate\Contracts\Validation\DataAwareRule; @@ -38,7 +41,17 @@ public function validate(string $attribute, mixed $value, Closure $fail): void return; } - $passes = ((int) Cache::get(BeamService::key($value), BeamService::claimsCountResolver($value))) > 0; + $remaining = (int) Cache::get(BeamService::key($value), BeamService::claimsCountResolver($value)); + if ($beam = Beam::where('code', $value)->with('collection')->first()) { + if ($beam->collection?->is_frozen) { + $remaining = $remaining - BeamClaim::claimable() + ->where('beam_id', $beam->id) + ->where('type', BeamType::TRANSFER_TOKEN->name) + ->count(); + } + } + + $passes = $remaining > 0; if (! $passes) { $fail('enjin-platform-beam::validation.can_claim')->translate(); diff --git a/tests/Feature/GraphQL/Mutations/ClaimBeamTest.php b/tests/Feature/GraphQL/Mutations/ClaimBeamTest.php index b49a2f5..287660b 100644 --- a/tests/Feature/GraphQL/Mutations/ClaimBeamTest.php +++ b/tests/Feature/GraphQL/Mutations/ClaimBeamTest.php @@ -9,6 +9,7 @@ use Enjin\Platform\Beam\Enums\BeamType; use Enjin\Platform\Beam\Events\BeamClaimPending; use Enjin\Platform\Beam\Jobs\ClaimBeam; +use Enjin\Platform\Beam\Listeners\PauseBeam; use Enjin\Platform\Beam\Models\BeamClaim; use Enjin\Platform\Beam\Rules\PassesClaimConditions; use Enjin\Platform\Beam\Services\BatchService; @@ -16,11 +17,14 @@ use Enjin\Platform\Beam\Tests\Feature\Traits\CreateBeamData; use Enjin\Platform\Beam\Tests\Feature\Traits\SeedBeamData; use Enjin\Platform\Enums\Substrate\CryptoSignatureType; +use Enjin\Platform\Events\Substrate\MultiTokens\CollectionFrozen; use Enjin\Platform\Providers\Faker\SubstrateProvider; use Enjin\Platform\Services\Database\WalletService; +use Enjin\Platform\Services\Processor\Substrate\Codec\Polkadart\Events\MultiTokens\Frozen; use Enjin\Platform\Support\Account; use Enjin\Platform\Support\BitMask; use Enjin\Platform\Support\SS58Address; +use Faker\Generator; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Queue; @@ -456,6 +460,104 @@ public function signMessage(CryptoSignatureType $type, mixed $keypair, string $m return $signature; } + public function test_it_can_claim_mint_on_demand_with_frozen_collection() + { + $this->seedBeam(1, false, BeamType::MINT_ON_DEMAND); + $this->collection->update(['is_frozen' => true]); + $event = Frozen::fromChain([ + 'phase' => ['ApplyExtrinsic' => 1], + 'event' => [ + 'MultiTokens' => [ + 'Frozen' => [ + 'FreezeOf' => [ + 'collection_id' => $this->collection->collection_chain_id, + 'freeze_type' => ['Collection' => []], + ], + ], + ], + ], + ]); + resolve(PauseBeam::class)->handle(new CollectionFrozen($event)); + $this->assertFalse($this->beam->refresh()->hasFlag(BeamFlag::PAUSED)); + + $response = $this->graphql($this->method, [ + 'code' => $this->beam->code, + 'account' => app(Generator::class)->public_key(), + 'signature' => '', + ]); + $this->assertTrue($response); + } + + public function test_it_cannot_claim_transfer_token_with_frozen_collection() + { + $this->seedBeam(1, false, BeamType::TRANSFER_TOKEN); + $this->collection->update(['is_frozen' => true]); + $event = Frozen::fromChain([ + 'phase' => ['ApplyExtrinsic' => 1], + 'event' => [ + 'MultiTokens' => [ + 'Frozen' => [ + 'FreezeOf' => [ + 'collection_id' => $this->collection->collection_chain_id, + 'freeze_type' => ['Collection' => []], + ], + ], + ], + ], + ]); + resolve(PauseBeam::class)->handle(new CollectionFrozen($event)); + $this->assertTrue($this->beam->refresh()->hasFlag(BeamFlag::PAUSED)); + + $response = $this->graphql($this->method, [ + 'code' => $this->beam->code, + 'account' => app(Generator::class)->public_key(), + 'signature' => '', + ], true); + $this->assertArraySubset( + $response['error'], + ['code' => ['There are no more claims available.', 'The beam is paused.']] + ); + } + + public function test_it_can_claim_with_mint_on_demand_and_transfer_token_with_frozen_collection() + { + $this->seedBeam(2, false, BeamType::MINT_ON_DEMAND); + $this->claims->first()->update(['type' => BeamType::TRANSFER_TOKEN->name]); + $this->collection->update(['is_frozen' => true]); + $event = Frozen::fromChain([ + 'phase' => ['ApplyExtrinsic' => 1], + 'event' => [ + 'MultiTokens' => [ + 'Frozen' => [ + 'FreezeOf' => [ + 'collection_id' => $this->collection->collection_chain_id, + 'freeze_type' => ['Collection' => []], + ], + ], + ], + ], + ]); + resolve(PauseBeam::class)->handle(new CollectionFrozen($event)); + $this->assertFalse($this->beam->refresh()->hasFlag(BeamFlag::PAUSED)); + + $response = $this->graphql($this->method, [ + 'code' => $this->beam->code, + 'account' => app(Generator::class)->public_key(), + 'signature' => '', + ]); + $this->assertTrue($response); + + $response = $this->graphql($this->method, [ + 'code' => $this->beam->code, + 'account' => app(Generator::class)->public_key(), + 'signature' => '', + ], true); + $this->assertArraySubset( + $response['error'], + ['code' => ['There are no more claims available.']] + ); + } + /** * Get keypair. */