From b3c2e9cd1234592bbe8ab6075ae59f43ffa9f895 Mon Sep 17 00:00:00 2001 From: Marco Martinez Date: Mon, 14 Nov 2022 10:59:52 -0700 Subject: [PATCH] fix find by cm logic, fix some unit tests (#82) --- .../metaplex/lib/modules/nfts/NftClient.kt | 7 +- ...tsByCandyMachineOnChainOperationHandler.kt | 23 ++-- .../candymachine/CandyMachineClientTests.kt | 20 +++- ...NftsByCandyMachineOperationHandlerTests.kt | 113 ++++++++++++++++++ ...NftsByOwnerOnChainOperationHandlerTests.kt | 25 ++++ 5 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOperationHandlerTests.kt diff --git a/lib/src/main/java/com/metaplex/lib/modules/nfts/NftClient.kt b/lib/src/main/java/com/metaplex/lib/modules/nfts/NftClient.kt index 39afdc2..2751d39 100644 --- a/lib/src/main/java/com/metaplex/lib/modules/nfts/NftClient.kt +++ b/lib/src/main/java/com/metaplex/lib/modules/nfts/NftClient.kt @@ -44,7 +44,7 @@ class NftClient(private val connection: Connection, val signer: IdentityDriver, FindNftsByCreatorOnChainOperationHandler(connection, dispatcher) .handle(FindNftsByCreatorInput(creator, position)) - suspend fun findAllByCandyMachine(candyMachine: PublicKey, version: UInt? = 2U): Result> = + suspend fun findAllByCandyMachine(candyMachine: PublicKey, version: Int? = 2): Result> = FindNftsByCandyMachineOnChainOperationHandler(connection, dispatcher) .handle(FindNftsByCandyMachineInput(candyMachine, version)) //endregion @@ -65,4 +65,9 @@ class NftClient(private val connection: Connection, val signer: IdentityDriver, return FindNftByMintOnChainOperationHandler(connection, dispatcher) .handle(newMintAccount.publicKey) } + + @Deprecated("Deprecated, please use the signed integer version instead", + replaceWith = ReplaceWith("findAllByCandyMachine(candyMachine, version)")) + suspend fun findAllByCandyMachine(candyMachine: PublicKey, version: UInt? = 2U) + : Result> = findAllByCandyMachine(candyMachine, version?.toInt()) } \ No newline at end of file diff --git a/lib/src/main/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOnChainOperationHandler.kt b/lib/src/main/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOnChainOperationHandler.kt index 8d797b2..dbc20df 100644 --- a/lib/src/main/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOnChainOperationHandler.kt +++ b/lib/src/main/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOnChainOperationHandler.kt @@ -2,6 +2,8 @@ package com.metaplex.lib.modules.nfts.operations import com.metaplex.lib.Metaplex import com.metaplex.lib.drivers.solana.Connection +import com.metaplex.lib.modules.candymachines.models.CandyMachine +import com.metaplex.lib.modules.candymachinesv2.models.CandyMachineV2 import com.metaplex.lib.modules.nfts.models.NFT import com.metaplex.lib.shared.* import com.solana.core.PublicKey @@ -10,10 +12,9 @@ import kotlinx.coroutines.Dispatchers data class FindNftsByCandyMachineInput( val candyMachine : PublicKey, - val version : UInt?, + val version : Int?, ) -val candyMachineId = PublicKey("cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ") class FindNftsByCandyMachineOnChainOperationHandler(override val connection: Connection, override val dispatcher: CoroutineDispatcher = Dispatchers.IO) : OperationHandler> { @@ -21,16 +22,16 @@ class FindNftsByCandyMachineOnChainOperationHandler(override val connection: Con constructor(metaplex: Metaplex) : this(metaplex.connection) override suspend fun handle(input: FindNftsByCandyMachineInput): Result> { - val cmAddress = if((input.version ?: 2) == 2){ - val pdaSeeds = listOf( + val cmAddress = if(input.version == 3){ + PublicKey.findProgramAddress(listOf( "candy_machine".toByteArray(), - candyMachineId.toByteArray(), - ) - val pdaAddres = PublicKey.findProgramAddress( - pdaSeeds, - candyMachineId - ) - pdaAddres.address + input.candyMachine.toByteArray(), + ), PublicKey(CandyMachine.PROGRAM_ADDRESS)).address + } else if(input.version == 2){ + PublicKey.findProgramAddress(listOf( + CandyMachineV2.PROGRAM_NAME.toByteArray(), + input.candyMachine.toByteArray() + ), PublicKey(CandyMachineV2.PROGRAM_ADDRESS)).address } else input.candyMachine return FindNftsByCreatorOnChainOperationHandler(connection, dispatcher) diff --git a/lib/src/test/java/com/metaplex/lib/modules/candymachine/CandyMachineClientTests.kt b/lib/src/test/java/com/metaplex/lib/modules/candymachine/CandyMachineClientTests.kt index 4f0f587..3a226d0 100644 --- a/lib/src/test/java/com/metaplex/lib/modules/candymachine/CandyMachineClientTests.kt +++ b/lib/src/test/java/com/metaplex/lib/modules/candymachine/CandyMachineClientTests.kt @@ -278,15 +278,17 @@ class CandyMachineClientTests { // when connection.airdrop(signer.publicKey, 1f) val nft = createCollectionNft(connection, identityDriver).getOrThrow() - val mintResult = client.create(250, 1, nft.mint, signer.publicKey).map { + val mintResult = client.create(250, 1, nft.mint, + signer.publicKey, withoutCandyGuard = true).map { client.insertItems(it, listOf( CandyMachineItem("My NFT", "https://example.com/mynft"), )) - client.mintNft(it)//.getOrThrow() + client.mintNft(it).getOrThrow() }.getOrThrow() // then Assert.assertNotNull(mintResult) + Assert.assertEquals(nft.mint, mintResult.collection?.key) } //region CANDY GUARDS @@ -452,15 +454,18 @@ class CandyMachineClientTests { // when connection.airdrop(signer.publicKey, 10f) val nft = createCollectionNft(connection, identityDriver).getOrThrow() - val candyMachine = client.create(333, 5000, nft.mint, signer.publicKey).getOrThrow() + val candyMachine = client.create(333, 5000, nft.mint, + signer.publicKey, withoutCandyGuard = true).getOrThrow() val candyGuard = client.createCandyGuard(listOf(), mapOf()).getOrThrow() client.wrapCandyGuard(candyGuard, candyMachine.address) - val finalCandyMachine = client.refresh(candyMachine) + val finalCandyMachine = client.refresh(candyMachine).getOrThrow() //then Assert.assertNotNull(finalCandyMachine) + Assert.assertEquals(candyGuard.authority, finalCandyMachine.authority) + Assert.assertEquals(CandyGuard.pda(candyGuard.base).address, finalCandyMachine.mintAuthority) } @Test @@ -475,15 +480,18 @@ class CandyMachineClientTests { // when connection.airdrop(signer.publicKey, 10f) val nft = createCollectionNft(connection, identityDriver).getOrThrow() - val candyMachine = client.create(333, 5000, nft.mint, signer.publicKey, authority.publicKey).getOrThrow() + val candyMachine = client.create(333, 5000, nft.mint, + signer.publicKey, authority.publicKey, withoutCandyGuard = true).getOrThrow() val candyGuard = client.createCandyGuard(listOf(), mapOf(), authority.publicKey).getOrThrow() client.wrapCandyGuard(candyGuard, candyMachine.address, authority) - val finalCandyMachine = client.refresh(candyMachine) + val finalCandyMachine = client.refresh(candyMachine).getOrThrow() //then Assert.assertNotNull(finalCandyMachine) + Assert.assertEquals(candyGuard.authority, finalCandyMachine.authority) + Assert.assertEquals(CandyGuard.pda(candyGuard.base).address, finalCandyMachine.mintAuthority) } //endregion //endregion diff --git a/lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOperationHandlerTests.kt b/lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOperationHandlerTests.kt new file mode 100644 index 0000000..0d54101 --- /dev/null +++ b/lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByCandyMachineOperationHandlerTests.kt @@ -0,0 +1,113 @@ +/* + * FindNftsByCandyMachineOperationHnadlerTests + * Metaplex + * + * Created by Funkatronics on 11/14/2022 + */ + +package com.metaplex.lib.modules.nfts.operations + +import com.metaplex.lib.MetaplexTestUtils +import com.metaplex.lib.drivers.solana.SolanaConnectionDriver +import com.metaplex.lib.generateConnectionDriver +import com.metaplex.lib.generateMetaplexInstance +import com.metaplex.lib.modules.candymachines.models.CandyMachineItem +import com.metaplex.lib.modules.nfts.models.Metadata +import com.metaplex.lib.modules.nfts.models.NFT +import com.metaplex.lib.shared.OperationError +import com.metaplex.mock.driver.rpc.MockErrorRpcDriver +import com.solana.core.HotAccount +import com.solana.core.PublicKey +import com.util.airdrop +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Test + +class FindNftsByCandyMachineOperationHandlerTests { + + @Test + fun testFindAllByOwnerHandlesAndReturnsError() = runTest { + // given + val expectedErrorMessage = "An Error Occurred" + val owner = PublicKey(ByteArray(PublicKey.PUBLIC_KEY_LENGTH)) + val connection = SolanaConnectionDriver(MockErrorRpcDriver(expectedErrorMessage)) + + // when + val result = FindNftsByCandyMachineOnChainOperationHandler(connection) + .handle(FindNftsByCandyMachineInput(owner, 3)) + + // This is really bad, OperationError needs to be refactored. An exception should not wrap + // another exception into a custom property. Getting the actual error out is so gross: + val actualExceptionUnwrapped = + (result.exceptionOrNull() as? OperationError.GetFindNftsByCreatorOperation)?.exception + + // then + Assert.assertTrue(result.isFailure) + Assert.assertEquals(expectedErrorMessage, actualExceptionUnwrapped?.message) + } + //endregion + + @Test + fun testFindNftsByCandyMachineV2OnChainOperation() = runTest { + // given + val owner = HotAccount() + val connection = MetaplexTestUtils.generateConnectionDriver() + val metaplex = MetaplexTestUtils.generateMetaplexInstance(owner, connection) + + // when + connection.airdrop(owner.publicKey, 1f) + + val candyMachine = + metaplex.candyMachinesV2.create(1, 250, 1).getOrThrow() + + val createdNft = metaplex.candyMachinesV2.mintNft(candyMachine).getOrThrow() + + val ownerNfts: List = FindNftsByCandyMachineOnChainOperationHandler(connection) + .handle(FindNftsByCandyMachineInput(candyMachine.address, 2)).getOrThrow() + + // then + Assert.assertNotNull(ownerNfts.first()) + Assert.assertTrue(ownerNfts.size == 1) + Assert.assertEquals(createdNft.mint, ownerNfts.first()?.mint) + Assert.assertEquals(createdNft.metadataAccount.mint, + ownerNfts.first()?.metadataAccount?.mint) + Assert.assertEquals(createdNft.metadataAccount.update_authority, + ownerNfts.first()?.metadataAccount?.update_authority) + } + + @Test + fun testFindNftsByCandyMachineOnChainOperation() = runTest { + // given + val owner = HotAccount() + val connection = MetaplexTestUtils.generateConnectionDriver() + val metaplex = MetaplexTestUtils.generateMetaplexInstance(owner, connection) + + // when + connection.airdrop(owner.publicKey, 1f) + + val collection = metaplex.nft.create( + Metadata("My NFT", uri = "http://example.com/sd8756fsuyvvbf37684", + sellerFeeBasisPoints = 250), true + ).getOrThrow() + + val candyMachine = metaplex.candyMachines.create(250, 1, + collection.mint, collection.updateAuthority, withoutCandyGuard = true).getOrThrow() + + metaplex.candyMachines.insertItems(candyMachine, + listOf(CandyMachineItem("Degen #1", "https://example.com/degen/1"))) + + val createdNft = metaplex.candyMachines.mintNft(candyMachine).getOrThrow() + + val ownerNfts: List = FindNftsByCandyMachineOnChainOperationHandler(connection) + .handle(FindNftsByCandyMachineInput(candyMachine.address, 3)).getOrThrow() + + // then + Assert.assertNotNull(ownerNfts.first()) + Assert.assertTrue(ownerNfts.size == 1) + Assert.assertEquals(createdNft.mint, ownerNfts.first()?.mint) + Assert.assertEquals(createdNft.metadataAccount.mint, + ownerNfts.first()?.metadataAccount?.mint) + Assert.assertEquals(createdNft.metadataAccount.update_authority, + ownerNfts.first()?.metadataAccount?.update_authority) + } +} \ No newline at end of file diff --git a/lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByOwnerOnChainOperationHandlerTests.kt b/lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByOwnerOnChainOperationHandlerTests.kt index 46e040e..4b3cb22 100644 --- a/lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByOwnerOnChainOperationHandlerTests.kt +++ b/lib/src/test/java/com/metaplex/lib/modules/nfts/operations/FindNftsByOwnerOnChainOperationHandlerTests.kt @@ -3,9 +3,13 @@ package com.metaplex.lib.modules.nfts.operations import com.metaplex.lib.* +import com.metaplex.lib.drivers.solana.SolanaConnectionDriver import com.metaplex.lib.modules.nfts.models.Metadata import com.metaplex.lib.modules.nfts.models.NFT +import com.metaplex.lib.shared.OperationError +import com.metaplex.mock.driver.rpc.MockErrorRpcDriver import com.solana.core.HotAccount +import com.solana.core.PublicKey import com.util.airdrop import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -14,6 +18,27 @@ import org.junit.Test class FindNftsByOwnerOnChainOperationHandlerTests { + @Test + fun testFindAllByOwnerHandlesAndReturnsError() = runTest { + // given + val expectedErrorMessage = "An Error Occurred" + val owner = PublicKey(ByteArray(PublicKey.PUBLIC_KEY_LENGTH)) + val connection = SolanaConnectionDriver(MockErrorRpcDriver(expectedErrorMessage)) + + // when + val result = FindNftsByOwnerOnChainOperationHandler(connection).handle(owner) + + // This is really bad, OperationError needs to be refactored. An exception should not wrap + // another exception into a custom property. Getting the actual error out is so gross: + val actualExceptionUnwrapped = + (result.exceptionOrNull() as? OperationError.GetFindNftsByOwnerOperation)?.exception + + // then + Assert.assertTrue(result.isFailure) + Assert.assertEquals(expectedErrorMessage, actualExceptionUnwrapped?.message) + } + //endregion + @Test fun testFindNftsByOwnerOnChainOperation() = runTest { // given