From aafaa221228af68bf209156eb55205384f47b881 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Mon, 19 May 2025 23:27:00 +0300 Subject: [PATCH 01/22] test(benches): add nft test --- src/benchmarks/nft/nft.test.spec.ts | 1166 +++++++++++++++++++++++++++ 1 file changed, 1166 insertions(+) create mode 100644 src/benchmarks/nft/nft.test.spec.ts diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts new file mode 100644 index 0000000000..9f8f2d933e --- /dev/null +++ b/src/benchmarks/nft/nft.test.spec.ts @@ -0,0 +1,1166 @@ +// Type imports +import type { + Address, + Slice, + ContractProvider, + Sender, + Builder, + TupleItem, + TupleItemInt, + TupleItemSlice, + TupleItemCell, +} from "@ton/core"; +import type { + SandboxContract, + TreasuryContract, + SendMessageResult +} from "@ton/sandbox"; + +// Value imports +import { + beginCell, + Cell, + toNano, + Dictionary +} from "@ton/core"; +import { Blockchain } from "@ton/sandbox"; + +// NFT Collection imports +import type { + DeployNFT, + GetRoyaltyParams, + GetStaticData, + BatchDeploy, + RoyaltyParams, + InitNFTBody, + ChangeOwner, +} from "./output/collection_NFTCollection"; +import { + storeRoyaltyParams, + loadInitNFTBody, + NFTCollection, +} from "./output/collection_NFTCollection"; + +// NFT Item imports +import type { + Transfer, + NFTData +} from "./output/item_NFTItem"; +import { + storeInitNFTBody, + NFTItem +} from "./output/item_NFTItem"; + +import "@ton/test-utils"; +import { randomInt } from "crypto"; + +import { setStoragePrices } from "@/src/test/utils/gasUtils"; + +/** Operation codes for NFT contract messages */ +const Operations = { + TransferNft: 0x5fcc3d14, + OwnershipAssignment: 0x05138d91, + Excess: 0xd53276db, + GetStaticData: 0x2fcb26a2, + ReportStaticData: 0x8b771735, + GetRoyaltyParams: 0x693d3950, + ReportRoyaltyParams: 0xa8cb00ad, + EditContent: 0x1a0b9d51, + TransferEditorship: 0x1c04412a, + EditorshipAssigned: 0x511a4463, +} as const; + +/** Storage and transaction related constants */ +const Storage = { + /** Minimum amount of TONs required for storage */ + MinTons: 50000000n, + /** Amount of TONs for deployment operations */ + DeployAmount: toNano("0.1"), + /** Amount of TONs for transfer operations */ + TransferAmount: toNano("1"), + /** Amount of TONs for batch deployment */ + BatchDeployAmount: toNano("100"), + /** Amount of TONs for ownership change */ + ChangeOwnerAmount: 100000000n, + /** Amount of TONs for NFT minting */ + NftMintAmount: 10000000n, + /** Forward fee values */ + ForwardFee: { + /** Base forward fee for single message */ + Base: 623605n, + /** Forward fee for double message */ + Double: 729606n, + }, +} as const; + +/** Error codes */ +const ErrorCodes = { + /** Error code for not initialized contract */ + NotInit: 9, + /** Error code for not owner */ + NotOwner: 401, + /** Error code for invalid fees */ + InvalidFees: 402, + /** Error code for incorrect index */ + IncorrectIndex: 402, + /** Error code for invalid data */ + InvalidData: 65535, +} as const; + +/** Test related constants */ +const TestValues = { + /** Default item index used in tests */ + ItemIndex: 100n, + /** Batch operation sizes */ + BatchSize: { + Min: 1n, + Max: 250n, + Default: 50n, + OverLimit: 260n, + Small: 10n, + }, + /** Range for random number generation */ + RandomRange: 1337, + /** Royalty parameters */ + Royalty: { + Nominator: 1n, + Dominator: 100n, + }, + /** Bit sizes for different data types */ + BitSizes: { + Uint1: 1, + Uint8: 8, + Uint16: 16, + Uint32: 32, + Uint64: 64, + }, + /** Additional test values */ + ExtraValues: { + BatchMultiplier: 10n, + } +} as const; + +/** Dictionary type for NFT deployment data */ +export type dictDeployNFT = { + amount: bigint; + initNFTBody: InitNFTBody; +}; + +/** Dictionary value parser for NFT deployment */ +export const dictDeployNFTItem = { + serialize: (src: dictDeployNFT, builder: Builder) => { + builder + .storeCoins(src.amount) + .storeRef( + beginCell().store(storeInitNFTBody(src.initNFTBody)).endCell(), + ); + }, + parse: (src: Slice) => { + return { + amount: src.loadCoins(), + initNFTBody: loadInitNFTBody(src.loadRef().asSlice()), + }; + }, +}; + +const minTonsForStorage = Storage.MinTons; + +/** + * Sends a transfer message to an NFT item contract + * @param itemNFT - The NFT item contract instance + * @param from - The sender of the transfer + * @param value - Amount of TONs to send + * @param newOwner - Address of the new owner + * @param responseDestination - Address to send the response to + * @param forwardAmount - Amount of TONs to forward + * @param forwardPayload - Optional payload to forward + * @returns Promise resolving to the transaction result + */ +const sendTransfer = async ( + itemNFT: SandboxContract, + from: Sender, + value: bigint, + newOwner: Address, + responseDestination: Address | null, + forwardAmount: bigint, + forwardPayload: Slice = beginCell().storeUint(0, 1).asSlice(), +): Promise => { + const msg: Transfer = { + $$type: "Transfer", + queryId: 0n, + newOwner: newOwner, + responseDestination: responseDestination, + customPayload: null, + forwardAmount: forwardAmount, + forwardPayload: forwardPayload, + }; + + return await itemNFT.send(from, { value }, msg); +}; + +/** + * Helper function to load NFT data from a tuple of contract getter results + * @param source - Array of tuple items containing NFT data + * @returns Parsed NFT data object + */ +function loadGetterTupleNFTData(source: TupleItem[]): NFTData { + const _init = (source[0] as TupleItemInt).value; + const _index = (source[1] as TupleItemInt).value; + const _collectionAddress = (source[2] as TupleItemSlice).cell + .asSlice() + .loadAddress(); + const _owner = (source[3] as TupleItemSlice).cell.asSlice().loadAddress(); + const _content = (source[4] as TupleItemCell).cell; + return { + $$type: "NFTData" as const, + init: _init, + itemIndex: _index, + collectionAddress: _collectionAddress, + owner: _owner, + content: _content, + }; +} + +/** + * Extends NFTItem interface with owner getter functionality + */ +declare module "./output/item_NFTItem" { + interface NFTItem { + /** + * Gets the current owner of the NFT + * @param provider - Contract provider instance + * @returns Promise resolving to owner's address or null if not initialized + */ + getOwner(provider: ContractProvider): Promise
; + } +} + +/** + * Extends NFTCollection interface with additional getter functionality + */ +declare module "./output/collection_NFTCollection" { + interface NFTCollection { + /** + * Gets the next available item index for minting + * @param provider - Contract provider instance + * @returns Promise resolving to the next item index + */ + getNextItemIndex(provider: ContractProvider): Promise; + + /** + * Gets the current owner of the collection + * @param provider - Contract provider instance + * @returns Promise resolving to owner's address + */ + getOwner(provider: ContractProvider): Promise
; + } +} + +NFTItem.prototype.getOwner = async function ( + this: NFTItem, + provider: ContractProvider, +): Promise
{ + const res = await this.getGetNftData(provider); + return res.owner; +}; + +describe("NFT Item Contract", () => { + let blockchain: Blockchain; + let itemNFT: SandboxContract; + let owner: SandboxContract; + let notOwner: SandboxContract; + let defaultContent: Cell; + let emptyAddress: Address | null; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + + const config = blockchain.config; + blockchain.setConfig( + setStoragePrices(config, { + unixTimeSince: 0, + bitPricePerSecond: 0n, + cellPricePerSecond: 0n, + masterChainBitPricePerSecond: 0n, + masterChainCellPricePerSecond: 0n, + }), + ); + + owner = await blockchain.treasury("owner"); + notOwner = await blockchain.treasury("notOwner"); + + emptyAddress = null; + defaultContent = Cell.fromBase64("te6ccgEBAQEAAgAAAA=="); // just some content ( doesn't matter ) + + itemNFT = blockchain.openContract( + await NFTItem.fromInit(null, null, owner.address, 0n), + ); + const deployItemMsg: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultContent, + }; + + const deployResult = await itemNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), + ); + + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + deploy: true, + success: true, + }); + }); + + const messageGetStaticData = async ( + sender: SandboxContract, + itemNFT: SandboxContract, + ) => { + const msg: GetStaticData = { + $$type: "GetStaticData", + queryId: 1n, + }; + + const trxResult = await itemNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + return trxResult; + }; + + it("should deploy correctly", async () => { + // checking in beforeEach + }); + + it("should get nft data correctly", async () => { + const staticData = await itemNFT.getGetNftData(); + + expect(staticData.init).toBe(-1n); + expect(staticData.itemIndex).toBe(0n); + expect(staticData.collectionAddress).toEqualAddress(owner.address); + expect(staticData.owner).toEqualAddress(owner.address); + expect(staticData.content).toEqualCell(defaultContent); + }); + + it("should get static data correctly", async () => { + const trxResult = await messageGetStaticData(owner, itemNFT); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }); + + describe("Transfer ownership Fee cases", () => { + let balance: bigint; + let fwdFee: bigint; + + beforeEach(async () => { + balance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + fwdFee = Storage.ForwardFee.Base; + }); + + it("Transfer forward amount too much", async () => { + // NFT should reject transfer if balance lower than forward_amount + message forward fee + minimal storage fee + // Sending message with forward_amount of 1 TON and balance 0.1 TON + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + Storage.TransferAmount, + ); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }); + + it("test transfer storage fee", async () => { + // Now let's try forward_amount exactly equal to balance and fwd_fee 0 + // 1 TON Balance forward_amount:1 TON fwd_fee:0 (just add to transfer value) verifying that minimal storage comes into play + // Should fail with no actions + + // [] and {} just kinds of () for more understandable description + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + fwdFee, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance, + ); // balance + 1ton + fwd - (1ton + balance) = [0] + {fwdFee} and [0] < [minTonsForStorage] + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }); + it("test transfer forward fee 2.0", async () => { + // Let's verify that storage fee was an error trigger by increasing balance by min_storage + // Expect success + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + minTonsForStorage + fwdFee, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance, + ); // balance + 1ton + minTonsForStorage + fwdFee - (1ton + balance) = [minTonsForStorage] + {fwdFee} + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }); + + + it("test transfer forward fee single", async () => { + // If transfer is successfull NFT supposed to send up to 2 messages + // 1)To the owner_address with forward_amount of coins + // 2)To the response_addr with forward_payload if response_addr is not addr_none + // Each of those messages costs fwd_fee + // In this case we test scenario where only single message required to be sent; + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + Storage.ForwardFee.Base, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance - minTonsForStorage, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); // balance + 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }); + + describe("test transfer forward fee double", function () { + beforeEach(() => { + fwdFee = Storage.ForwardFee.Double; + }); + it("should false with only one fwd fee on balance", async () => { + // If transfer is successfull NFT supposed to send up to 2 messages + // 1)To the owner_address with forward_amount of coins + // 2)To the response_addr with forward_payload if response_addr is not addr_none + // Each of those messages costs fwd_fee + // In this case we test scenario where both messages required to be sent but balance has funs only for single message + // To do so resp_dst has be a valid address not equal to addr_none + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + fwdFee, + notOwner.address, + owner.address, + Storage.TransferAmount + balance - Storage.MinTons, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); + + // 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} and {fwdFee} < {2 * fwdFee} + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }); + + // let now check if we have 2 fwdFees on balance + it("should work with 2 fwdFee on balance", async () => { + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + 2n * fwdFee, + notOwner.address, + owner.address, + Storage.TransferAmount + balance - minTonsForStorage, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); + // 1ton + 2 * fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {2 * fwdFee} + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + balance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + expect(balance).toBeLessThan(minTonsForStorage); + }); + }); + // int __test_transfer_success_forward_no_response testing in next test suite + }); + + describe("Transfer Ownership Tests", () => { + it("Test ownership assigned", async () => { + const oldOwner = await itemNFT.getOwner(); + expect(oldOwner).toEqualAddress(owner.address); + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + owner.address, + 1n, + ); + + const newOwner = await itemNFT.getOwner(); + expect(newOwner).toEqualAddress(notOwner.address); + expect(trxRes.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }); + + it("Test transfer ownership without any messages", async () => { + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + 0n, + ); + const newOwner = await itemNFT.getOwner(); + expect(newOwner).toEqualAddress(notOwner.address); + expect(trxRes.transactions).not.toHaveTransaction({ + from: itemNFT.address, + }); + }); + + it("Not owner should not be able to transfer ownership", async () => { + const trxResult = await sendTransfer( + itemNFT, + notOwner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + 0n, + ); + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }); + }); + + describe("NOT INITIALIZED TESTS", () => { + const itemIndex: bigint = 100n; + beforeEach(async () => { + itemNFT = blockchain.openContract( + await NFTItem.fromInit(null, null, owner.address, itemIndex), + ); + const _deployResult = await itemNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + beginCell().asSlice(), + ); + }); + + it("should not get static data", async () => { + const staticData = await itemNFT.getGetNftData(); + expect(staticData.init).toBe(0n); + expect(staticData.collectionAddress).toEqualAddress(owner.address); + expect(staticData.owner).toBeNull(); + expect(staticData.itemIndex).toBe(itemIndex); + expect(staticData.content).toBeNull(); + }); + + it("should not transfer ownership", async () => { + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + owner.address, + 0n, + ); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotInit, + }); + }); + + it("should not get static data message", async () => { + const trxResult = await messageGetStaticData(owner, itemNFT); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotInit, + }); + }); + }); +}); + +NFTCollection.prototype.getNextItemIndex = async function ( + this: NFTCollection, + provider: ContractProvider, +): Promise { + const res = await this.getGetCollectionData(provider); + return res.nextItemIndex; +}; + +NFTCollection.prototype.getOwner = async function ( + this: NFTCollection, + provider: ContractProvider, +): Promise
{ + const res = await this.getGetCollectionData(provider); + return res.owner; +}; + +describe("NFT Collection Contract", () => { + let blockchain: Blockchain; + let collectionNFT: SandboxContract; + let itemNFT: SandboxContract; + + let owner: SandboxContract; + let notOwner: SandboxContract; + + let defaultContent: Cell; + let defaultCommonContent: Cell; + let defaultCollectionContent: Cell; + let defaultNFTContent: Cell; + let royaltyParams: RoyaltyParams; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + owner = await blockchain.treasury("owner"); + notOwner = await blockchain.treasury("notOwner"); + + defaultCommonContent = beginCell().storeStringTail("common").endCell(); + defaultCollectionContent = beginCell() + .storeStringTail("collectioncontent") + .endCell(); + + defaultNFTContent = beginCell().storeStringTail("1.json").endCell(); + + defaultContent = beginCell() + .storeRef(defaultCollectionContent) + .storeRef(defaultCommonContent) + .endCell(); + + royaltyParams = { + $$type: "RoyaltyParams", + nominator: 1n, + dominator: 100n, + owner: owner.address, + }; + + collectionNFT = blockchain.openContract( + await NFTCollection.fromInit( + owner.address, + 0n, + defaultContent, + royaltyParams, + ), + ); + const deployCollectionMsg: GetRoyaltyParams = { + $$type: "GetRoyaltyParams", + queryId: 0n, + }; + + const deployResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + deployCollectionMsg, + ); + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + deploy: true, + success: true, + }); + }); + + it("should deploy correctly", async () => { + // checking in beforeEach + }); + + it("should get static data correctly", async () => { + const staticData = await collectionNFT.getGetCollectionData(); + expect(staticData.owner).toEqualAddress(owner.address); + expect(staticData.nextItemIndex).toBe(0n); + expect(staticData.collectionContent).toEqualCell( + defaultCollectionContent, + ); + }); + + it("should get nft content correctly", async () => { + const content = await collectionNFT.getGetNftContent( + 0n, + defaultContent, + ); + const expectedContent = beginCell() + .storeUint(1, 8) + .storeSlice(defaultCommonContent.asSlice()) + .storeRef(defaultContent) + .endCell(); + expect(content).toEqualCell(expectedContent); + }); + + describe("ROYALTY TESTS", () => { + it("test royalty msg", async () => { + const queryId = randomInt(TestValues.RandomRange) + 1; + + const msg: GetRoyaltyParams = { + $$type: "GetRoyaltyParams", + queryId: BigInt(queryId), + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + + const exceptedMsg: Cell = beginCell() + .storeUint(Operations.ReportRoyaltyParams, 32) + .storeUint(queryId, 64) + .storeUint(royaltyParams.nominator, 16) + .storeUint(royaltyParams.dominator, 16) + .storeAddress(royaltyParams.owner) + .endCell(); + expect(trxResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: owner.address, + body: exceptedMsg, + }); + }); + + it("test royalty getter", async () => { + const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); + expect( + beginCell() + .store(storeRoyaltyParams(currRoyaltyParams)) + .asSlice(), + ).toEqualSlice( + beginCell().store(storeRoyaltyParams(royaltyParams)).asSlice(), + ); + }); + }); + + describe("NFT DEPLOY TESTS", () => { + it("should deploy NFTItem correctly", async () => { + // checking in beforeEach + }); + + /** + * Helper function to deploy an NFT item + * @param itemIndex - Index of the NFT to deploy + * @param collectionNFT - Collection contract instance + * @param sender - Sender of the deployment transaction + * @param owner - Owner of the deployed NFT + * @returns Promise resolving to the NFT item contract and transaction result + */ + const deployNFT = async ( + itemIndex: bigint, + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + ): Promise<[SandboxContract, SendMessageResult]> => { + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + const mintMsg: DeployNFT = { + $$type: "DeployNFT", + queryId: 1n, + itemIndex: itemIndex, + amount: Storage.NftMintAmount, + initNFTBody: beginCell() + .store(storeInitNFTBody(initNFTBody)) + .endCell(), + }; + + const itemNFT = blockchain.openContract( + await NFTItem.fromInit( + null, + null, + collectionNFT.address, + itemIndex, + ), + ); + + const trxResult = await collectionNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + mintMsg, + ); + return [itemNFT, trxResult]; + }; + + it("should mint NFTItem correctly", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + const [itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + ); + const nftData = await itemNFT.getGetNftData(); + + expect(nftData.content).toEqualCell(defaultNFTContent); + expect(nftData.owner).toEqualAddress(owner.address); + expect(nftData.itemIndex).toBe(nextItemIndex); + expect(nftData.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }); + + it("should not mint NFTItem if not owner", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + notOwner, + notOwner, + ); + expect( + (_trx as { transactions: unknown }).transactions, + ).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }); + + it("should not deploy previous nft", async () => { + let nextItemIndex: bigint = await collectionNFT.getNextItemIndex(); + for (let i = 0; i < 10; i++) { + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + ); + nextItemIndex++; + } + const [_itemNFT, _trx] = await deployNFT( + 0n, + collectionNFT, + owner, + owner, + ); + expect( + (_trx as { transactions: unknown }).transactions, + ).toHaveTransaction({ + from: collectionNFT.address, + to: _itemNFT.address, + deploy: false, + success: false, + exitCode: ErrorCodes.InvalidData, + }); + }); + + it("shouldn't mint item itemIndex > nextItemIndex", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex + 1n, + collectionNFT, + owner, + owner, + ); + expect( + (_trx as { transactions: unknown }).transactions, + ).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.IncorrectIndex, + }); + }); + + it("test get nft by itemIndex", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + // deploy new nft to get itemIndex + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + ); + const nftAddress = + await collectionNFT.getGetNftAddressByIndex(nextItemIndex); + const newNFT = blockchain.getContract(nftAddress); + const getData = await (await newNFT).get("get_nft_data"); + const dataNFT = loadGetterTupleNFTData(getData.stack); + expect(dataNFT.itemIndex).toBe(nextItemIndex); + expect(dataNFT.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }); + }); + + describe("BATCH MINT TESTS", () => { + /** + * Helper function to batch mint NFTs + * @param collectionNFT - Collection contract instance + * @param sender - Sender of the batch mint transaction + * @param owner - Owner of the minted NFTs + * @param count - Number of NFTs to mint + * @param extra - Optional extra index to mint + * @returns Promise resolving to the transaction result + */ + const batchMintNFTProcess = async ( + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + count: bigint, + extra: bigint = -1n, + ): Promise => { + const dct = Dictionary.empty( + Dictionary.Keys.BigUint(64), + dictDeployNFTItem, + ); + let i: bigint = 0n; + + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + while (i < count) { + dct.set(i, { + amount: Storage.NftMintAmount, + initNFTBody: initNFTBody, + }); + i += 1n; + } + + if (extra != -1n) { + dct.set(extra, { + amount: Storage.NftMintAmount, + initNFTBody: initNFTBody, + }); + } + + const batchMintNFT: BatchDeploy = { + $$type: "BatchDeploy", + queryId: 0n, + deployList: beginCell().storeDictDirect(dct).endCell(), + }; + + return await collectionNFT.send( + sender.getSender(), + { value: Storage.BatchDeployAmount * (count + TestValues.ExtraValues.BatchMultiplier) }, + batchMintNFT, + ); + }; + beforeEach(async () => {}); + + it.skip("test max batch mint", async () => { + let L = 1n; + let R = 1000n; + while (R - L > 1) { + const M = (L + R) / 2n; + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + M, + ); + try { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + L = M; + } catch { + R = M; + } + } + console.log("maximum batch amount is", L); + }); + + it("Should batch mint correctly", async () => { + const count = 50n; + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + count, + ); + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + itemNFT = blockchain.openContract( + await NFTItem.fromInit( + null, + null, + collectionNFT.address, + count - 1n, + ), + ); + + // it was deployed, that's why we can get it + expect(await itemNFT.getGetNftData()).toHaveProperty( + "itemIndex", + count - 1n, + ); + }); + + it("Shouldn't batch mint more than 250 items", async () => { + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + 260n, + ); + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + }); // in orig func contracts exit code -14, but it throw in code 399 ( we can just check ) + }); + + it("Should not batch mint not owner", async () => { + const trxResult = await batchMintNFTProcess( + collectionNFT, + notOwner, + owner, + 10n, + ); + + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }); + describe("!!--DIFF TEST---!!", () => { + it("Should HAVE message in batchdeploy with previous indexes", async () => { + await batchMintNFTProcess(collectionNFT, owner, owner, 50n); + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + 50n, + ); + + itemNFT = blockchain.openContract( + await NFTItem.fromInit( + null, + null, + collectionNFT.address, + 10n, + ), + ); // random number + + expect(trxResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: itemNFT.address, + }); + }); + + it("Should THROW if we have index > nextItemIndex", async () => { + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + 50n, + 70n, + ); + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + }); + }); + }); + }); + + describe("TRANSFER OWNERSHIP TEST", () => { + it("Owner should be able to transfer ownership", async () => { + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: notOwner.address, + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + expect(await collectionNFT.getOwner()).toEqualAddress( + notOwner.address, + ); + }); + it("Not owner should not be able to transfer ownership", async () => { + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: owner.address, + }; + + const trxResult = await collectionNFT.send( + notOwner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }); + }); +}); From 00310053fa3082fab1ca946cc8864acb7bbcd05c Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 00:13:50 +0300 Subject: [PATCH 02/22] feat(bench): separate test and benchmark runners --- jest-bench.config.js | 7 +++++++ jest-test.config.js | 7 +++++++ package.json | 5 +++-- src/benchmarks/nft/nft.test.spec.ts | 2 +- src/benchmarks/update.build.ts | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 jest-bench.config.js create mode 100644 jest-test.config.js diff --git a/jest-bench.config.js b/jest-bench.config.js new file mode 100644 index 0000000000..95fb970936 --- /dev/null +++ b/jest-bench.config.js @@ -0,0 +1,7 @@ +const baseConfig = require('./jest.config.js'); + +module.exports = { + ...baseConfig, + testMatch: ['**/src/benchmarks/**/*.spec.ts'], + testPathIgnorePatterns: ['/node_modules/', '/dist/', '.*\\.test\\.spec\\.ts$'], +}; \ No newline at end of file diff --git a/jest-test.config.js b/jest-test.config.js new file mode 100644 index 0000000000..3b4fd486f2 --- /dev/null +++ b/jest-test.config.js @@ -0,0 +1,7 @@ +const baseConfig = require('./jest.config.js'); + +module.exports = { + ...baseConfig, + testMatch: ['**/src/benchmarks/**/*.test.spec.ts'], + testPathIgnorePatterns: ['/node_modules/', '/dist/'], +}; \ No newline at end of file diff --git a/package.json b/package.json index 7909924aba..a0720025c2 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,9 @@ "test": "jest", "test:fast": "jest --config=./jest-fast.config.js", "test:allure": "rimraf ./allure-results && yarn test && allure serve allure-results", - "bench": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest ./src/benchmarks", - "bench:ci": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=false jest ./src/benchmarks", + "bench": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --config=./jest-bench.config.js", + "bench:test": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --config=./jest-test.config.js", + "bench:ci": "yarn gen:contracts:benchmarks && yarn bench && yarn bench:test", "bench:update": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true ts-node src/benchmarks/update.build.ts", "bench:add": "ts-node src/benchmarks/prompt.build.ts && yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true ADD=true ts-node src/benchmarks/update.build.ts", "coverage": "cross-env COVERAGE=true NODE_OPTIONS=--max_old_space_size=5120 jest --config=./jest-ci.config.js", diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts index 9f8f2d933e..bce462948d 100644 --- a/src/benchmarks/nft/nft.test.spec.ts +++ b/src/benchmarks/nft/nft.test.spec.ts @@ -54,7 +54,7 @@ import { import "@ton/test-utils"; import { randomInt } from "crypto"; -import { setStoragePrices } from "@/src/test/utils/gasUtils"; +import { setStoragePrices } from "@/test/utils/gasUtils"; /** Operation codes for NFT contract messages */ const Operations = { diff --git a/src/benchmarks/update.build.ts b/src/benchmarks/update.build.ts index 013c18768d..59c7da2b5e 100644 --- a/src/benchmarks/update.build.ts +++ b/src/benchmarks/update.build.ts @@ -216,7 +216,7 @@ const updateCodeSizeResultsFile = async ( const main = async () => { try { - const benchmarkPaths = globSync(["**/*.spec.ts"], { + const benchmarkPaths = globSync(["**/*.spec.ts", "!**/*.test.spec.ts"], { cwd: __dirname, }); From 91c928f1fabacfdfce94eebd84827110a8ed5f06 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 00:16:40 +0300 Subject: [PATCH 03/22] fmt --- jest-bench.config.js | 12 ++++++--- jest-test.config.js | 8 +++--- src/benchmarks/nft/nft.test.spec.ts | 42 ++++++++++++----------------- src/benchmarks/update.build.ts | 9 ++++--- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/jest-bench.config.js b/jest-bench.config.js index 95fb970936..be0c83c941 100644 --- a/jest-bench.config.js +++ b/jest-bench.config.js @@ -1,7 +1,11 @@ -const baseConfig = require('./jest.config.js'); +const baseConfig = require("./jest.config.js"); module.exports = { ...baseConfig, - testMatch: ['**/src/benchmarks/**/*.spec.ts'], - testPathIgnorePatterns: ['/node_modules/', '/dist/', '.*\\.test\\.spec\\.ts$'], -}; \ No newline at end of file + testMatch: ["**/src/benchmarks/**/*.spec.ts"], + testPathIgnorePatterns: [ + "/node_modules/", + "/dist/", + ".*\\.test\\.spec\\.ts$", + ], +}; diff --git a/jest-test.config.js b/jest-test.config.js index 3b4fd486f2..16633023c1 100644 --- a/jest-test.config.js +++ b/jest-test.config.js @@ -1,7 +1,7 @@ -const baseConfig = require('./jest.config.js'); +const baseConfig = require("./jest.config.js"); module.exports = { ...baseConfig, - testMatch: ['**/src/benchmarks/**/*.test.spec.ts'], - testPathIgnorePatterns: ['/node_modules/', '/dist/'], -}; \ No newline at end of file + testMatch: ["**/src/benchmarks/**/*.test.spec.ts"], + testPathIgnorePatterns: ["/node_modules/", "/dist/"], +}; diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts index bce462948d..85c7d03380 100644 --- a/src/benchmarks/nft/nft.test.spec.ts +++ b/src/benchmarks/nft/nft.test.spec.ts @@ -10,19 +10,14 @@ import type { TupleItemSlice, TupleItemCell, } from "@ton/core"; -import type { - SandboxContract, +import type { + SandboxContract, TreasuryContract, - SendMessageResult + SendMessageResult, } from "@ton/sandbox"; // Value imports -import { - beginCell, - Cell, - toNano, - Dictionary -} from "@ton/core"; +import { beginCell, Cell, toNano, Dictionary } from "@ton/core"; import { Blockchain } from "@ton/sandbox"; // NFT Collection imports @@ -42,14 +37,8 @@ import { } from "./output/collection_NFTCollection"; // NFT Item imports -import type { - Transfer, - NFTData -} from "./output/item_NFTItem"; -import { - storeInitNFTBody, - NFTItem -} from "./output/item_NFTItem"; +import type { Transfer, NFTData } from "./output/item_NFTItem"; +import { storeInitNFTBody, NFTItem } from "./output/item_NFTItem"; import "@ton/test-utils"; import { randomInt } from "crypto"; @@ -137,7 +126,7 @@ const TestValues = { /** Additional test values */ ExtraValues: { BatchMultiplier: 10n, - } + }, } as const; /** Dictionary type for NFT deployment data */ @@ -284,13 +273,13 @@ describe("NFT Item Contract", () => { masterChainBitPricePerSecond: 0n, masterChainCellPricePerSecond: 0n, }), - ); + ); owner = await blockchain.treasury("owner"); notOwner = await blockchain.treasury("notOwner"); emptyAddress = null; - defaultContent = Cell.fromBase64("te6ccgEBAQEAAgAAAA=="); // just some content ( doesn't matter ) + defaultContent = beginCell().endCell(); // just some content ( doesn't matter ) itemNFT = blockchain.openContract( await NFTItem.fromInit(null, null, owner.address, 0n), @@ -428,9 +417,8 @@ describe("NFT Item Contract", () => { }); }); - it("test transfer forward fee single", async () => { - // If transfer is successfull NFT supposed to send up to 2 messages + // If transfer is successful NFT supposed to send up to 2 messages // 1)To the owner_address with forward_amount of coins // 2)To the response_addr with forward_payload if response_addr is not addr_none // Each of those messages costs fwd_fee @@ -460,7 +448,7 @@ describe("NFT Item Contract", () => { fwdFee = Storage.ForwardFee.Double; }); it("should false with only one fwd fee on balance", async () => { - // If transfer is successfull NFT supposed to send up to 2 messages + // If transfer is successful NFT supposed to send up to 2 messages // 1)To the owner_address with forward_amount of coins // 2)To the response_addr with forward_payload if response_addr is not addr_none // Each of those messages costs fwd_fee @@ -985,7 +973,11 @@ describe("NFT Collection Contract", () => { return await collectionNFT.send( sender.getSender(), - { value: Storage.BatchDeployAmount * (count + TestValues.ExtraValues.BatchMultiplier) }, + { + value: + Storage.BatchDeployAmount * + (count + TestValues.ExtraValues.BatchMultiplier), + }, batchMintNFT, ); }; @@ -1077,7 +1069,7 @@ describe("NFT Collection Contract", () => { }); }); describe("!!--DIFF TEST---!!", () => { - it("Should HAVE message in batchdeploy with previous indexes", async () => { + it("Should HAVE message in batchDeploy with previous indexes", async () => { await batchMintNFTProcess(collectionNFT, owner, owner, 50n); const trxResult = await batchMintNFTProcess( collectionNFT, diff --git a/src/benchmarks/update.build.ts b/src/benchmarks/update.build.ts index 59c7da2b5e..c44212d3bf 100644 --- a/src/benchmarks/update.build.ts +++ b/src/benchmarks/update.build.ts @@ -216,9 +216,12 @@ const updateCodeSizeResultsFile = async ( const main = async () => { try { - const benchmarkPaths = globSync(["**/*.spec.ts", "!**/*.test.spec.ts"], { - cwd: __dirname, - }); + const benchmarkPaths = globSync( + ["**/*.spec.ts", "!**/*.test.spec.ts"], + { + cwd: __dirname, + }, + ); const benchmarkName = process.argv[2]; From 9e48e8ec536f263db42a019446ec43beda4fd463 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 00:18:03 +0300 Subject: [PATCH 04/22] fix --- src/benchmarks/nft/nft.test.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts index 85c7d03380..f90c3fbc11 100644 --- a/src/benchmarks/nft/nft.test.spec.ts +++ b/src/benchmarks/nft/nft.test.spec.ts @@ -3,6 +3,7 @@ import type { Address, Slice, ContractProvider, + Cell, Sender, Builder, TupleItem, @@ -10,14 +11,15 @@ import type { TupleItemSlice, TupleItemCell, } from "@ton/core"; +// Value imports +import { beginCell, toNano, Dictionary } from "@ton/core"; + import type { SandboxContract, TreasuryContract, SendMessageResult, } from "@ton/sandbox"; -// Value imports -import { beginCell, Cell, toNano, Dictionary } from "@ton/core"; import { Blockchain } from "@ton/sandbox"; // NFT Collection imports From b6a6c8b35ebdfe6f0d2d16610d7939ce24d2f754 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 00:19:52 +0300 Subject: [PATCH 05/22] fix x2 --- src/benchmarks/nft/nft.test.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts index f90c3fbc11..2dbc3966b6 100644 --- a/src/benchmarks/nft/nft.test.spec.ts +++ b/src/benchmarks/nft/nft.test.spec.ts @@ -652,7 +652,7 @@ describe("NFT Collection Contract", () => { defaultCommonContent = beginCell().storeStringTail("common").endCell(); defaultCollectionContent = beginCell() - .storeStringTail("collectioncontent") + .storeStringTail("collectionContent") .endCell(); defaultNFTContent = beginCell().storeStringTail("1.json").endCell(); From d1ed5d97b6248ecce9713e5ea59cef4d065b72a8 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 13:13:47 +0300 Subject: [PATCH 06/22] add allure steps --- src/benchmarks/nft/nft.test.spec.ts | 550 ++++++++++++++++++++-------- 1 file changed, 404 insertions(+), 146 deletions(-) diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts index 2dbc3966b6..0c488c113b 100644 --- a/src/benchmarks/nft/nft.test.spec.ts +++ b/src/benchmarks/nft/nft.test.spec.ts @@ -31,22 +31,24 @@ import type { RoyaltyParams, InitNFTBody, ChangeOwner, -} from "./output/collection_NFTCollection"; +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; import { storeRoyaltyParams, loadInitNFTBody, NFTCollection, -} from "./output/collection_NFTCollection"; +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; // NFT Item imports -import type { Transfer, NFTData } from "./output/item_NFTItem"; -import { storeInitNFTBody, NFTItem } from "./output/item_NFTItem"; +import type { Transfer, NFTData } from "@/benchmarks/nft/tact/output/item_NFTItem"; +import { storeInitNFTBody, NFTItem } from "@/benchmarks/nft/tact/output/item_NFTItem"; import "@ton/test-utils"; import { randomInt } from "crypto"; import { setStoragePrices } from "@/test/utils/gasUtils"; +import { step } from "@/test/allure/allure"; + /** Operation codes for NFT contract messages */ const Operations = { TransferNft: 0x5fcc3d14, @@ -298,12 +300,17 @@ describe("NFT Item Contract", () => { beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), ); - expect(deployResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - deploy: true, - success: true, - }); + await step( + "Check that deployResult.transactions has correct transaction", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + deploy: true, + success: true, + }); + }, + ); }); const messageGetStaticData = async ( @@ -330,20 +337,43 @@ describe("NFT Item Contract", () => { it("should get nft data correctly", async () => { const staticData = await itemNFT.getGetNftData(); - expect(staticData.init).toBe(-1n); - expect(staticData.itemIndex).toBe(0n); - expect(staticData.collectionAddress).toEqualAddress(owner.address); - expect(staticData.owner).toEqualAddress(owner.address); - expect(staticData.content).toEqualCell(defaultContent); + await step("Check that staticData.init is -1", () => { + expect(staticData.init).toBe(-1n); + }); + await step("Check that staticData.itemIndex is 0", () => { + expect(staticData.itemIndex).toBe(0n); + }); + await step( + "Check that staticData.collectionAddress equals owner.address", + () => { + expect(staticData.collectionAddress).toEqualAddress( + owner.address, + ); + }, + ); + await step("Check that staticData.owner equals owner.address", () => { + expect(staticData.owner).toEqualAddress(owner.address); + }); + await step( + "Check that staticData.content equals defaultContent", + () => { + expect(staticData.content).toEqualCell(defaultContent); + }, + ); }); it("should get static data correctly", async () => { const trxResult = await messageGetStaticData(owner, itemNFT); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); + await step( + "Check that trxResult.transactions has correct transaction (get static data)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); }); describe("Transfer ownership Fee cases", () => { @@ -369,12 +399,17 @@ describe("NFT Item Contract", () => { emptyAddress, Storage.TransferAmount, ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); + await step( + "Check that trxResult.transactions has correct transaction (transfer forward amount too much)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); }); it("test transfer storage fee", async () => { @@ -392,12 +427,17 @@ describe("NFT Item Contract", () => { emptyAddress, Storage.TransferAmount + balance, ); // balance + 1ton + fwd - (1ton + balance) = [0] + {fwdFee} and [0] < [minTonsForStorage] - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); + await step( + "Check that trxResult.transactions has correct transaction (test transfer storage fee)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); }); it("test transfer forward fee 2.0", async () => { // Let's verify that storage fee was an error trigger by increasing balance by min_storage @@ -417,6 +457,25 @@ describe("NFT Item Contract", () => { to: itemNFT.address, success: true, }); + await step( + "Check that trxResult.transactions has correct transaction (test transfer forward fee 2.0)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + balance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + await step( + "Check that balance is less than minTonsForStorage (test transfer forward fee 2.0)", + () => { + expect(balance).toBeLessThan(minTonsForStorage); + }, + ); }); it("test transfer forward fee single", async () => { @@ -443,6 +502,16 @@ describe("NFT Item Contract", () => { to: itemNFT.address, success: true, }); + await step( + "Check that trxResult.transactions has correct transaction (test transfer forward fee single)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); }); describe("test transfer forward fee double", function () { @@ -477,6 +546,17 @@ describe("NFT Item Contract", () => { success: false, exitCode: ErrorCodes.InvalidFees, }); + await step( + "Check that trxResult.transactions has correct transaction (double forward fee, not enough for both)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); }); // let now check if we have 2 fwdFees on balance @@ -503,6 +583,22 @@ describe("NFT Item Contract", () => { await blockchain.getContract(itemNFT.address) ).balance; expect(balance).toBeLessThan(minTonsForStorage); + await step( + "Check that trxResult.transactions has correct transaction (double forward fee, enough for both)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + await step( + "Check that balance is less than minTonsForStorage (double forward fee)", + () => { + expect(balance).toBeLessThan(minTonsForStorage); + }, + ); }); }); // int __test_transfer_success_forward_no_response testing in next test suite @@ -511,7 +607,9 @@ describe("NFT Item Contract", () => { describe("Transfer Ownership Tests", () => { it("Test ownership assigned", async () => { const oldOwner = await itemNFT.getOwner(); - expect(oldOwner).toEqualAddress(owner.address); + await step("Check that oldOwner equals owner.address", () => { + expect(oldOwner).toEqualAddress(owner.address); + }); const trxRes = await sendTransfer( itemNFT, owner.getSender(), @@ -522,12 +620,19 @@ describe("NFT Item Contract", () => { ); const newOwner = await itemNFT.getOwner(); - expect(newOwner).toEqualAddress(notOwner.address); - expect(trxRes.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, + await step("Check that newOwner equals notOwner.address", () => { + expect(newOwner).toEqualAddress(notOwner.address); }); + await step( + "Check that trxRes.transactions has correct transaction (ownership assigned)", + () => { + expect(trxRes.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); }); it("Test transfer ownership without any messages", async () => { @@ -540,10 +645,20 @@ describe("NFT Item Contract", () => { 0n, ); const newOwner = await itemNFT.getOwner(); - expect(newOwner).toEqualAddress(notOwner.address); - expect(trxRes.transactions).not.toHaveTransaction({ - from: itemNFT.address, - }); + await step( + "Check that newOwner equals notOwner.address (no messages)", + () => { + expect(newOwner).toEqualAddress(notOwner.address); + }, + ); + await step( + "Check that trxRes.transactions does NOT have transaction from itemNFT.address (no messages)", + () => { + expect(trxRes.transactions).not.toHaveTransaction({ + from: itemNFT.address, + }); + }, + ); }); it("Not owner should not be able to transfer ownership", async () => { @@ -555,12 +670,17 @@ describe("NFT Item Contract", () => { emptyAddress, 0n, ); - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); + await step( + "Check that trxResult.transactions has correct transaction (not owner should not be able to transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); }); }); @@ -579,11 +699,38 @@ describe("NFT Item Contract", () => { it("should not get static data", async () => { const staticData = await itemNFT.getGetNftData(); - expect(staticData.init).toBe(0n); - expect(staticData.collectionAddress).toEqualAddress(owner.address); - expect(staticData.owner).toBeNull(); - expect(staticData.itemIndex).toBe(itemIndex); - expect(staticData.content).toBeNull(); + await step( + "Check that staticData.init is 0 (not initialized)", + () => { + expect(staticData.init).toBe(0n); + }, + ); + await step( + "Check that staticData.collectionAddress equals owner.address (not initialized)", + () => { + expect(staticData.collectionAddress).toEqualAddress( + owner.address, + ); + }, + ); + await step( + "Check that staticData.owner is null (not initialized)", + () => { + expect(staticData.owner).toBeNull(); + }, + ); + await step( + "Check that staticData.itemIndex is itemIndex (not initialized)", + () => { + expect(staticData.itemIndex).toBe(itemIndex); + }, + ); + await step( + "Check that staticData.content is null (not initialized)", + () => { + expect(staticData.content).toBeNull(); + }, + ); }); it("should not transfer ownership", async () => { @@ -687,12 +834,17 @@ describe("NFT Collection Contract", () => { { value: Storage.DeployAmount }, deployCollectionMsg, ); - expect(deployResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - deploy: true, - success: true, - }); + await step( + "Check that deployResult.transactions has correct transaction (collection)", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + deploy: true, + success: true, + }); + }, + ); }); it("should deploy correctly", async () => { @@ -701,10 +853,25 @@ describe("NFT Collection Contract", () => { it("should get static data correctly", async () => { const staticData = await collectionNFT.getGetCollectionData(); - expect(staticData.owner).toEqualAddress(owner.address); - expect(staticData.nextItemIndex).toBe(0n); - expect(staticData.collectionContent).toEqualCell( - defaultCollectionContent, + await step( + "Check that staticData.owner equals owner.address (collection)", + () => { + expect(staticData.owner).toEqualAddress(owner.address); + }, + ); + await step( + "Check that staticData.nextItemIndex is 0 (collection)", + () => { + expect(staticData.nextItemIndex).toBe(0n); + }, + ); + await step( + "Check that staticData.collectionContent equals defaultCollectionContent (collection)", + () => { + expect(staticData.collectionContent).toEqualCell( + defaultCollectionContent, + ); + }, ); }); @@ -718,7 +885,12 @@ describe("NFT Collection Contract", () => { .storeSlice(defaultCommonContent.asSlice()) .storeRef(defaultContent) .endCell(); - expect(content).toEqualCell(expectedContent); + await step( + "Check that content equals expectedContent (nft content)", + () => { + expect(content).toEqualCell(expectedContent); + }, + ); }); describe("ROYALTY TESTS", () => { @@ -736,11 +908,16 @@ describe("NFT Collection Contract", () => { msg, ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); + await step( + "Check that trxResult.transactions has correct transaction (royalty msg)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); const exceptedMsg: Cell = beginCell() .storeUint(Operations.ReportRoyaltyParams, 32) @@ -830,30 +1007,49 @@ describe("NFT Collection Contract", () => { ); const nftData = await itemNFT.getGetNftData(); - expect(nftData.content).toEqualCell(defaultNFTContent); - expect(nftData.owner).toEqualAddress(owner.address); - expect(nftData.itemIndex).toBe(nextItemIndex); - expect(nftData.collectionAddress).toEqualAddress( - collectionNFT.address, + await step( + "Check that nftData.content equals defaultNFTContent", + () => { + expect(nftData.content).toEqualCell(defaultNFTContent); + }, + ); + await step("Check that nftData.owner equals owner.address", () => { + expect(nftData.owner).toEqualAddress(owner.address); + }); + await step("Check that nftData.itemIndex is nextItemIndex", () => { + expect(nftData.itemIndex).toBe(nextItemIndex); + }); + await step( + "Check that nftData.collectionAddress equals collectionNFT.address", + () => { + expect(nftData.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }, ); }); it("should not mint NFTItem if not owner", async () => { const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [_itemNFT, _trx] = await deployNFT( + const [_itemNFT, trx] = await deployNFT( nextItemIndex, collectionNFT, notOwner, notOwner, ); - expect( - (_trx as { transactions: unknown }).transactions, - ).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); + await step( + "Check that trx.transactions has correct transaction (not owner mint)", + () => { + expect( + trx.transactions + ).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); }); it("should not deploy previous nft", async () => { @@ -867,39 +1063,49 @@ describe("NFT Collection Contract", () => { ); nextItemIndex++; } - const [_itemNFT, _trx] = await deployNFT( + const [_itemNFT, trx] = await deployNFT( 0n, collectionNFT, owner, owner, ); - expect( - (_trx as { transactions: unknown }).transactions, - ).toHaveTransaction({ - from: collectionNFT.address, - to: _itemNFT.address, - deploy: false, - success: false, - exitCode: ErrorCodes.InvalidData, - }); + await step( + "Check that trx.transactions has correct transaction (should not deploy previous nft)", + () => { + expect( + trx.transactions, + ).toHaveTransaction({ + from: collectionNFT.address, + to: _itemNFT.address, + deploy: false, + success: false, + exitCode: ErrorCodes.InvalidData, + }); + }, + ); }); it("shouldn't mint item itemIndex > nextItemIndex", async () => { const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [_itemNFT, _trx] = await deployNFT( + const [_itemNFT, trx] = await deployNFT( nextItemIndex + 1n, collectionNFT, owner, owner, ); - expect( - (_trx as { transactions: unknown }).transactions, - ).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.IncorrectIndex, - }); + await step( + "Check that trx.transactions has correct transaction (itemIndex > nextItemIndex)", + () => { + expect( + trx.transactions + ).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.IncorrectIndex, + }); + }, + ); }); it("test get nft by itemIndex", async () => { @@ -916,9 +1122,16 @@ describe("NFT Collection Contract", () => { const newNFT = blockchain.getContract(nftAddress); const getData = await (await newNFT).get("get_nft_data"); const dataNFT = loadGetterTupleNFTData(getData.stack); - expect(dataNFT.itemIndex).toBe(nextItemIndex); - expect(dataNFT.collectionAddress).toEqualAddress( - collectionNFT.address, + await step("Check that dataNFT.itemIndex is nextItemIndex", () => { + expect(dataNFT.itemIndex).toBe(nextItemIndex); + }); + await step( + "Check that dataNFT.collectionAddress equals collectionNFT.address", + () => { + expect(dataNFT.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }, ); }); }); @@ -1019,11 +1232,16 @@ describe("NFT Collection Contract", () => { count, ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); + await step( + "Check that trxResult.transactions has correct transaction (batch mint success)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); itemNFT = blockchain.openContract( await NFTItem.fromInit( null, @@ -1034,9 +1252,14 @@ describe("NFT Collection Contract", () => { ); // it was deployed, that's why we can get it - expect(await itemNFT.getGetNftData()).toHaveProperty( - "itemIndex", - count - 1n, + await step( + "Check that itemNFT.getGetNftData() has property itemIndex", + async () => { + expect(await itemNFT.getGetNftData()).toHaveProperty( + "itemIndex", + count - 1n, + ); + }, ); }); @@ -1048,11 +1271,16 @@ describe("NFT Collection Contract", () => { 260n, ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - }); // in orig func contracts exit code -14, but it throw in code 399 ( we can just check ) + await step( + "Check that trxResult.transactions has correct transaction (should not batch mint more than 250)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + }); + }, + ); }); it("Should not batch mint not owner", async () => { @@ -1063,12 +1291,17 @@ describe("NFT Collection Contract", () => { 10n, ); - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); + await step( + "Check that trxResult.transactions has correct transaction (not owner batch mint)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); }); describe("!!--DIFF TEST---!!", () => { it("Should HAVE message in batchDeploy with previous indexes", async () => { @@ -1089,10 +1322,15 @@ describe("NFT Collection Contract", () => { ), ); // random number - expect(trxResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: itemNFT.address, - }); + await step( + "Check that trxResult.transactions has correct transaction (batchDeploy with previous indexes)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: itemNFT.address, + }); + }, + ); }); it("Should THROW if we have index > nextItemIndex", async () => { @@ -1104,11 +1342,16 @@ describe("NFT Collection Contract", () => { 70n, ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - }); + await step( + "Check that trxResult.transactions has correct transaction (index > nextItemIndex)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + }); + }, + ); }); }); }); @@ -1127,13 +1370,23 @@ describe("NFT Collection Contract", () => { changeOwnerMsg, ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - expect(await collectionNFT.getOwner()).toEqualAddress( - notOwner.address, + await step( + "Check that trxResult.transactions has correct transaction (owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + await step( + "Check that collectionNFT.getOwner() equals notOwner.address", + async () => { + expect(await collectionNFT.getOwner()).toEqualAddress( + notOwner.address, + ); + }, ); }); it("Not owner should not be able to transfer ownership", async () => { @@ -1149,12 +1402,17 @@ describe("NFT Collection Contract", () => { changeOwnerMsg, ); - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); + await step( + "Check that trxResult.transactions has correct transaction (not owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); }); }); }); From 8b87036629d8a910c878e4fc40874ec6f4c940f6 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 13:15:28 +0300 Subject: [PATCH 07/22] fix --- src/benchmarks/nft/nft.test.spec.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts index 0c488c113b..06e65dce9a 100644 --- a/src/benchmarks/nft/nft.test.spec.ts +++ b/src/benchmarks/nft/nft.test.spec.ts @@ -39,8 +39,14 @@ import { } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; // NFT Item imports -import type { Transfer, NFTData } from "@/benchmarks/nft/tact/output/item_NFTItem"; -import { storeInitNFTBody, NFTItem } from "@/benchmarks/nft/tact/output/item_NFTItem"; +import type { + Transfer, + NFTData, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; +import { + storeInitNFTBody, + NFTItem, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; import "@ton/test-utils"; import { randomInt } from "crypto"; @@ -217,7 +223,7 @@ function loadGetterTupleNFTData(source: TupleItem[]): NFTData { /** * Extends NFTItem interface with owner getter functionality */ -declare module "./output/item_NFTItem" { +declare module "@/benchmarks/nft/tact/output/item_NFTItem" { interface NFTItem { /** * Gets the current owner of the NFT @@ -231,7 +237,7 @@ declare module "./output/item_NFTItem" { /** * Extends NFTCollection interface with additional getter functionality */ -declare module "./output/collection_NFTCollection" { +declare module "@/benchmarks/nft/tact/output/collection_NFTCollection" { interface NFTCollection { /** * Gets the next available item index for minting @@ -1040,9 +1046,7 @@ describe("NFT Collection Contract", () => { await step( "Check that trx.transactions has correct transaction (not owner mint)", () => { - expect( - trx.transactions - ).toHaveTransaction({ + expect(trx.transactions).toHaveTransaction({ from: notOwner.address, to: collectionNFT.address, success: false, @@ -1072,9 +1076,7 @@ describe("NFT Collection Contract", () => { await step( "Check that trx.transactions has correct transaction (should not deploy previous nft)", () => { - expect( - trx.transactions, - ).toHaveTransaction({ + expect(trx.transactions).toHaveTransaction({ from: collectionNFT.address, to: _itemNFT.address, deploy: false, @@ -1096,9 +1098,7 @@ describe("NFT Collection Contract", () => { await step( "Check that trx.transactions has correct transaction (itemIndex > nextItemIndex)", () => { - expect( - trx.transactions - ).toHaveTransaction({ + expect(trx.transactions).toHaveTransaction({ from: owner.address, to: collectionNFT.address, success: false, From 2ca66e9a54d0c74c78d466836a985c69abcf249b Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 13:50:22 +0300 Subject: [PATCH 08/22] fix add/update script --- src/benchmarks/update.build.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/benchmarks/update.build.ts b/src/benchmarks/update.build.ts index c44212d3bf..78a110125f 100644 --- a/src/benchmarks/update.build.ts +++ b/src/benchmarks/update.build.ts @@ -217,11 +217,11 @@ const updateCodeSizeResultsFile = async ( const main = async () => { try { const benchmarkPaths = globSync( - ["**/*.spec.ts", "!**/*.test.spec.ts"], + ["**/*.spec.ts"], { cwd: __dirname, }, - ); + ).filter(path => !path.includes('.test.')); const benchmarkName = process.argv[2]; From bcec36f51ba9e2fce68e1ec268b37ba1b4d29215 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 13:51:10 +0300 Subject: [PATCH 09/22] fmt --- src/benchmarks/update.build.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/benchmarks/update.build.ts b/src/benchmarks/update.build.ts index 78a110125f..ace3788bc2 100644 --- a/src/benchmarks/update.build.ts +++ b/src/benchmarks/update.build.ts @@ -216,12 +216,9 @@ const updateCodeSizeResultsFile = async ( const main = async () => { try { - const benchmarkPaths = globSync( - ["**/*.spec.ts"], - { - cwd: __dirname, - }, - ).filter(path => !path.includes('.test.')); + const benchmarkPaths = globSync(["**/*.spec.ts"], { + cwd: __dirname, + }).filter((path) => !path.includes(".test.")); const benchmarkName = process.argv[2]; From b0c6ac8aa83f8b179725628aa5fb45cc95c7d50a Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Tue, 20 May 2025 18:17:14 +0300 Subject: [PATCH 10/22] xd --- .../benchmarks/jest-bench.config.js | 2 +- .../benchmarks/jest-test.config.js | 2 +- src/benchmarks/nft/bench.spec.ts | 4 + src/benchmarks/nft/{nft.spec.ts => bench.ts} | 131 +- src/benchmarks/nft/nft.test.spec.ts | 1418 ---------------- src/benchmarks/nft/run.ts | 131 ++ src/benchmarks/nft/test.spec.ts | 4 + src/benchmarks/nft/tests/test.ts | 1458 +++++++++++++++++ 8 files changed, 1611 insertions(+), 1539 deletions(-) rename jest-bench.config.js => src/benchmarks/jest-bench.config.js (80%) rename jest-test.config.js => src/benchmarks/jest-test.config.js (75%) create mode 100644 src/benchmarks/nft/bench.spec.ts rename src/benchmarks/nft/{nft.spec.ts => bench.ts} (74%) delete mode 100644 src/benchmarks/nft/nft.test.spec.ts create mode 100644 src/benchmarks/nft/run.ts create mode 100644 src/benchmarks/nft/test.spec.ts create mode 100644 src/benchmarks/nft/tests/test.ts diff --git a/jest-bench.config.js b/src/benchmarks/jest-bench.config.js similarity index 80% rename from jest-bench.config.js rename to src/benchmarks/jest-bench.config.js index be0c83c941..2b65592414 100644 --- a/jest-bench.config.js +++ b/src/benchmarks/jest-bench.config.js @@ -1,4 +1,4 @@ -const baseConfig = require("./jest.config.js"); +const baseConfig = require("../../jest.config.js"); module.exports = { ...baseConfig, diff --git a/jest-test.config.js b/src/benchmarks/jest-test.config.js similarity index 75% rename from jest-test.config.js rename to src/benchmarks/jest-test.config.js index 16633023c1..3899ea5e38 100644 --- a/jest-test.config.js +++ b/src/benchmarks/jest-test.config.js @@ -1,4 +1,4 @@ -const baseConfig = require("./jest.config.js"); +const baseConfig = require("../../jest.config.js"); module.exports = { ...baseConfig, diff --git a/src/benchmarks/nft/bench.spec.ts b/src/benchmarks/nft/bench.spec.ts new file mode 100644 index 0000000000..087752aa8a --- /dev/null +++ b/src/benchmarks/nft/bench.spec.ts @@ -0,0 +1,4 @@ +import { bench } from "@/benchmarks/nft/bench"; +import { run } from "@/benchmarks/nft/run"; + +run(bench); diff --git a/src/benchmarks/nft/nft.spec.ts b/src/benchmarks/nft/bench.ts similarity index 74% rename from src/benchmarks/nft/nft.spec.ts rename to src/benchmarks/nft/bench.ts index 0ae5185c1e..b66feabe16 100644 --- a/src/benchmarks/nft/nft.spec.ts +++ b/src/benchmarks/nft/bench.ts @@ -1,31 +1,18 @@ import "@ton/test-utils"; import type { Address } from "@ton/core"; -import { - Cell, - beginCell, - toNano, - contractAddress, - Dictionary, -} from "@ton/core"; +import type { Cell } from "@ton/core"; +import { beginCell, toNano, Dictionary } from "@ton/core"; import type { Slice, Sender, Builder } from "@ton/core"; import { Blockchain } from "@ton/sandbox"; import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; +import type { BenchmarkResult, CodeSizeResult } from "@/benchmarks/utils/gas"; +import { getStateSizeForAccount, getUsedGas } from "@/benchmarks/utils/gas"; +import { join } from "path"; +import type { Step } from "@/test/utils/write-vm-log"; +import { writeLog } from "@/test/utils/write-vm-log"; +import type { NFTCollection } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; import { - generateResults, - getStateSizeForAccount, - generateCodeSizeResults, - getUsedGas, - printBenchmarkTable, - type BenchmarkResult, - type CodeSizeResult, -} from "@/benchmarks/utils/gas"; -import { join, resolve } from "path"; -import { readFileSync } from "fs"; -import { posixNormalize } from "@/utils/filePath"; -import { type Step, writeLog } from "@/test/utils/write-vm-log"; -import { - NFTCollection, ReportStaticData, loadInitNFTBody, } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; @@ -36,14 +23,11 @@ import type { RoyaltyParams, InitNFTBody, } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; -import { +import type { NFTItem, - type Transfer, - storeInitNFTBody, + Transfer, } from "@/benchmarks/nft/tact/output/collection_NFTItem"; - -import benchmarkResults from "@/benchmarks/nft/results_gas.json"; -import benchmarkCodeSizeResults from "@/benchmarks/nft/results_code_size.json"; +import { storeInitNFTBody } from "@/benchmarks/nft/tact/output/collection_NFTItem"; type dictDeployNFT = { amount: bigint; @@ -66,19 +50,7 @@ const dictDeployNFTItem = { }, }; -const loadFunCNFTBoc = () => { - const bocCollection = readFileSync( - posixNormalize(resolve(__dirname, "./func/output/nft-collection.boc")), - ); - - const bocItem = readFileSync( - posixNormalize(resolve(__dirname, "./func/output/nft-item.boc")), - ); - - return { bocCollection, bocItem }; -}; - -function testNFT( +export function bench( benchmarkResults: BenchmarkResult, codeSizeResults: CodeSizeResult, fromInitCollection: ( @@ -392,82 +364,3 @@ function testNFT( ).toEqual(codeSizeResults.size["item bits"]); }); } - -describe("NFT Gas Tests", () => { - const fullResults = generateResults(benchmarkResults); - const fullCodeSizeResults = generateCodeSizeResults( - benchmarkCodeSizeResults, - ); - - describe("func", () => { - const funcCodeSize = fullCodeSizeResults.at(0)!; - const funcResult = fullResults.at(0)!; - - function fromInitCollection( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) { - const nftData = loadFunCNFTBoc(); - const __code = Cell.fromBoc(nftData.bocCollection)[0]!; - - const royaltyCell = beginCell() - .storeUint(royaltyParams.nominator, 16) - .storeUint(royaltyParams.dominator, 16) - .storeAddress(royaltyParams.owner) - .endCell(); - - const __data = beginCell() - .storeAddress(owner) - .storeUint(index, 64) - .storeRef(content) - .storeRef(Cell.fromBoc(nftData.bocItem)[0]!) - .storeRef(royaltyCell) - .endCell(); - - const __gen_init = { code: __code, data: __data }; - const address = contractAddress(0, __gen_init); - return Promise.resolve(new NFTCollection(address, __gen_init)); - } - - function fromInitItem( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) { - const nftData = loadFunCNFTBoc(); - const __code = Cell.fromBoc(nftData.bocItem)[0]!; - - const __data = beginCell() - .storeUint(itemIndex, 64) - .storeAddress(collectionAddress) - .endCell(); - - const __gen_init = { code: __code, data: __data }; - const address = contractAddress(0, __gen_init); - return Promise.resolve(new NFTItem(address, __gen_init)); - } - - testNFT(funcResult, funcCodeSize, fromInitCollection, fromInitItem); - }); - - describe("tact", () => { - const tactCodeSize = fullCodeSizeResults.at(-1)!; - const tactResult = fullResults.at(-1)!; - testNFT( - tactResult, - tactCodeSize, - NFTCollection.fromInit.bind(NFTCollection), - NFTItem.fromInit.bind(NFTItem), - ); - }); - - afterAll(() => { - printBenchmarkTable(fullResults, fullCodeSizeResults, { - implementationName: "FunC", - printMode: "full", - }); - }); -}); diff --git a/src/benchmarks/nft/nft.test.spec.ts b/src/benchmarks/nft/nft.test.spec.ts deleted file mode 100644 index 06e65dce9a..0000000000 --- a/src/benchmarks/nft/nft.test.spec.ts +++ /dev/null @@ -1,1418 +0,0 @@ -// Type imports -import type { - Address, - Slice, - ContractProvider, - Cell, - Sender, - Builder, - TupleItem, - TupleItemInt, - TupleItemSlice, - TupleItemCell, -} from "@ton/core"; -// Value imports -import { beginCell, toNano, Dictionary } from "@ton/core"; - -import type { - SandboxContract, - TreasuryContract, - SendMessageResult, -} from "@ton/sandbox"; - -import { Blockchain } from "@ton/sandbox"; - -// NFT Collection imports -import type { - DeployNFT, - GetRoyaltyParams, - GetStaticData, - BatchDeploy, - RoyaltyParams, - InitNFTBody, - ChangeOwner, -} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; -import { - storeRoyaltyParams, - loadInitNFTBody, - NFTCollection, -} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; - -// NFT Item imports -import type { - Transfer, - NFTData, -} from "@/benchmarks/nft/tact/output/item_NFTItem"; -import { - storeInitNFTBody, - NFTItem, -} from "@/benchmarks/nft/tact/output/item_NFTItem"; - -import "@ton/test-utils"; -import { randomInt } from "crypto"; - -import { setStoragePrices } from "@/test/utils/gasUtils"; - -import { step } from "@/test/allure/allure"; - -/** Operation codes for NFT contract messages */ -const Operations = { - TransferNft: 0x5fcc3d14, - OwnershipAssignment: 0x05138d91, - Excess: 0xd53276db, - GetStaticData: 0x2fcb26a2, - ReportStaticData: 0x8b771735, - GetRoyaltyParams: 0x693d3950, - ReportRoyaltyParams: 0xa8cb00ad, - EditContent: 0x1a0b9d51, - TransferEditorship: 0x1c04412a, - EditorshipAssigned: 0x511a4463, -} as const; - -/** Storage and transaction related constants */ -const Storage = { - /** Minimum amount of TONs required for storage */ - MinTons: 50000000n, - /** Amount of TONs for deployment operations */ - DeployAmount: toNano("0.1"), - /** Amount of TONs for transfer operations */ - TransferAmount: toNano("1"), - /** Amount of TONs for batch deployment */ - BatchDeployAmount: toNano("100"), - /** Amount of TONs for ownership change */ - ChangeOwnerAmount: 100000000n, - /** Amount of TONs for NFT minting */ - NftMintAmount: 10000000n, - /** Forward fee values */ - ForwardFee: { - /** Base forward fee for single message */ - Base: 623605n, - /** Forward fee for double message */ - Double: 729606n, - }, -} as const; - -/** Error codes */ -const ErrorCodes = { - /** Error code for not initialized contract */ - NotInit: 9, - /** Error code for not owner */ - NotOwner: 401, - /** Error code for invalid fees */ - InvalidFees: 402, - /** Error code for incorrect index */ - IncorrectIndex: 402, - /** Error code for invalid data */ - InvalidData: 65535, -} as const; - -/** Test related constants */ -const TestValues = { - /** Default item index used in tests */ - ItemIndex: 100n, - /** Batch operation sizes */ - BatchSize: { - Min: 1n, - Max: 250n, - Default: 50n, - OverLimit: 260n, - Small: 10n, - }, - /** Range for random number generation */ - RandomRange: 1337, - /** Royalty parameters */ - Royalty: { - Nominator: 1n, - Dominator: 100n, - }, - /** Bit sizes for different data types */ - BitSizes: { - Uint1: 1, - Uint8: 8, - Uint16: 16, - Uint32: 32, - Uint64: 64, - }, - /** Additional test values */ - ExtraValues: { - BatchMultiplier: 10n, - }, -} as const; - -/** Dictionary type for NFT deployment data */ -export type dictDeployNFT = { - amount: bigint; - initNFTBody: InitNFTBody; -}; - -/** Dictionary value parser for NFT deployment */ -export const dictDeployNFTItem = { - serialize: (src: dictDeployNFT, builder: Builder) => { - builder - .storeCoins(src.amount) - .storeRef( - beginCell().store(storeInitNFTBody(src.initNFTBody)).endCell(), - ); - }, - parse: (src: Slice) => { - return { - amount: src.loadCoins(), - initNFTBody: loadInitNFTBody(src.loadRef().asSlice()), - }; - }, -}; - -const minTonsForStorage = Storage.MinTons; - -/** - * Sends a transfer message to an NFT item contract - * @param itemNFT - The NFT item contract instance - * @param from - The sender of the transfer - * @param value - Amount of TONs to send - * @param newOwner - Address of the new owner - * @param responseDestination - Address to send the response to - * @param forwardAmount - Amount of TONs to forward - * @param forwardPayload - Optional payload to forward - * @returns Promise resolving to the transaction result - */ -const sendTransfer = async ( - itemNFT: SandboxContract, - from: Sender, - value: bigint, - newOwner: Address, - responseDestination: Address | null, - forwardAmount: bigint, - forwardPayload: Slice = beginCell().storeUint(0, 1).asSlice(), -): Promise => { - const msg: Transfer = { - $$type: "Transfer", - queryId: 0n, - newOwner: newOwner, - responseDestination: responseDestination, - customPayload: null, - forwardAmount: forwardAmount, - forwardPayload: forwardPayload, - }; - - return await itemNFT.send(from, { value }, msg); -}; - -/** - * Helper function to load NFT data from a tuple of contract getter results - * @param source - Array of tuple items containing NFT data - * @returns Parsed NFT data object - */ -function loadGetterTupleNFTData(source: TupleItem[]): NFTData { - const _init = (source[0] as TupleItemInt).value; - const _index = (source[1] as TupleItemInt).value; - const _collectionAddress = (source[2] as TupleItemSlice).cell - .asSlice() - .loadAddress(); - const _owner = (source[3] as TupleItemSlice).cell.asSlice().loadAddress(); - const _content = (source[4] as TupleItemCell).cell; - return { - $$type: "NFTData" as const, - init: _init, - itemIndex: _index, - collectionAddress: _collectionAddress, - owner: _owner, - content: _content, - }; -} - -/** - * Extends NFTItem interface with owner getter functionality - */ -declare module "@/benchmarks/nft/tact/output/item_NFTItem" { - interface NFTItem { - /** - * Gets the current owner of the NFT - * @param provider - Contract provider instance - * @returns Promise resolving to owner's address or null if not initialized - */ - getOwner(provider: ContractProvider): Promise
; - } -} - -/** - * Extends NFTCollection interface with additional getter functionality - */ -declare module "@/benchmarks/nft/tact/output/collection_NFTCollection" { - interface NFTCollection { - /** - * Gets the next available item index for minting - * @param provider - Contract provider instance - * @returns Promise resolving to the next item index - */ - getNextItemIndex(provider: ContractProvider): Promise; - - /** - * Gets the current owner of the collection - * @param provider - Contract provider instance - * @returns Promise resolving to owner's address - */ - getOwner(provider: ContractProvider): Promise
; - } -} - -NFTItem.prototype.getOwner = async function ( - this: NFTItem, - provider: ContractProvider, -): Promise
{ - const res = await this.getGetNftData(provider); - return res.owner; -}; - -describe("NFT Item Contract", () => { - let blockchain: Blockchain; - let itemNFT: SandboxContract; - let owner: SandboxContract; - let notOwner: SandboxContract; - let defaultContent: Cell; - let emptyAddress: Address | null; - - beforeEach(async () => { - blockchain = await Blockchain.create(); - - const config = blockchain.config; - blockchain.setConfig( - setStoragePrices(config, { - unixTimeSince: 0, - bitPricePerSecond: 0n, - cellPricePerSecond: 0n, - masterChainBitPricePerSecond: 0n, - masterChainCellPricePerSecond: 0n, - }), - ); - - owner = await blockchain.treasury("owner"); - notOwner = await blockchain.treasury("notOwner"); - - emptyAddress = null; - defaultContent = beginCell().endCell(); // just some content ( doesn't matter ) - - itemNFT = blockchain.openContract( - await NFTItem.fromInit(null, null, owner.address, 0n), - ); - const deployItemMsg: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultContent, - }; - - const deployResult = await itemNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), - ); - - await step( - "Check that deployResult.transactions has correct transaction", - () => { - expect(deployResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - deploy: true, - success: true, - }); - }, - ); - }); - - const messageGetStaticData = async ( - sender: SandboxContract, - itemNFT: SandboxContract, - ) => { - const msg: GetStaticData = { - $$type: "GetStaticData", - queryId: 1n, - }; - - const trxResult = await itemNFT.send( - sender.getSender(), - { value: Storage.DeployAmount }, - msg, - ); - return trxResult; - }; - - it("should deploy correctly", async () => { - // checking in beforeEach - }); - - it("should get nft data correctly", async () => { - const staticData = await itemNFT.getGetNftData(); - - await step("Check that staticData.init is -1", () => { - expect(staticData.init).toBe(-1n); - }); - await step("Check that staticData.itemIndex is 0", () => { - expect(staticData.itemIndex).toBe(0n); - }); - await step( - "Check that staticData.collectionAddress equals owner.address", - () => { - expect(staticData.collectionAddress).toEqualAddress( - owner.address, - ); - }, - ); - await step("Check that staticData.owner equals owner.address", () => { - expect(staticData.owner).toEqualAddress(owner.address); - }); - await step( - "Check that staticData.content equals defaultContent", - () => { - expect(staticData.content).toEqualCell(defaultContent); - }, - ); - }); - - it("should get static data correctly", async () => { - const trxResult = await messageGetStaticData(owner, itemNFT); - await step( - "Check that trxResult.transactions has correct transaction (get static data)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - }); - - describe("Transfer ownership Fee cases", () => { - let balance: bigint; - let fwdFee: bigint; - - beforeEach(async () => { - balance = await ( - await blockchain.getContract(itemNFT.address) - ).balance; - fwdFee = Storage.ForwardFee.Base; - }); - - it("Transfer forward amount too much", async () => { - // NFT should reject transfer if balance lower than forward_amount + message forward fee + minimal storage fee - // Sending message with forward_amount of 1 TON and balance 0.1 TON - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - emptyAddress, - Storage.TransferAmount, - ); - await step( - "Check that trxResult.transactions has correct transaction (transfer forward amount too much)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - }, - ); - }); - - it("test transfer storage fee", async () => { - // Now let's try forward_amount exactly equal to balance and fwd_fee 0 - // 1 TON Balance forward_amount:1 TON fwd_fee:0 (just add to transfer value) verifying that minimal storage comes into play - // Should fail with no actions - - // [] and {} just kinds of () for more understandable description - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + fwdFee, - notOwner.address, - emptyAddress, - Storage.TransferAmount + balance, - ); // balance + 1ton + fwd - (1ton + balance) = [0] + {fwdFee} and [0] < [minTonsForStorage] - await step( - "Check that trxResult.transactions has correct transaction (test transfer storage fee)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - }, - ); - }); - it("test transfer forward fee 2.0", async () => { - // Let's verify that storage fee was an error trigger by increasing balance by min_storage - // Expect success - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + minTonsForStorage + fwdFee, - notOwner.address, - emptyAddress, - Storage.TransferAmount + balance, - ); // balance + 1ton + minTonsForStorage + fwdFee - (1ton + balance) = [minTonsForStorage] + {fwdFee} - - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - await step( - "Check that trxResult.transactions has correct transaction (test transfer forward fee 2.0)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - balance = await ( - await blockchain.getContract(itemNFT.address) - ).balance; - await step( - "Check that balance is less than minTonsForStorage (test transfer forward fee 2.0)", - () => { - expect(balance).toBeLessThan(minTonsForStorage); - }, - ); - }); - - it("test transfer forward fee single", async () => { - // If transfer is successful NFT supposed to send up to 2 messages - // 1)To the owner_address with forward_amount of coins - // 2)To the response_addr with forward_payload if response_addr is not addr_none - // Each of those messages costs fwd_fee - // In this case we test scenario where only single message required to be sent; - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + Storage.ForwardFee.Base, - notOwner.address, - emptyAddress, - Storage.TransferAmount + balance - minTonsForStorage, - beginCell() - .storeUint(1, 1) - .storeStringTail("testing") - .asSlice(), - ); // balance + 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} - - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - await step( - "Check that trxResult.transactions has correct transaction (test transfer forward fee single)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - }); - - describe("test transfer forward fee double", function () { - beforeEach(() => { - fwdFee = Storage.ForwardFee.Double; - }); - it("should false with only one fwd fee on balance", async () => { - // If transfer is successful NFT supposed to send up to 2 messages - // 1)To the owner_address with forward_amount of coins - // 2)To the response_addr with forward_payload if response_addr is not addr_none - // Each of those messages costs fwd_fee - // In this case we test scenario where both messages required to be sent but balance has funs only for single message - // To do so resp_dst has be a valid address not equal to addr_none - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + fwdFee, - notOwner.address, - owner.address, - Storage.TransferAmount + balance - Storage.MinTons, - beginCell() - .storeUint(1, 1) - .storeStringTail("testing") - .asSlice(), - ); - - // 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} and {fwdFee} < {2 * fwdFee} - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - await step( - "Check that trxResult.transactions has correct transaction (double forward fee, not enough for both)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - }, - ); - }); - - // let now check if we have 2 fwdFees on balance - it("should work with 2 fwdFee on balance", async () => { - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + 2n * fwdFee, - notOwner.address, - owner.address, - Storage.TransferAmount + balance - minTonsForStorage, - beginCell() - .storeUint(1, 1) - .storeStringTail("testing") - .asSlice(), - ); - // 1ton + 2 * fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {2 * fwdFee} - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - balance = await ( - await blockchain.getContract(itemNFT.address) - ).balance; - expect(balance).toBeLessThan(minTonsForStorage); - await step( - "Check that trxResult.transactions has correct transaction (double forward fee, enough for both)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - await step( - "Check that balance is less than minTonsForStorage (double forward fee)", - () => { - expect(balance).toBeLessThan(minTonsForStorage); - }, - ); - }); - }); - // int __test_transfer_success_forward_no_response testing in next test suite - }); - - describe("Transfer Ownership Tests", () => { - it("Test ownership assigned", async () => { - const oldOwner = await itemNFT.getOwner(); - await step("Check that oldOwner equals owner.address", () => { - expect(oldOwner).toEqualAddress(owner.address); - }); - const trxRes = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - owner.address, - 1n, - ); - - const newOwner = await itemNFT.getOwner(); - await step("Check that newOwner equals notOwner.address", () => { - expect(newOwner).toEqualAddress(notOwner.address); - }); - await step( - "Check that trxRes.transactions has correct transaction (ownership assigned)", - () => { - expect(trxRes.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - }); - - it("Test transfer ownership without any messages", async () => { - const trxRes = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - emptyAddress, - 0n, - ); - const newOwner = await itemNFT.getOwner(); - await step( - "Check that newOwner equals notOwner.address (no messages)", - () => { - expect(newOwner).toEqualAddress(notOwner.address); - }, - ); - await step( - "Check that trxRes.transactions does NOT have transaction from itemNFT.address (no messages)", - () => { - expect(trxRes.transactions).not.toHaveTransaction({ - from: itemNFT.address, - }); - }, - ); - }); - - it("Not owner should not be able to transfer ownership", async () => { - const trxResult = await sendTransfer( - itemNFT, - notOwner.getSender(), - Storage.DeployAmount, - notOwner.address, - emptyAddress, - 0n, - ); - await step( - "Check that trxResult.transactions has correct transaction (not owner should not be able to transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - }); - - describe("NOT INITIALIZED TESTS", () => { - const itemIndex: bigint = 100n; - beforeEach(async () => { - itemNFT = blockchain.openContract( - await NFTItem.fromInit(null, null, owner.address, itemIndex), - ); - const _deployResult = await itemNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - beginCell().asSlice(), - ); - }); - - it("should not get static data", async () => { - const staticData = await itemNFT.getGetNftData(); - await step( - "Check that staticData.init is 0 (not initialized)", - () => { - expect(staticData.init).toBe(0n); - }, - ); - await step( - "Check that staticData.collectionAddress equals owner.address (not initialized)", - () => { - expect(staticData.collectionAddress).toEqualAddress( - owner.address, - ); - }, - ); - await step( - "Check that staticData.owner is null (not initialized)", - () => { - expect(staticData.owner).toBeNull(); - }, - ); - await step( - "Check that staticData.itemIndex is itemIndex (not initialized)", - () => { - expect(staticData.itemIndex).toBe(itemIndex); - }, - ); - await step( - "Check that staticData.content is null (not initialized)", - () => { - expect(staticData.content).toBeNull(); - }, - ); - }); - - it("should not transfer ownership", async () => { - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - owner.address, - 0n, - ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotInit, - }); - }); - - it("should not get static data message", async () => { - const trxResult = await messageGetStaticData(owner, itemNFT); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotInit, - }); - }); - }); -}); - -NFTCollection.prototype.getNextItemIndex = async function ( - this: NFTCollection, - provider: ContractProvider, -): Promise { - const res = await this.getGetCollectionData(provider); - return res.nextItemIndex; -}; - -NFTCollection.prototype.getOwner = async function ( - this: NFTCollection, - provider: ContractProvider, -): Promise
{ - const res = await this.getGetCollectionData(provider); - return res.owner; -}; - -describe("NFT Collection Contract", () => { - let blockchain: Blockchain; - let collectionNFT: SandboxContract; - let itemNFT: SandboxContract; - - let owner: SandboxContract; - let notOwner: SandboxContract; - - let defaultContent: Cell; - let defaultCommonContent: Cell; - let defaultCollectionContent: Cell; - let defaultNFTContent: Cell; - let royaltyParams: RoyaltyParams; - - beforeEach(async () => { - blockchain = await Blockchain.create(); - owner = await blockchain.treasury("owner"); - notOwner = await blockchain.treasury("notOwner"); - - defaultCommonContent = beginCell().storeStringTail("common").endCell(); - defaultCollectionContent = beginCell() - .storeStringTail("collectionContent") - .endCell(); - - defaultNFTContent = beginCell().storeStringTail("1.json").endCell(); - - defaultContent = beginCell() - .storeRef(defaultCollectionContent) - .storeRef(defaultCommonContent) - .endCell(); - - royaltyParams = { - $$type: "RoyaltyParams", - nominator: 1n, - dominator: 100n, - owner: owner.address, - }; - - collectionNFT = blockchain.openContract( - await NFTCollection.fromInit( - owner.address, - 0n, - defaultContent, - royaltyParams, - ), - ); - const deployCollectionMsg: GetRoyaltyParams = { - $$type: "GetRoyaltyParams", - queryId: 0n, - }; - - const deployResult = await collectionNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - deployCollectionMsg, - ); - await step( - "Check that deployResult.transactions has correct transaction (collection)", - () => { - expect(deployResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - deploy: true, - success: true, - }); - }, - ); - }); - - it("should deploy correctly", async () => { - // checking in beforeEach - }); - - it("should get static data correctly", async () => { - const staticData = await collectionNFT.getGetCollectionData(); - await step( - "Check that staticData.owner equals owner.address (collection)", - () => { - expect(staticData.owner).toEqualAddress(owner.address); - }, - ); - await step( - "Check that staticData.nextItemIndex is 0 (collection)", - () => { - expect(staticData.nextItemIndex).toBe(0n); - }, - ); - await step( - "Check that staticData.collectionContent equals defaultCollectionContent (collection)", - () => { - expect(staticData.collectionContent).toEqualCell( - defaultCollectionContent, - ); - }, - ); - }); - - it("should get nft content correctly", async () => { - const content = await collectionNFT.getGetNftContent( - 0n, - defaultContent, - ); - const expectedContent = beginCell() - .storeUint(1, 8) - .storeSlice(defaultCommonContent.asSlice()) - .storeRef(defaultContent) - .endCell(); - await step( - "Check that content equals expectedContent (nft content)", - () => { - expect(content).toEqualCell(expectedContent); - }, - ); - }); - - describe("ROYALTY TESTS", () => { - it("test royalty msg", async () => { - const queryId = randomInt(TestValues.RandomRange) + 1; - - const msg: GetRoyaltyParams = { - $$type: "GetRoyaltyParams", - queryId: BigInt(queryId), - }; - - const trxResult = await collectionNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - msg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (royalty msg)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - }, - ); - - const exceptedMsg: Cell = beginCell() - .storeUint(Operations.ReportRoyaltyParams, 32) - .storeUint(queryId, 64) - .storeUint(royaltyParams.nominator, 16) - .storeUint(royaltyParams.dominator, 16) - .storeAddress(royaltyParams.owner) - .endCell(); - expect(trxResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: owner.address, - body: exceptedMsg, - }); - }); - - it("test royalty getter", async () => { - const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); - expect( - beginCell() - .store(storeRoyaltyParams(currRoyaltyParams)) - .asSlice(), - ).toEqualSlice( - beginCell().store(storeRoyaltyParams(royaltyParams)).asSlice(), - ); - }); - }); - - describe("NFT DEPLOY TESTS", () => { - it("should deploy NFTItem correctly", async () => { - // checking in beforeEach - }); - - /** - * Helper function to deploy an NFT item - * @param itemIndex - Index of the NFT to deploy - * @param collectionNFT - Collection contract instance - * @param sender - Sender of the deployment transaction - * @param owner - Owner of the deployed NFT - * @returns Promise resolving to the NFT item contract and transaction result - */ - const deployNFT = async ( - itemIndex: bigint, - collectionNFT: SandboxContract, - sender: SandboxContract, - owner: SandboxContract, - ): Promise<[SandboxContract, SendMessageResult]> => { - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - const mintMsg: DeployNFT = { - $$type: "DeployNFT", - queryId: 1n, - itemIndex: itemIndex, - amount: Storage.NftMintAmount, - initNFTBody: beginCell() - .store(storeInitNFTBody(initNFTBody)) - .endCell(), - }; - - const itemNFT = blockchain.openContract( - await NFTItem.fromInit( - null, - null, - collectionNFT.address, - itemIndex, - ), - ); - - const trxResult = await collectionNFT.send( - sender.getSender(), - { value: Storage.DeployAmount }, - mintMsg, - ); - return [itemNFT, trxResult]; - }; - - it("should mint NFTItem correctly", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [itemNFT, _trx] = await deployNFT( - nextItemIndex, - collectionNFT, - owner, - owner, - ); - const nftData = await itemNFT.getGetNftData(); - - await step( - "Check that nftData.content equals defaultNFTContent", - () => { - expect(nftData.content).toEqualCell(defaultNFTContent); - }, - ); - await step("Check that nftData.owner equals owner.address", () => { - expect(nftData.owner).toEqualAddress(owner.address); - }); - await step("Check that nftData.itemIndex is nextItemIndex", () => { - expect(nftData.itemIndex).toBe(nextItemIndex); - }); - await step( - "Check that nftData.collectionAddress equals collectionNFT.address", - () => { - expect(nftData.collectionAddress).toEqualAddress( - collectionNFT.address, - ); - }, - ); - }); - - it("should not mint NFTItem if not owner", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [_itemNFT, trx] = await deployNFT( - nextItemIndex, - collectionNFT, - notOwner, - notOwner, - ); - await step( - "Check that trx.transactions has correct transaction (not owner mint)", - () => { - expect(trx.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - - it("should not deploy previous nft", async () => { - let nextItemIndex: bigint = await collectionNFT.getNextItemIndex(); - for (let i = 0; i < 10; i++) { - const [_itemNFT, _trx] = await deployNFT( - nextItemIndex, - collectionNFT, - owner, - owner, - ); - nextItemIndex++; - } - const [_itemNFT, trx] = await deployNFT( - 0n, - collectionNFT, - owner, - owner, - ); - await step( - "Check that trx.transactions has correct transaction (should not deploy previous nft)", - () => { - expect(trx.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: _itemNFT.address, - deploy: false, - success: false, - exitCode: ErrorCodes.InvalidData, - }); - }, - ); - }); - - it("shouldn't mint item itemIndex > nextItemIndex", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [_itemNFT, trx] = await deployNFT( - nextItemIndex + 1n, - collectionNFT, - owner, - owner, - ); - await step( - "Check that trx.transactions has correct transaction (itemIndex > nextItemIndex)", - () => { - expect(trx.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.IncorrectIndex, - }); - }, - ); - }); - - it("test get nft by itemIndex", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - // deploy new nft to get itemIndex - const [_itemNFT, _trx] = await deployNFT( - nextItemIndex, - collectionNFT, - owner, - owner, - ); - const nftAddress = - await collectionNFT.getGetNftAddressByIndex(nextItemIndex); - const newNFT = blockchain.getContract(nftAddress); - const getData = await (await newNFT).get("get_nft_data"); - const dataNFT = loadGetterTupleNFTData(getData.stack); - await step("Check that dataNFT.itemIndex is nextItemIndex", () => { - expect(dataNFT.itemIndex).toBe(nextItemIndex); - }); - await step( - "Check that dataNFT.collectionAddress equals collectionNFT.address", - () => { - expect(dataNFT.collectionAddress).toEqualAddress( - collectionNFT.address, - ); - }, - ); - }); - }); - - describe("BATCH MINT TESTS", () => { - /** - * Helper function to batch mint NFTs - * @param collectionNFT - Collection contract instance - * @param sender - Sender of the batch mint transaction - * @param owner - Owner of the minted NFTs - * @param count - Number of NFTs to mint - * @param extra - Optional extra index to mint - * @returns Promise resolving to the transaction result - */ - const batchMintNFTProcess = async ( - collectionNFT: SandboxContract, - sender: SandboxContract, - owner: SandboxContract, - count: bigint, - extra: bigint = -1n, - ): Promise => { - const dct = Dictionary.empty( - Dictionary.Keys.BigUint(64), - dictDeployNFTItem, - ); - let i: bigint = 0n; - - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - while (i < count) { - dct.set(i, { - amount: Storage.NftMintAmount, - initNFTBody: initNFTBody, - }); - i += 1n; - } - - if (extra != -1n) { - dct.set(extra, { - amount: Storage.NftMintAmount, - initNFTBody: initNFTBody, - }); - } - - const batchMintNFT: BatchDeploy = { - $$type: "BatchDeploy", - queryId: 0n, - deployList: beginCell().storeDictDirect(dct).endCell(), - }; - - return await collectionNFT.send( - sender.getSender(), - { - value: - Storage.BatchDeployAmount * - (count + TestValues.ExtraValues.BatchMultiplier), - }, - batchMintNFT, - ); - }; - beforeEach(async () => {}); - - it.skip("test max batch mint", async () => { - let L = 1n; - let R = 1000n; - while (R - L > 1) { - const M = (L + R) / 2n; - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - M, - ); - try { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - L = M; - } catch { - R = M; - } - } - console.log("maximum batch amount is", L); - }); - - it("Should batch mint correctly", async () => { - const count = 50n; - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - count, - ); - - await step( - "Check that trxResult.transactions has correct transaction (batch mint success)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - }, - ); - itemNFT = blockchain.openContract( - await NFTItem.fromInit( - null, - null, - collectionNFT.address, - count - 1n, - ), - ); - - // it was deployed, that's why we can get it - await step( - "Check that itemNFT.getGetNftData() has property itemIndex", - async () => { - expect(await itemNFT.getGetNftData()).toHaveProperty( - "itemIndex", - count - 1n, - ); - }, - ); - }); - - it("Shouldn't batch mint more than 250 items", async () => { - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - 260n, - ); - - await step( - "Check that trxResult.transactions has correct transaction (should not batch mint more than 250)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - }); - }, - ); - }); - - it("Should not batch mint not owner", async () => { - const trxResult = await batchMintNFTProcess( - collectionNFT, - notOwner, - owner, - 10n, - ); - - await step( - "Check that trxResult.transactions has correct transaction (not owner batch mint)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - describe("!!--DIFF TEST---!!", () => { - it("Should HAVE message in batchDeploy with previous indexes", async () => { - await batchMintNFTProcess(collectionNFT, owner, owner, 50n); - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - 50n, - ); - - itemNFT = blockchain.openContract( - await NFTItem.fromInit( - null, - null, - collectionNFT.address, - 10n, - ), - ); // random number - - await step( - "Check that trxResult.transactions has correct transaction (batchDeploy with previous indexes)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: itemNFT.address, - }); - }, - ); - }); - - it("Should THROW if we have index > nextItemIndex", async () => { - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - 50n, - 70n, - ); - - await step( - "Check that trxResult.transactions has correct transaction (index > nextItemIndex)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - }); - }, - ); - }); - }); - }); - - describe("TRANSFER OWNERSHIP TEST", () => { - it("Owner should be able to transfer ownership", async () => { - const changeOwnerMsg: ChangeOwner = { - $$type: "ChangeOwner", - queryId: 1n, - newOwner: notOwner.address, - }; - - const trxResult = await collectionNFT.send( - owner.getSender(), - { value: Storage.ChangeOwnerAmount }, - changeOwnerMsg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (owner transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - }, - ); - await step( - "Check that collectionNFT.getOwner() equals notOwner.address", - async () => { - expect(await collectionNFT.getOwner()).toEqualAddress( - notOwner.address, - ); - }, - ); - }); - it("Not owner should not be able to transfer ownership", async () => { - const changeOwnerMsg: ChangeOwner = { - $$type: "ChangeOwner", - queryId: 1n, - newOwner: owner.address, - }; - - const trxResult = await collectionNFT.send( - notOwner.getSender(), - { value: Storage.ChangeOwnerAmount }, - changeOwnerMsg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (not owner transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - }); -}); diff --git a/src/benchmarks/nft/run.ts b/src/benchmarks/nft/run.ts new file mode 100644 index 0000000000..12c1c7ec06 --- /dev/null +++ b/src/benchmarks/nft/run.ts @@ -0,0 +1,131 @@ +import "@ton/test-utils"; +import type { Address } from "@ton/core"; +import { Cell, beginCell, contractAddress } from "@ton/core"; + +import { + generateResults, + generateCodeSizeResults, + printBenchmarkTable, + type BenchmarkResult, + type CodeSizeResult, +} from "@/benchmarks/utils/gas"; +import { resolve } from "path"; +import { readFileSync } from "fs"; +import { posixNormalize } from "@/utils/filePath"; + +import { NFTCollection } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import type { RoyaltyParams } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { NFTItem } from "@/benchmarks/nft/tact/output/collection_NFTItem"; + +import benchmarkResults from "@/benchmarks/nft/results_gas.json"; +import benchmarkCodeSizeResults from "@/benchmarks/nft/results_code_size.json"; + +const loadFunCNFTBoc = () => { + const bocCollection = readFileSync( + posixNormalize(resolve(__dirname, "./func/output/nft-collection.boc")), + ); + + const bocItem = readFileSync( + posixNormalize(resolve(__dirname, "./func/output/nft-item.boc")), + ); + + return { bocCollection, bocItem }; +}; + +export const run = ( + testNFT: ( + benchmarkResults: BenchmarkResult, + codeSizeResults: CodeSizeResult, + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, + ) => void, +) => { + describe("NFT Gas Tests", () => { + const fullResults = generateResults(benchmarkResults); + const fullCodeSizeResults = generateCodeSizeResults( + benchmarkCodeSizeResults, + ); + + describe("func", () => { + const funcCodeSize = fullCodeSizeResults.at(0)!; + const funcResult = fullResults.at(0)!; + + function fromInitCollection( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) { + const nftData = loadFunCNFTBoc(); + const __code = Cell.fromBoc(nftData.bocCollection)[0]!; + + const royaltyCell = beginCell() + .storeUint(royaltyParams.nominator, 16) + .storeUint(royaltyParams.dominator, 16) + .storeAddress(royaltyParams.owner) + .endCell(); + + const __data = beginCell() + .storeAddress(owner) + .storeUint(index, 64) + .storeRef(content) + .storeRef(Cell.fromBoc(nftData.bocItem)[0]!) + .storeRef(royaltyCell) + .endCell(); + + const __gen_init = { code: __code, data: __data }; + const address = contractAddress(0, __gen_init); + return Promise.resolve(new NFTCollection(address, __gen_init)); + } + + function fromInitItem( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) { + const nftData = loadFunCNFTBoc(); + const __code = Cell.fromBoc(nftData.bocItem)[0]!; + + const __data = beginCell() + .storeUint(itemIndex, 64) + .storeAddress(collectionAddress) + .endCell(); + + const __gen_init = { code: __code, data: __data }; + const address = contractAddress(0, __gen_init); + return Promise.resolve(new NFTItem(address, __gen_init)); + } + + testNFT(funcResult, funcCodeSize, fromInitCollection, fromInitItem); + }); + + describe("tact", () => { + const tactCodeSize = fullCodeSizeResults.at(-1)!; + const tactResult = fullResults.at(-1)!; + testNFT( + tactResult, + tactCodeSize, + NFTCollection.fromInit.bind(NFTCollection), + NFTItem.fromInit.bind(NFTItem), + ); + }); + + afterAll(() => { + printBenchmarkTable(fullResults, fullCodeSizeResults, { + implementationName: "FunC", + printMode: "full", + }); + }); + }); +}; diff --git a/src/benchmarks/nft/test.spec.ts b/src/benchmarks/nft/test.spec.ts new file mode 100644 index 0000000000..087752aa8a --- /dev/null +++ b/src/benchmarks/nft/test.spec.ts @@ -0,0 +1,4 @@ +import { bench } from "@/benchmarks/nft/bench"; +import { run } from "@/benchmarks/nft/run"; + +run(bench); diff --git a/src/benchmarks/nft/tests/test.ts b/src/benchmarks/nft/tests/test.ts new file mode 100644 index 0000000000..dc028b1762 --- /dev/null +++ b/src/benchmarks/nft/tests/test.ts @@ -0,0 +1,1458 @@ +// Type imports +import type { + Address, + Slice, + ContractProvider, + Cell, + Sender, + Builder, + TupleItem, + TupleItemInt, + TupleItemSlice, + TupleItemCell, +} from "@ton/core"; +// Value imports +import { beginCell, toNano, Dictionary } from "@ton/core"; + +import type { + SandboxContract, + TreasuryContract, + SendMessageResult, +} from "@ton/sandbox"; + +import { Blockchain } from "@ton/sandbox"; + +// NFT Collection imports +import type { + DeployNFT, + GetRoyaltyParams, + GetStaticData, + BatchDeploy, + RoyaltyParams, + InitNFTBody, + ChangeOwner, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { + storeRoyaltyParams, + loadInitNFTBody, + NFTCollection, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; + +// NFT Item imports +import type { + Transfer, + NFTData, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; +import { + storeInitNFTBody, + NFTItem, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; + +import "@ton/test-utils"; +import { randomInt } from "crypto"; + +import { setStoragePrices } from "@/test/utils/gasUtils"; + +import { step } from "@/test/allure/allure"; + +/** Operation codes for NFT contract messages */ +const Operations = { + TransferNft: 0x5fcc3d14, + OwnershipAssignment: 0x05138d91, + Excess: 0xd53276db, + GetStaticData: 0x2fcb26a2, + ReportStaticData: 0x8b771735, + GetRoyaltyParams: 0x693d3950, + ReportRoyaltyParams: 0xa8cb00ad, + EditContent: 0x1a0b9d51, + TransferEditorship: 0x1c04412a, + EditorshipAssigned: 0x511a4463, +} as const; + +/** Storage and transaction related constants */ +const Storage = { + /** Minimum amount of TONs required for storage */ + MinTons: 50000000n, + /** Amount of TONs for deployment operations */ + DeployAmount: toNano("0.1"), + /** Amount of TONs for transfer operations */ + TransferAmount: toNano("1"), + /** Amount of TONs for batch deployment */ + BatchDeployAmount: toNano("100"), + /** Amount of TONs for ownership change */ + ChangeOwnerAmount: 100000000n, + /** Amount of TONs for NFT minting */ + NftMintAmount: 10000000n, + /** Forward fee values */ + ForwardFee: { + /** Base forward fee for single message */ + Base: 623605n, + /** Forward fee for double message */ + Double: 729606n, + }, +} as const; + +/** Error codes */ +const ErrorCodes = { + /** Error code for not initialized contract */ + NotInit: 9, + /** Error code for not owner */ + NotOwner: 401, + /** Error code for invalid fees */ + InvalidFees: 402, + /** Error code for incorrect index */ + IncorrectIndex: 402, + /** Error code for invalid data */ + InvalidData: 65535, +} as const; + +/** Test related constants */ +const TestValues = { + /** Default item index used in tests */ + ItemIndex: 100n, + /** Batch operation sizes */ + BatchSize: { + Min: 1n, + Max: 250n, + Default: 50n, + OverLimit: 260n, + Small: 10n, + }, + /** Range for random number generation */ + RandomRange: 1337, + /** Royalty parameters */ + Royalty: { + Nominator: 1n, + Dominator: 100n, + }, + /** Bit sizes for different data types */ + BitSizes: { + Uint1: 1, + Uint8: 8, + Uint16: 16, + Uint32: 32, + Uint64: 64, + }, + /** Additional test values */ + ExtraValues: { + BatchMultiplier: 10n, + }, +} as const; + +/** Dictionary type for NFT deployment data */ +export type dictDeployNFT = { + amount: bigint; + initNFTBody: InitNFTBody; +}; + +/** Dictionary value parser for NFT deployment */ +export const dictDeployNFTItem = { + serialize: (src: dictDeployNFT, builder: Builder) => { + builder + .storeCoins(src.amount) + .storeRef( + beginCell().store(storeInitNFTBody(src.initNFTBody)).endCell(), + ); + }, + parse: (src: Slice) => { + return { + amount: src.loadCoins(), + initNFTBody: loadInitNFTBody(src.loadRef().asSlice()), + }; + }, +}; + +const minTonsForStorage = Storage.MinTons; + +/** + * Sends a transfer message to an NFT item contract + * @param itemNFT - The NFT item contract instance + * @param from - The sender of the transfer + * @param value - Amount of TONs to send + * @param newOwner - Address of the new owner + * @param responseDestination - Address to send the response to + * @param forwardAmount - Amount of TONs to forward + * @param forwardPayload - Optional payload to forward + * @returns Promise resolving to the transaction result + */ +const sendTransfer = async ( + itemNFT: SandboxContract, + from: Sender, + value: bigint, + newOwner: Address, + responseDestination: Address | null, + forwardAmount: bigint, + forwardPayload: Slice = beginCell().storeUint(0, 1).asSlice(), +): Promise => { + const msg: Transfer = { + $$type: "Transfer", + queryId: 0n, + newOwner: newOwner, + responseDestination: responseDestination, + customPayload: null, + forwardAmount: forwardAmount, + forwardPayload: forwardPayload, + }; + + return await itemNFT.send(from, { value }, msg); +}; + +/** + * Helper function to load NFT data from a tuple of contract getter results + * @param source - Array of tuple items containing NFT data + * @returns Parsed NFT data object + */ +function loadGetterTupleNFTData(source: TupleItem[]): NFTData { + const _init = (source[0] as TupleItemInt).value; + const _index = (source[1] as TupleItemInt).value; + const _collectionAddress = (source[2] as TupleItemSlice).cell + .asSlice() + .loadAddress(); + const _owner = (source[3] as TupleItemSlice).cell.asSlice().loadAddress(); + const _content = (source[4] as TupleItemCell).cell; + return { + $$type: "NFTData" as const, + init: _init, + itemIndex: _index, + collectionAddress: _collectionAddress, + owner: _owner, + content: _content, + }; +} + +/** + * Extends NFTItem interface with owner getter functionality + */ +declare module "@/benchmarks/nft/tact/output/item_NFTItem" { + interface NFTItem { + /** + * Gets the current owner of the NFT + * @param provider - Contract provider instance + * @returns Promise resolving to owner's address or null if not initialized + */ + getOwner(provider: ContractProvider): Promise
; + } +} + +// function getOwner(provider: ContractProvider): Promise
{ +// return provider.get("getOwner", []); +// } + +/** + * Extends NFTCollection interface with additional getter functionality + */ +declare module "@/benchmarks/nft/tact/output/collection_NFTCollection" { + interface NFTCollection { + /** + * Gets the next available item index for minting + * @param provider - Contract provider instance + * @returns Promise resolving to the next item index + */ + getNextItemIndex(provider: ContractProvider): Promise; + + /** + * Gets the current owner of the collection + * @param provider - Contract provider instance + * @returns Promise resolving to owner's address + */ + getOwner(provider: ContractProvider): Promise
; + } +} + +NFTItem.prototype.getOwner = async function ( + this: NFTItem, + provider: ContractProvider, +): Promise
{ + const res = await this.getGetNftData(provider); + return res.owner; +}; + +export function test( + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) { + describe("NFT Item Contract", () => { + let blockchain: Blockchain; + let itemNFT: SandboxContract; + let owner: SandboxContract; + let notOwner: SandboxContract; + let defaultContent: Cell; + let emptyAddress: Address | null; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + + const config = blockchain.config; + blockchain.setConfig( + setStoragePrices(config, { + unixTimeSince: 0, + bitPricePerSecond: 0n, + cellPricePerSecond: 0n, + masterChainBitPricePerSecond: 0n, + masterChainCellPricePerSecond: 0n, + }), + ); + + owner = await blockchain.treasury("owner"); + notOwner = await blockchain.treasury("notOwner"); + + emptyAddress = null; + defaultContent = beginCell().endCell(); // just some content ( doesn't matter ) + + itemNFT = blockchain.openContract( + await fromInitItem(null, null, owner.address, 0n), + ); + const deployItemMsg: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultContent, + }; + + const deployResult = await itemNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), + ); + + await step( + "Check that deployResult.transactions has correct transaction", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + deploy: true, + success: true, + }); + }, + ); + }); + + const messageGetStaticData = async ( + sender: SandboxContract, + itemNFT: SandboxContract, + ) => { + const msg: GetStaticData = { + $$type: "GetStaticData", + queryId: 1n, + }; + + const trxResult = await itemNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + return trxResult; + }; + + it("should deploy correctly", async () => { + // checking in beforeEach + }); + + it("should get nft data correctly", async () => { + const staticData = await itemNFT.getGetNftData(); + + await step("Check that staticData.init is -1", () => { + expect(staticData.init).toBe(-1n); + }); + await step("Check that staticData.itemIndex is 0", () => { + expect(staticData.itemIndex).toBe(0n); + }); + await step( + "Check that staticData.collectionAddress equals owner.address", + () => { + expect(staticData.collectionAddress).toEqualAddress( + owner.address, + ); + }, + ); + await step( + "Check that staticData.owner equals owner.address", + () => { + expect(staticData.owner).toEqualAddress(owner.address); + }, + ); + await step( + "Check that staticData.content equals defaultContent", + () => { + expect(staticData.content).toEqualCell(defaultContent); + }, + ); + }); + + it("should get static data correctly", async () => { + const trxResult = await messageGetStaticData(owner, itemNFT); + await step( + "Check that trxResult.transactions has correct transaction (get static data)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + }); + + describe("Transfer ownership Fee cases", () => { + let balance: bigint; + let fwdFee: bigint; + + beforeEach(async () => { + balance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + fwdFee = Storage.ForwardFee.Base; + }); + + it("Transfer forward amount too much", async () => { + // NFT should reject transfer if balance lower than forward_amount + message forward fee + minimal storage fee + // Sending message with forward_amount of 1 TON and balance 0.1 TON + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + Storage.TransferAmount, + ); + await step( + "Check that trxResult.transactions has correct transaction (transfer forward amount too much)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); + }); + + it("test transfer storage fee", async () => { + // Now let's try forward_amount exactly equal to balance and fwd_fee 0 + // 1 TON Balance forward_amount:1 TON fwd_fee:0 (just add to transfer value) verifying that minimal storage comes into play + // Should fail with no actions + + // [] and {} just kinds of () for more understandable description + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + fwdFee, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance, + ); // balance + 1ton + fwd - (1ton + balance) = [0] + {fwdFee} and [0] < [minTonsForStorage] + await step( + "Check that trxResult.transactions has correct transaction (test transfer storage fee)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); + }); + it("test transfer forward fee 2.0", async () => { + // Let's verify that storage fee was an error trigger by increasing balance by min_storage + // Expect success + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + minTonsForStorage + fwdFee, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance, + ); // balance + 1ton + minTonsForStorage + fwdFee - (1ton + balance) = [minTonsForStorage] + {fwdFee} + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + await step( + "Check that trxResult.transactions has correct transaction (test transfer forward fee 2.0)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + balance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + await step( + "Check that balance is less than minTonsForStorage (test transfer forward fee 2.0)", + () => { + expect(balance).toBeLessThan(minTonsForStorage); + }, + ); + }); + + it("test transfer forward fee single", async () => { + // If transfer is successful NFT supposed to send up to 2 messages + // 1)To the owner_address with forward_amount of coins + // 2)To the response_addr with forward_payload if response_addr is not addr_none + // Each of those messages costs fwd_fee + // In this case we test scenario where only single message required to be sent; + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + Storage.ForwardFee.Base, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance - minTonsForStorage, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); // balance + 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} + + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + await step( + "Check that trxResult.transactions has correct transaction (test transfer forward fee single)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + }); + + describe("test transfer forward fee double", function () { + beforeEach(() => { + fwdFee = Storage.ForwardFee.Double; + }); + it("should false with only one fwd fee on balance", async () => { + // If transfer is successful NFT supposed to send up to 2 messages + // 1)To the owner_address with forward_amount of coins + // 2)To the response_addr with forward_payload if response_addr is not addr_none + // Each of those messages costs fwd_fee + // In this case we test scenario where both messages required to be sent but balance has funs only for single message + // To do so resp_dst has be a valid address not equal to addr_none + + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + fwdFee, + notOwner.address, + owner.address, + Storage.TransferAmount + balance - Storage.MinTons, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); + + // 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} and {fwdFee} < {2 * fwdFee} + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + await step( + "Check that trxResult.transactions has correct transaction (double forward fee, not enough for both)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); + }); + + // let now check if we have 2 fwdFees on balance + it("should work with 2 fwdFee on balance", async () => { + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + 2n * fwdFee, + notOwner.address, + owner.address, + Storage.TransferAmount + balance - minTonsForStorage, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); + // 1ton + 2 * fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {2 * fwdFee} + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + balance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + expect(balance).toBeLessThan(minTonsForStorage); + await step( + "Check that trxResult.transactions has correct transaction (double forward fee, enough for both)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + await step( + "Check that balance is less than minTonsForStorage (double forward fee)", + () => { + expect(balance).toBeLessThan(minTonsForStorage); + }, + ); + }); + }); + // int __test_transfer_success_forward_no_response testing in next test suite + }); + + describe("Transfer Ownership Tests", () => { + it("Test ownership assigned", async () => { + const oldOwner = await itemNFT.getOwner(); + await step("Check that oldOwner equals owner.address", () => { + expect(oldOwner).toEqualAddress(owner.address); + }); + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + owner.address, + 1n, + ); + + const newOwner = await itemNFT.getOwner(); + await step( + "Check that newOwner equals notOwner.address", + () => { + expect(newOwner).toEqualAddress(notOwner.address); + }, + ); + await step( + "Check that trxRes.transactions has correct transaction (ownership assigned)", + () => { + expect(trxRes.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + }); + + it("Test transfer ownership without any messages", async () => { + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + 0n, + ); + const newOwner = await itemNFT.getOwner(); + await step( + "Check that newOwner equals notOwner.address (no messages)", + () => { + expect(newOwner).toEqualAddress(notOwner.address); + }, + ); + await step( + "Check that trxRes.transactions does NOT have transaction from itemNFT.address (no messages)", + () => { + expect(trxRes.transactions).not.toHaveTransaction({ + from: itemNFT.address, + }); + }, + ); + }); + + it("Not owner should not be able to transfer ownership", async () => { + const trxResult = await sendTransfer( + itemNFT, + notOwner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + 0n, + ); + await step( + "Check that trxResult.transactions has correct transaction (not owner should not be able to transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + }); + + describe("NOT INITIALIZED TESTS", () => { + const itemIndex: bigint = 100n; + beforeEach(async () => { + itemNFT = blockchain.openContract( + await fromInitItem(null, null, owner.address, itemIndex), + ); + const _deployResult = await itemNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + beginCell().asSlice(), + ); + }); + + it("should not get static data", async () => { + const staticData = await itemNFT.getGetNftData(); + await step( + "Check that staticData.init is 0 (not initialized)", + () => { + expect(staticData.init).toBe(0n); + }, + ); + await step( + "Check that staticData.collectionAddress equals owner.address (not initialized)", + () => { + expect(staticData.collectionAddress).toEqualAddress( + owner.address, + ); + }, + ); + await step( + "Check that staticData.owner is null (not initialized)", + () => { + expect(staticData.owner).toBeNull(); + }, + ); + await step( + "Check that staticData.itemIndex is itemIndex (not initialized)", + () => { + expect(staticData.itemIndex).toBe(itemIndex); + }, + ); + await step( + "Check that staticData.content is null (not initialized)", + () => { + expect(staticData.content).toBeNull(); + }, + ); + }); + + it("should not transfer ownership", async () => { + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + owner.address, + 0n, + ); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotInit, + }); + }); + + it("should not get static data message", async () => { + const trxResult = await messageGetStaticData(owner, itemNFT); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotInit, + }); + }); + }); + }); + + NFTCollection.prototype.getNextItemIndex = async function ( + this: NFTCollection, + provider: ContractProvider, + ): Promise { + const res = await this.getGetCollectionData(provider); + return res.nextItemIndex; + }; + + NFTCollection.prototype.getOwner = async function ( + this: NFTCollection, + provider: ContractProvider, + ): Promise
{ + const res = await this.getGetCollectionData(provider); + return res.owner; + }; + + describe("NFT Collection Contract", () => { + let blockchain: Blockchain; + let collectionNFT: SandboxContract; + let itemNFT: SandboxContract; + + let owner: SandboxContract; + let notOwner: SandboxContract; + + let defaultContent: Cell; + let defaultCommonContent: Cell; + let defaultCollectionContent: Cell; + let defaultNFTContent: Cell; + let royaltyParams: RoyaltyParams; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + owner = await blockchain.treasury("owner"); + notOwner = await blockchain.treasury("notOwner"); + + defaultCommonContent = beginCell() + .storeStringTail("common") + .endCell(); + defaultCollectionContent = beginCell() + .storeStringTail("collectionContent") + .endCell(); + + defaultNFTContent = beginCell().storeStringTail("1.json").endCell(); + + defaultContent = beginCell() + .storeRef(defaultCollectionContent) + .storeRef(defaultCommonContent) + .endCell(); + + royaltyParams = { + $$type: "RoyaltyParams", + nominator: 1n, + dominator: 100n, + owner: owner.address, + }; + + collectionNFT = blockchain.openContract( + await fromInitCollection( + owner.address, + 0n, + defaultContent, + royaltyParams, + ), + ); + const deployCollectionMsg: GetRoyaltyParams = { + $$type: "GetRoyaltyParams", + queryId: 0n, + }; + + const deployResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + deployCollectionMsg, + ); + await step( + "Check that deployResult.transactions has correct transaction (collection)", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + deploy: true, + success: true, + }); + }, + ); + }); + + it("should deploy correctly", async () => { + // checking in beforeEach + }); + + it("should get static data correctly", async () => { + const staticData = await collectionNFT.getGetCollectionData(); + await step( + "Check that staticData.owner equals owner.address (collection)", + () => { + expect(staticData.owner).toEqualAddress(owner.address); + }, + ); + await step( + "Check that staticData.nextItemIndex is 0 (collection)", + () => { + expect(staticData.nextItemIndex).toBe(0n); + }, + ); + await step( + "Check that staticData.collectionContent equals defaultCollectionContent (collection)", + () => { + expect(staticData.collectionContent).toEqualCell( + defaultCollectionContent, + ); + }, + ); + }); + + it("should get nft content correctly", async () => { + const content = await collectionNFT.getGetNftContent( + 0n, + defaultContent, + ); + const expectedContent = beginCell() + .storeUint(1, 8) + .storeSlice(defaultCommonContent.asSlice()) + .storeRef(defaultContent) + .endCell(); + await step( + "Check that content equals expectedContent (nft content)", + () => { + expect(content).toEqualCell(expectedContent); + }, + ); + }); + + describe("ROYALTY TESTS", () => { + it("test royalty msg", async () => { + const queryId = randomInt(TestValues.RandomRange) + 1; + + const msg: GetRoyaltyParams = { + $$type: "GetRoyaltyParams", + queryId: BigInt(queryId), + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (royalty msg)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + + const exceptedMsg: Cell = beginCell() + .storeUint(Operations.ReportRoyaltyParams, 32) + .storeUint(queryId, 64) + .storeUint(royaltyParams.nominator, 16) + .storeUint(royaltyParams.dominator, 16) + .storeAddress(royaltyParams.owner) + .endCell(); + expect(trxResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: owner.address, + body: exceptedMsg, + }); + }); + + it("test royalty getter", async () => { + const currRoyaltyParams = + await collectionNFT.getRoyaltyParams(); + expect( + beginCell() + .store(storeRoyaltyParams(currRoyaltyParams)) + .asSlice(), + ).toEqualSlice( + beginCell() + .store(storeRoyaltyParams(royaltyParams)) + .asSlice(), + ); + }); + }); + + describe("NFT DEPLOY TESTS", () => { + it("should deploy NFTItem correctly", async () => { + // checking in beforeEach + }); + + /** + * Helper function to deploy an NFT item + * @param itemIndex - Index of the NFT to deploy + * @param collectionNFT - Collection contract instance + * @param sender - Sender of the deployment transaction + * @param owner - Owner of the deployed NFT + * @returns Promise resolving to the NFT item contract and transaction result + */ + const deployNFT = async ( + itemIndex: bigint, + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + ): Promise<[SandboxContract, SendMessageResult]> => { + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + const mintMsg: DeployNFT = { + $$type: "DeployNFT", + queryId: 1n, + itemIndex: itemIndex, + amount: Storage.NftMintAmount, + initNFTBody: beginCell() + .store(storeInitNFTBody(initNFTBody)) + .endCell(), + }; + + const itemNFT = blockchain.openContract( + await fromInitItem( + null, + null, + collectionNFT.address, + itemIndex, + ), + ); + + const trxResult = await collectionNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + mintMsg, + ); + return [itemNFT, trxResult]; + }; + + it("should mint NFTItem correctly", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + const [itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + ); + const nftData = await itemNFT.getGetNftData(); + + await step( + "Check that nftData.content equals defaultNFTContent", + () => { + expect(nftData.content).toEqualCell(defaultNFTContent); + }, + ); + await step( + "Check that nftData.owner equals owner.address", + () => { + expect(nftData.owner).toEqualAddress(owner.address); + }, + ); + await step( + "Check that nftData.itemIndex is nextItemIndex", + () => { + expect(nftData.itemIndex).toBe(nextItemIndex); + }, + ); + await step( + "Check that nftData.collectionAddress equals collectionNFT.address", + () => { + expect(nftData.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }, + ); + }); + + it("should not mint NFTItem if not owner", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + const [_itemNFT, trx] = await deployNFT( + nextItemIndex, + collectionNFT, + notOwner, + notOwner, + ); + await step( + "Check that trx.transactions has correct transaction (not owner mint)", + () => { + expect(trx.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + + it("should not deploy previous nft", async () => { + let nextItemIndex: bigint = + await collectionNFT.getNextItemIndex(); + for (let i = 0; i < 10; i++) { + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + ); + nextItemIndex++; + } + const [_itemNFT, trx] = await deployNFT( + 0n, + collectionNFT, + owner, + owner, + ); + await step( + "Check that trx.transactions has correct transaction (should not deploy previous nft)", + () => { + expect(trx.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: _itemNFT.address, + deploy: false, + success: false, + exitCode: ErrorCodes.InvalidData, + }); + }, + ); + }); + + it("shouldn't mint item itemIndex > nextItemIndex", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + const [_itemNFT, trx] = await deployNFT( + nextItemIndex + 1n, + collectionNFT, + owner, + owner, + ); + await step( + "Check that trx.transactions has correct transaction (itemIndex > nextItemIndex)", + () => { + expect(trx.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.IncorrectIndex, + }); + }, + ); + }); + + it("test get nft by itemIndex", async () => { + const nextItemIndex = await collectionNFT.getNextItemIndex(); + // deploy new nft to get itemIndex + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + ); + const nftAddress = + await collectionNFT.getGetNftAddressByIndex(nextItemIndex); + const newNFT = blockchain.getContract(nftAddress); + const getData = await (await newNFT).get("get_nft_data"); + const dataNFT = loadGetterTupleNFTData(getData.stack); + await step( + "Check that dataNFT.itemIndex is nextItemIndex", + () => { + expect(dataNFT.itemIndex).toBe(nextItemIndex); + }, + ); + await step( + "Check that dataNFT.collectionAddress equals collectionNFT.address", + () => { + expect(dataNFT.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }, + ); + }); + }); + + describe("BATCH MINT TESTS", () => { + /** + * Helper function to batch mint NFTs + * @param collectionNFT - Collection contract instance + * @param sender - Sender of the batch mint transaction + * @param owner - Owner of the minted NFTs + * @param count - Number of NFTs to mint + * @param extra - Optional extra index to mint + * @returns Promise resolving to the transaction result + */ + const batchMintNFTProcess = async ( + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + count: bigint, + extra: bigint = -1n, + ): Promise => { + const dct = Dictionary.empty( + Dictionary.Keys.BigUint(64), + dictDeployNFTItem, + ); + let i: bigint = 0n; + + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + while (i < count) { + dct.set(i, { + amount: Storage.NftMintAmount, + initNFTBody: initNFTBody, + }); + i += 1n; + } + + if (extra != -1n) { + dct.set(extra, { + amount: Storage.NftMintAmount, + initNFTBody: initNFTBody, + }); + } + + const batchMintNFT: BatchDeploy = { + $$type: "BatchDeploy", + queryId: 0n, + deployList: beginCell().storeDictDirect(dct).endCell(), + }; + + return await collectionNFT.send( + sender.getSender(), + { + value: + Storage.BatchDeployAmount * + (count + TestValues.ExtraValues.BatchMultiplier), + }, + batchMintNFT, + ); + }; + beforeEach(async () => {}); + + it.skip("test max batch mint", async () => { + let L = 1n; + let R = 1000n; + while (R - L > 1) { + const M = (L + R) / 2n; + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + M, + ); + try { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + L = M; + } catch { + R = M; + } + } + console.log("maximum batch amount is", L); + }); + + it("Should batch mint correctly", async () => { + const count = 50n; + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + count, + ); + + await step( + "Check that trxResult.transactions has correct transaction (batch mint success)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + itemNFT = blockchain.openContract( + await fromInitItem( + null, + null, + collectionNFT.address, + count - 1n, + ), + ); + + // it was deployed, that's why we can get it + await step( + "Check that itemNFT.getGetNftData() has property itemIndex", + async () => { + expect(await itemNFT.getGetNftData()).toHaveProperty( + "itemIndex", + count - 1n, + ); + }, + ); + }); + + it("Shouldn't batch mint more than 250 items", async () => { + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + 260n, + ); + + await step( + "Check that trxResult.transactions has correct transaction (should not batch mint more than 250)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + }); + }, + ); + }); + + it("Should not batch mint not owner", async () => { + const trxResult = await batchMintNFTProcess( + collectionNFT, + notOwner, + owner, + 10n, + ); + + await step( + "Check that trxResult.transactions has correct transaction (not owner batch mint)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + describe("!!--DIFF TEST---!!", () => { + it("Should HAVE message in batchDeploy with previous indexes", async () => { + await batchMintNFTProcess(collectionNFT, owner, owner, 50n); + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + 50n, + ); + + itemNFT = blockchain.openContract( + await fromInitItem( + null, + null, + collectionNFT.address, + 10n, + ), + ); // random number + + await step( + "Check that trxResult.transactions has correct transaction (batchDeploy with previous indexes)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: itemNFT.address, + }); + }, + ); + }); + + it("Should THROW if we have index > nextItemIndex", async () => { + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + 50n, + 70n, + ); + + await step( + "Check that trxResult.transactions has correct transaction (index > nextItemIndex)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + }); + }, + ); + }); + }); + }); + + describe("TRANSFER OWNERSHIP TEST", () => { + it("Owner should be able to transfer ownership", async () => { + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: notOwner.address, + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + await step( + "Check that collectionNFT.getOwner() equals notOwner.address", + async () => { + expect(await collectionNFT.getOwner()).toEqualAddress( + notOwner.address, + ); + }, + ); + }); + it("Not owner should not be able to transfer ownership", async () => { + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: owner.address, + }; + + const trxResult = await collectionNFT.send( + notOwner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (not owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + }); + }); +} From f55983e148026c6c9939b3779c4f91f284e63bfa Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Sun, 25 May 2025 16:59:18 +0300 Subject: [PATCH 11/22] upd --- package.json | 4 +- .../escrow/{escrow.spec.ts => bench.spec.ts} | 0 src/benchmarks/jest-bench.config.js | 11 - src/benchmarks/jest-test.config.js | 7 - .../jetton/{jetton.spec.ts => bench.spec.ts} | 0 src/benchmarks/nft/bench.spec.ts | 372 ++++- src/benchmarks/nft/bench.ts | 366 ----- src/benchmarks/nft/run.ts | 94 +- src/benchmarks/nft/test.spec.ts | 64 +- src/benchmarks/nft/tests/collection.ts | 727 ++++++++ src/benchmarks/nft/tests/item.ts | 240 +++ src/benchmarks/nft/tests/test.ts | 1458 ----------------- src/benchmarks/nft/tests/transfer-fee.ts | 313 ++++ src/benchmarks/nft/tests/utils.ts | 203 +++ .../{notcoin.spec.ts => bench.spec.ts} | 0 .../sbt/{sbt.spec.ts => bench.spec.ts} | 0 src/benchmarks/update.build.ts | 2 +- .../{wallet-v4.spec.ts => bench.spec.ts} | 0 .../{wallet-v5.spec.ts => bench.spec.ts} | 0 19 files changed, 1964 insertions(+), 1897 deletions(-) rename src/benchmarks/escrow/{escrow.spec.ts => bench.spec.ts} (100%) delete mode 100644 src/benchmarks/jest-bench.config.js delete mode 100644 src/benchmarks/jest-test.config.js rename src/benchmarks/jetton/{jetton.spec.ts => bench.spec.ts} (100%) delete mode 100644 src/benchmarks/nft/bench.ts create mode 100644 src/benchmarks/nft/tests/collection.ts create mode 100644 src/benchmarks/nft/tests/item.ts delete mode 100644 src/benchmarks/nft/tests/test.ts create mode 100644 src/benchmarks/nft/tests/transfer-fee.ts create mode 100644 src/benchmarks/nft/tests/utils.ts rename src/benchmarks/notcoin/{notcoin.spec.ts => bench.spec.ts} (100%) rename src/benchmarks/sbt/{sbt.spec.ts => bench.spec.ts} (100%) rename src/benchmarks/wallet-v4/{wallet-v4.spec.ts => bench.spec.ts} (100%) rename src/benchmarks/wallet-v5/{wallet-v5.spec.ts => bench.spec.ts} (100%) diff --git a/package.json b/package.json index a0720025c2..9d88c030bf 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "test": "jest", "test:fast": "jest --config=./jest-fast.config.js", "test:allure": "rimraf ./allure-results && yarn test && allure serve allure-results", - "bench": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --config=./jest-bench.config.js", - "bench:test": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --config=./jest-test.config.js", + "bench": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --testMatch=\"**/src/benchmarks/**/bench.spec.ts\"", + "bench:test": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --testMatch=\"**/src/benchmarks/**/test.spec.ts\"", "bench:ci": "yarn gen:contracts:benchmarks && yarn bench && yarn bench:test", "bench:update": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true ts-node src/benchmarks/update.build.ts", "bench:add": "ts-node src/benchmarks/prompt.build.ts && yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true ADD=true ts-node src/benchmarks/update.build.ts", diff --git a/src/benchmarks/escrow/escrow.spec.ts b/src/benchmarks/escrow/bench.spec.ts similarity index 100% rename from src/benchmarks/escrow/escrow.spec.ts rename to src/benchmarks/escrow/bench.spec.ts diff --git a/src/benchmarks/jest-bench.config.js b/src/benchmarks/jest-bench.config.js deleted file mode 100644 index 2b65592414..0000000000 --- a/src/benchmarks/jest-bench.config.js +++ /dev/null @@ -1,11 +0,0 @@ -const baseConfig = require("../../jest.config.js"); - -module.exports = { - ...baseConfig, - testMatch: ["**/src/benchmarks/**/*.spec.ts"], - testPathIgnorePatterns: [ - "/node_modules/", - "/dist/", - ".*\\.test\\.spec\\.ts$", - ], -}; diff --git a/src/benchmarks/jest-test.config.js b/src/benchmarks/jest-test.config.js deleted file mode 100644 index 3899ea5e38..0000000000 --- a/src/benchmarks/jest-test.config.js +++ /dev/null @@ -1,7 +0,0 @@ -const baseConfig = require("../../jest.config.js"); - -module.exports = { - ...baseConfig, - testMatch: ["**/src/benchmarks/**/*.test.spec.ts"], - testPathIgnorePatterns: ["/node_modules/", "/dist/"], -}; diff --git a/src/benchmarks/jetton/jetton.spec.ts b/src/benchmarks/jetton/bench.spec.ts similarity index 100% rename from src/benchmarks/jetton/jetton.spec.ts rename to src/benchmarks/jetton/bench.spec.ts diff --git a/src/benchmarks/nft/bench.spec.ts b/src/benchmarks/nft/bench.spec.ts index 087752aa8a..8110dfe0d0 100644 --- a/src/benchmarks/nft/bench.spec.ts +++ b/src/benchmarks/nft/bench.spec.ts @@ -1,4 +1,370 @@ -import { bench } from "@/benchmarks/nft/bench"; -import { run } from "@/benchmarks/nft/run"; +import "@ton/test-utils"; +import type { Address } from "@ton/core"; +import type { Cell } from "@ton/core"; +import { beginCell, toNano, Dictionary } from "@ton/core"; + +import type { Slice, Sender, Builder } from "@ton/core"; +import { Blockchain } from "@ton/sandbox"; +import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; +import type { BenchmarkResult, CodeSizeResult } from "@/benchmarks/utils/gas"; +import { getStateSizeForAccount, getUsedGas } from "@/benchmarks/utils/gas"; +import { join } from "path"; +import type { Step } from "@/test/utils/write-vm-log"; +import { writeLog } from "@/test/utils/write-vm-log"; +import type { NFTCollection } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { + ReportStaticData, + loadInitNFTBody, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import type { + DeployNFT, + GetStaticData, + BatchDeploy, + RoyaltyParams, + InitNFTBody, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import type { + NFTItem, + Transfer, +} from "@/benchmarks/nft/tact/output/collection_NFTItem"; +import { storeInitNFTBody } from "@/benchmarks/nft/tact/output/collection_NFTItem"; + +type dictDeployNFT = { + amount: bigint; + initNFTBody: InitNFTBody; +}; + +const dictDeployNFTItem = { + serialize: (src: dictDeployNFT, builder: Builder) => { + builder + .storeCoins(src.amount) + .storeRef( + beginCell().store(storeInitNFTBody(src.initNFTBody)).endCell(), + ); + }, + parse: (src: Slice) => { + return { + amount: src.loadCoins(), + initNFTBody: loadInitNFTBody(src.loadRef().asSlice()), + }; + }, +}; + +function bench( + benchmarkResults: BenchmarkResult, + codeSizeResults: CodeSizeResult, + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) { + let blockchain: Blockchain; + let owner: SandboxContract; + let notOwner: SandboxContract; + let itemNFT: SandboxContract; + let collectionNFT: SandboxContract; + + let defaultContent: Cell; + let defaultCommonContent: Cell; + let defaultCollectionContent: Cell; + let defaultNFTContent: Cell; + let royaltyParams: RoyaltyParams; + + let step: Step; + + beforeAll(async () => { + blockchain = await Blockchain.create(); + owner = await blockchain.treasury("owner"); + notOwner = await blockchain.treasury("notOwner"); + + defaultCommonContent = beginCell().storeStringTail("common").endCell(); + defaultCollectionContent = beginCell() + .storeStringTail("collectionContent") + .endCell(); + defaultNFTContent = beginCell().endCell(); + defaultContent = beginCell() + .storeRef(defaultCollectionContent) + .storeRef(defaultCommonContent) + .endCell(); + + royaltyParams = { + $$type: "RoyaltyParams", + nominator: 1n, + dominator: 100n, + owner: owner.address, + }; + + step = writeLog({ + path: join(__dirname, "output", "log.yaml"), + blockchain, + }); + + // Deploy Collection + collectionNFT = blockchain.openContract( + await fromInitCollection( + owner.address, + 0n, + defaultContent, + royaltyParams, + ), + ); + + const deployResult = await collectionNFT.send( + owner.getSender(), + { value: toNano("0.1") }, + { + $$type: "GetRoyaltyParams", + queryId: 0n, + }, + ); + + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + deploy: true, + success: true, + }); + + // Deploy Item + itemNFT = blockchain.openContract( + await fromInitItem(null, null, owner.address, 0n), + ); + + const deployItemMsg: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + const deployItemResult = await itemNFT.send( + owner.getSender(), + { value: toNano("0.1") }, + beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), + ); + + expect(deployItemResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + deploy: true, + success: true, + }); + }); + + it("transfer", async () => { + const sendTransfer = async ( + itemNFT: SandboxContract, + from: Sender, + value: bigint, + newOwner: Address, + responseDestination: Address | null, + forwardAmount: bigint, + forwardPayload: Slice = beginCell().storeUint(0, 1).asSlice(), + ) => { + const msg: Transfer = { + $$type: "Transfer", + queryId: 0n, + newOwner: newOwner, + responseDestination: responseDestination, + customPayload: null, // we don't use it in contract + forwardAmount: forwardAmount, + forwardPayload: forwardPayload, + }; + + return await itemNFT.send(from, { value }, msg); + }; + + const sendResult = await step("transfer", async () => + sendTransfer( + itemNFT, + owner.getSender(), + toNano(1), + notOwner.address, + owner.address, + 0n, + ), + ); + + expect(sendResult.transactions).not.toHaveTransaction({ + success: false, + }); + + const gasUsed = getUsedGas(sendResult, "internal"); + expect(gasUsed).toEqual(benchmarkResults.gas["transfer"]); + }); + + it("get static data", async () => { + const sendGetStaticData = async ( + itemNFT: SandboxContract, + from: Sender, + value: bigint, + ) => { + const msg: GetStaticData = { + $$type: "GetStaticData", + queryId: 0n, + }; + + return await itemNFT.send(from, { value }, msg); + }; + + const sendResult = await step("get static data", async () => + sendGetStaticData(itemNFT, owner.getSender(), toNano(1)), + ); -run(bench); + expect(sendResult.transactions).toHaveTransaction({ + from: itemNFT.address, + to: owner.address, + body: beginCell() + .storeUint(ReportStaticData, 32) + .storeUint(0n, 64) + .storeUint(0n, 256) + .storeAddress(owner.address) + .endCell(), + success: true, + }); + + expect(sendResult.transactions).not.toHaveTransaction({ + success: false, + }); + + const gasUsed = getUsedGas(sendResult, "internal"); + expect(gasUsed).toEqual(benchmarkResults.gas["get static data"]); + }); + + it("deploy nft", async () => { + const sendDeployNFT = async ( + collectionNFT: SandboxContract, + from: Sender, + value: bigint, + ) => { + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + const msg: DeployNFT = { + $$type: "DeployNFT", + queryId: 1n, + itemIndex: 0n, + amount: 10000000n, + initNFTBody: beginCell() + .store(storeInitNFTBody(initNFTBody)) + .endCell(), + }; + + return await collectionNFT.send(from, { value }, msg); + }; + + const sendResult = await step("deploy nft", async () => + sendDeployNFT(collectionNFT, owner.getSender(), toNano(1)), + ); + + expect(sendResult.transactions).not.toHaveTransaction({ + success: false, + }); + + expect(sendResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + deploy: true, + }); + + const gasUsed = getUsedGas(sendResult, "internal"); + expect(gasUsed).toEqual(benchmarkResults.gas["deploy nft"]); + }); + + it("batch deploy nft", async () => { + const batchMintNFTProcess = async ( + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + count: bigint, + ) => { + const dct = Dictionary.empty( + Dictionary.Keys.BigUint(64), + dictDeployNFTItem, + ); + let i: bigint = 1n; + count += i; + + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + while (i < count) { + dct.set(i, { + amount: 10000000n, + initNFTBody: initNFTBody, + }); + i += 1n; + } + + const batchMintNFT: BatchDeploy = { + $$type: "BatchDeploy", + queryId: 0n, + deployList: beginCell().storeDictDirect(dct).endCell(), + }; + + return await collectionNFT.send( + sender.getSender(), + { value: toNano("100") * (count + 10n) }, + batchMintNFT, + ); + }; + + const sendResult = await step("batch deploy nft", async () => + batchMintNFTProcess(collectionNFT, owner, owner, 100n), + ); + + expect(sendResult.transactions).not.toHaveTransaction({ + success: false, + }); + + expect(sendResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + deploy: true, + }); + + const gasUsed = getUsedGas(sendResult, "internal"); + expect(gasUsed).toEqual(benchmarkResults.gas["batch deploy nft"]); + }); + + it("collection cells", async () => { + expect( + (await getStateSizeForAccount(blockchain, collectionNFT.address)) + .cells, + ).toEqual(codeSizeResults.size["collection cells"]); + }); + + it("collection bits", async () => { + expect( + (await getStateSizeForAccount(blockchain, collectionNFT.address)) + .bits, + ).toEqual(codeSizeResults.size["collection bits"]); + }); + + it("item cells", async () => { + expect( + (await getStateSizeForAccount(blockchain, itemNFT.address)).cells, + ).toEqual(codeSizeResults.size["item cells"]); + }); + + it("item bits", async () => { + expect( + (await getStateSizeForAccount(blockchain, itemNFT.address)).bits, + ).toEqual(codeSizeResults.size["item bits"]); + }); +} + + +import { run } from "@/benchmarks/nft/run"; +run(bench); \ No newline at end of file diff --git a/src/benchmarks/nft/bench.ts b/src/benchmarks/nft/bench.ts deleted file mode 100644 index b66feabe16..0000000000 --- a/src/benchmarks/nft/bench.ts +++ /dev/null @@ -1,366 +0,0 @@ -import "@ton/test-utils"; -import type { Address } from "@ton/core"; -import type { Cell } from "@ton/core"; -import { beginCell, toNano, Dictionary } from "@ton/core"; - -import type { Slice, Sender, Builder } from "@ton/core"; -import { Blockchain } from "@ton/sandbox"; -import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; -import type { BenchmarkResult, CodeSizeResult } from "@/benchmarks/utils/gas"; -import { getStateSizeForAccount, getUsedGas } from "@/benchmarks/utils/gas"; -import { join } from "path"; -import type { Step } from "@/test/utils/write-vm-log"; -import { writeLog } from "@/test/utils/write-vm-log"; -import type { NFTCollection } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; -import { - ReportStaticData, - loadInitNFTBody, -} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; -import type { - DeployNFT, - GetStaticData, - BatchDeploy, - RoyaltyParams, - InitNFTBody, -} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; -import type { - NFTItem, - Transfer, -} from "@/benchmarks/nft/tact/output/collection_NFTItem"; -import { storeInitNFTBody } from "@/benchmarks/nft/tact/output/collection_NFTItem"; - -type dictDeployNFT = { - amount: bigint; - initNFTBody: InitNFTBody; -}; - -const dictDeployNFTItem = { - serialize: (src: dictDeployNFT, builder: Builder) => { - builder - .storeCoins(src.amount) - .storeRef( - beginCell().store(storeInitNFTBody(src.initNFTBody)).endCell(), - ); - }, - parse: (src: Slice) => { - return { - amount: src.loadCoins(), - initNFTBody: loadInitNFTBody(src.loadRef().asSlice()), - }; - }, -}; - -export function bench( - benchmarkResults: BenchmarkResult, - codeSizeResults: CodeSizeResult, - fromInitCollection: ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) => Promise, - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) { - let blockchain: Blockchain; - let owner: SandboxContract; - let notOwner: SandboxContract; - let itemNFT: SandboxContract; - let collectionNFT: SandboxContract; - - let defaultContent: Cell; - let defaultCommonContent: Cell; - let defaultCollectionContent: Cell; - let defaultNFTContent: Cell; - let royaltyParams: RoyaltyParams; - - let step: Step; - - beforeAll(async () => { - blockchain = await Blockchain.create(); - owner = await blockchain.treasury("owner"); - notOwner = await blockchain.treasury("notOwner"); - - defaultCommonContent = beginCell().storeStringTail("common").endCell(); - defaultCollectionContent = beginCell() - .storeStringTail("collectionContent") - .endCell(); - defaultNFTContent = beginCell().endCell(); - defaultContent = beginCell() - .storeRef(defaultCollectionContent) - .storeRef(defaultCommonContent) - .endCell(); - - royaltyParams = { - $$type: "RoyaltyParams", - nominator: 1n, - dominator: 100n, - owner: owner.address, - }; - - step = writeLog({ - path: join(__dirname, "output", "log.yaml"), - blockchain, - }); - - // Deploy Collection - collectionNFT = blockchain.openContract( - await fromInitCollection( - owner.address, - 0n, - defaultContent, - royaltyParams, - ), - ); - - const deployResult = await collectionNFT.send( - owner.getSender(), - { value: toNano("0.1") }, - { - $$type: "GetRoyaltyParams", - queryId: 0n, - }, - ); - - expect(deployResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - deploy: true, - success: true, - }); - - // Deploy Item - itemNFT = blockchain.openContract( - await fromInitItem(null, null, owner.address, 0n), - ); - - const deployItemMsg: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - const deployItemResult = await itemNFT.send( - owner.getSender(), - { value: toNano("0.1") }, - beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), - ); - - expect(deployItemResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - deploy: true, - success: true, - }); - }); - - it("transfer", async () => { - const sendTransfer = async ( - itemNFT: SandboxContract, - from: Sender, - value: bigint, - newOwner: Address, - responseDestination: Address | null, - forwardAmount: bigint, - forwardPayload: Slice = beginCell().storeUint(0, 1).asSlice(), - ) => { - const msg: Transfer = { - $$type: "Transfer", - queryId: 0n, - newOwner: newOwner, - responseDestination: responseDestination, - customPayload: null, // we don't use it in contract - forwardAmount: forwardAmount, - forwardPayload: forwardPayload, - }; - - return await itemNFT.send(from, { value }, msg); - }; - - const sendResult = await step("transfer", async () => - sendTransfer( - itemNFT, - owner.getSender(), - toNano(1), - notOwner.address, - owner.address, - 0n, - ), - ); - - expect(sendResult.transactions).not.toHaveTransaction({ - success: false, - }); - - const gasUsed = getUsedGas(sendResult, "internal"); - expect(gasUsed).toEqual(benchmarkResults.gas["transfer"]); - }); - - it("get static data", async () => { - const sendGetStaticData = async ( - itemNFT: SandboxContract, - from: Sender, - value: bigint, - ) => { - const msg: GetStaticData = { - $$type: "GetStaticData", - queryId: 0n, - }; - - return await itemNFT.send(from, { value }, msg); - }; - - const sendResult = await step("get static data", async () => - sendGetStaticData(itemNFT, owner.getSender(), toNano(1)), - ); - - expect(sendResult.transactions).toHaveTransaction({ - from: itemNFT.address, - to: owner.address, - body: beginCell() - .storeUint(ReportStaticData, 32) - .storeUint(0n, 64) - .storeUint(0n, 256) - .storeAddress(owner.address) - .endCell(), - success: true, - }); - - expect(sendResult.transactions).not.toHaveTransaction({ - success: false, - }); - - const gasUsed = getUsedGas(sendResult, "internal"); - expect(gasUsed).toEqual(benchmarkResults.gas["get static data"]); - }); - - it("deploy nft", async () => { - const sendDeployNFT = async ( - collectionNFT: SandboxContract, - from: Sender, - value: bigint, - ) => { - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - const msg: DeployNFT = { - $$type: "DeployNFT", - queryId: 1n, - itemIndex: 0n, - amount: 10000000n, - initNFTBody: beginCell() - .store(storeInitNFTBody(initNFTBody)) - .endCell(), - }; - - return await collectionNFT.send(from, { value }, msg); - }; - - const sendResult = await step("deploy nft", async () => - sendDeployNFT(collectionNFT, owner.getSender(), toNano(1)), - ); - - expect(sendResult.transactions).not.toHaveTransaction({ - success: false, - }); - - expect(sendResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - deploy: true, - }); - - const gasUsed = getUsedGas(sendResult, "internal"); - expect(gasUsed).toEqual(benchmarkResults.gas["deploy nft"]); - }); - - it("batch deploy nft", async () => { - const batchMintNFTProcess = async ( - collectionNFT: SandboxContract, - sender: SandboxContract, - owner: SandboxContract, - count: bigint, - ) => { - const dct = Dictionary.empty( - Dictionary.Keys.BigUint(64), - dictDeployNFTItem, - ); - let i: bigint = 1n; - count += i; - - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - while (i < count) { - dct.set(i, { - amount: 10000000n, - initNFTBody: initNFTBody, - }); - i += 1n; - } - - const batchMintNFT: BatchDeploy = { - $$type: "BatchDeploy", - queryId: 0n, - deployList: beginCell().storeDictDirect(dct).endCell(), - }; - - return await collectionNFT.send( - sender.getSender(), - { value: toNano("100") * (count + 10n) }, - batchMintNFT, - ); - }; - - const sendResult = await step("batch deploy nft", async () => - batchMintNFTProcess(collectionNFT, owner, owner, 100n), - ); - - expect(sendResult.transactions).not.toHaveTransaction({ - success: false, - }); - - expect(sendResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - deploy: true, - }); - - const gasUsed = getUsedGas(sendResult, "internal"); - expect(gasUsed).toEqual(benchmarkResults.gas["batch deploy nft"]); - }); - - it("collection cells", async () => { - expect( - (await getStateSizeForAccount(blockchain, collectionNFT.address)) - .cells, - ).toEqual(codeSizeResults.size["collection cells"]); - }); - - it("collection bits", async () => { - expect( - (await getStateSizeForAccount(blockchain, collectionNFT.address)) - .bits, - ).toEqual(codeSizeResults.size["collection bits"]); - }); - - it("item cells", async () => { - expect( - (await getStateSizeForAccount(blockchain, itemNFT.address)).cells, - ).toEqual(codeSizeResults.size["item cells"]); - }); - - it("item bits", async () => { - expect( - (await getStateSizeForAccount(blockchain, itemNFT.address)).bits, - ).toEqual(codeSizeResults.size["item bits"]); - }); -} diff --git a/src/benchmarks/nft/run.ts b/src/benchmarks/nft/run.ts index 12c1c7ec06..18dc5f41c3 100644 --- a/src/benchmarks/nft/run.ts +++ b/src/benchmarks/nft/run.ts @@ -32,6 +32,53 @@ const loadFunCNFTBoc = () => { return { bocCollection, bocItem }; }; +const fromInitCollection = ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, +) => { + const nftData = loadFunCNFTBoc(); + const __code = Cell.fromBoc(nftData.bocCollection)[0]!; + + const royaltyCell = beginCell() + .storeUint(royaltyParams.nominator, 16) + .storeUint(royaltyParams.dominator, 16) + .storeAddress(royaltyParams.owner) + .endCell(); + + const __data = beginCell() + .storeAddress(owner) + .storeUint(index, 64) + .storeRef(content) + .storeRef(Cell.fromBoc(nftData.bocItem)[0]!) + .storeRef(royaltyCell) + .endCell(); + + const __gen_init = { code: __code, data: __data }; + const address = contractAddress(0, __gen_init); + return Promise.resolve(new NFTCollection(address, __gen_init)); +}; + +export const fromInitItem = ( + _owner: Address | null, + _content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, +) => { + const nftData = loadFunCNFTBoc(); + const __code = Cell.fromBoc(nftData.bocItem)[0]!; + + const __data = beginCell() + .storeUint(itemIndex, 64) + .storeAddress(collectionAddress) + .endCell(); + + const __gen_init = { code: __code, data: __data }; + const address = contractAddress(0, __gen_init); + return Promise.resolve(new NFTItem(address, __gen_init)); +}; + export const run = ( testNFT: ( benchmarkResults: BenchmarkResult, @@ -60,53 +107,6 @@ export const run = ( const funcCodeSize = fullCodeSizeResults.at(0)!; const funcResult = fullResults.at(0)!; - function fromInitCollection( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) { - const nftData = loadFunCNFTBoc(); - const __code = Cell.fromBoc(nftData.bocCollection)[0]!; - - const royaltyCell = beginCell() - .storeUint(royaltyParams.nominator, 16) - .storeUint(royaltyParams.dominator, 16) - .storeAddress(royaltyParams.owner) - .endCell(); - - const __data = beginCell() - .storeAddress(owner) - .storeUint(index, 64) - .storeRef(content) - .storeRef(Cell.fromBoc(nftData.bocItem)[0]!) - .storeRef(royaltyCell) - .endCell(); - - const __gen_init = { code: __code, data: __data }; - const address = contractAddress(0, __gen_init); - return Promise.resolve(new NFTCollection(address, __gen_init)); - } - - function fromInitItem( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) { - const nftData = loadFunCNFTBoc(); - const __code = Cell.fromBoc(nftData.bocItem)[0]!; - - const __data = beginCell() - .storeUint(itemIndex, 64) - .storeAddress(collectionAddress) - .endCell(); - - const __gen_init = { code: __code, data: __data }; - const address = contractAddress(0, __gen_init); - return Promise.resolve(new NFTItem(address, __gen_init)); - } - testNFT(funcResult, funcCodeSize, fromInitCollection, fromInitItem); }); diff --git a/src/benchmarks/nft/test.spec.ts b/src/benchmarks/nft/test.spec.ts index 087752aa8a..e94454db8c 100644 --- a/src/benchmarks/nft/test.spec.ts +++ b/src/benchmarks/nft/test.spec.ts @@ -1,4 +1,64 @@ -import { bench } from "@/benchmarks/nft/bench"; +import type { Address, Cell } from "@ton/core"; + +import type { NFTItem } from "@/benchmarks/nft/tact/output/collection_NFTItem"; +import type { + NFTCollection, + RoyaltyParams, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; + +import { testItem } from "@/benchmarks/nft/tests/item"; +import { + testTransferFee, + testTransferForwardFeeDouble, +} from "@/benchmarks/nft/tests/transfer-fee"; +import { + testCollection, + testDeploy, + testRoyalty, + testBatchDeploy, +} from "@/benchmarks/nft/tests/collection"; + +import type { BenchmarkResult, CodeSizeResult } from "@/benchmarks/utils/gas"; + +type FromInitItem = ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, +) => Promise; +type FromInitCollection = ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, +) => Promise; + +const testNFTItem = (fromInitItem: FromInitItem) => { + testItem(fromInitItem); + testTransferFee(fromInitItem); + testTransferForwardFeeDouble(fromInitItem); +}; + +const testNFTCollection = ( + fromInitCollection: FromInitCollection, + fromInitItem: FromInitItem, +) => { + testCollection(fromInitCollection); + testRoyalty(fromInitCollection); + testDeploy(fromInitCollection, fromInitItem); + testBatchDeploy(fromInitCollection, fromInitItem); +}; + +export const testNFT = ( + _benchmarkResults: BenchmarkResult, + _codeSizeResults: CodeSizeResult, + fromInitCollection: FromInitCollection, + fromInitItem: FromInitItem, +) => { + testNFTItem(fromInitItem); + testNFTCollection(fromInitCollection, fromInitItem); +}; + import { run } from "@/benchmarks/nft/run"; -run(bench); +run(testNFT); diff --git a/src/benchmarks/nft/tests/collection.ts b/src/benchmarks/nft/tests/collection.ts new file mode 100644 index 0000000000..eb268e953d --- /dev/null +++ b/src/benchmarks/nft/tests/collection.ts @@ -0,0 +1,727 @@ +import type { Address, Cell } from "@ton/core"; +import { beginCell, Dictionary } from "@ton/core"; +import type { + SandboxContract, + TreasuryContract, + SendMessageResult, +} from "@ton/sandbox"; +import { Blockchain } from "@ton/sandbox"; +import type { + DeployNFT, + GetRoyaltyParams, + BatchDeploy, + RoyaltyParams, + InitNFTBody, + ChangeOwner, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { + storeRoyaltyParams, + type NFTCollection, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { + storeInitNFTBody, + type NFTItem, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; +import "@ton/test-utils"; +import { step } from "@/test/allure/allure"; +import { + getOwner, + getNextItemIndex, + loadGetterTupleNFTData, + Operations, + Storage, + ErrorCodes, + TestValues, + dictDeployNFTItem, +} from "@/benchmarks/nft/tests/utils"; + +const globalSetup = async ( + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, +) => { + const blockchain = await Blockchain.create(); + const owner = await blockchain.treasury("owner"); + const notOwner = await blockchain.treasury("notOwner"); + const defaultCommonContent = beginCell() + .storeStringTail("common") + .endCell(); + const defaultCollectionContent = beginCell() + .storeStringTail("collectionContent") + .endCell(); + const defaultNFTContent = beginCell().storeStringTail("1.json").endCell(); + const defaultContent = beginCell() + .storeRef(defaultCollectionContent) + .storeRef(defaultCommonContent) + .endCell(); + + const royaltyParams: RoyaltyParams = { + $$type: "RoyaltyParams", + nominator: 1n, + dominator: 100n, + owner: owner.address, + }; + + const collectionNFT = blockchain.openContract( + await fromInitCollection( + owner.address, + 0n, + defaultContent, + royaltyParams, + ), + ); + const deployCollectionMsg: GetRoyaltyParams = { + $$type: "GetRoyaltyParams", + queryId: 0n, + }; + + const deployResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + deployCollectionMsg, + ); + await step( + "Check that deployResult.transactions has correct transaction (collection)", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + deploy: true, + success: true, + }); + }, + ); + + return { + blockchain, + owner, + notOwner, + defaultContent, + defaultCommonContent, + defaultCollectionContent, + defaultNFTContent, + royaltyParams, + collectionNFT, + }; +}; + +export const testCollection = ( + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, +) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("NFT Collection Contract", () => { + it("should deploy correctly", async () => { + await setup(); + // checking in setup + }); + + it("should get static data correctly", async () => { + const { collectionNFT, owner, defaultCollectionContent } = + await setup(); + const staticData = await collectionNFT.getGetCollectionData(); + await step( + "Check that staticData.owner equals owner.address (collection)", + () => { + expect(staticData.owner).toEqualAddress(owner.address); + }, + ); + await step( + "Check that staticData.nextItemIndex is 0 (collection)", + () => { + expect(staticData.nextItemIndex).toBe(0n); + }, + ); + await step( + "Check that staticData.collectionContent equals defaultCollectionContent (collection)", + () => { + expect(staticData.collectionContent).toEqualCell( + defaultCollectionContent, + ); + }, + ); + }); + + it("should get nft content correctly", async () => { + const { collectionNFT, defaultContent, defaultCommonContent } = + await setup(); + const content = await collectionNFT.getGetNftContent( + 0n, + defaultContent, + ); + const expectedContent = beginCell() + .storeUint(1, 8) + .storeSlice(defaultCommonContent.asSlice()) + .storeRef(defaultContent) + .endCell(); + await step( + "Check that content equals expectedContent (nft content)", + () => { + expect(content).toEqualCell(expectedContent); + }, + ); + }); + + it("should transfer ownership correctly", async () => { + const { collectionNFT, owner, notOwner } = await setup(); + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: notOwner.address, + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + await step( + "Check that collectionNFT.getOwner() equals notOwner.address", + async () => { + expect(await getOwner(collectionNFT)).toEqualAddress( + notOwner.address, + ); + }, + ); + }); + it("should return error if not owner tries to transfer ownership", async () => { + const { collectionNFT, owner, notOwner } = await setup(); + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: owner.address, + }; + + const trxResult = await collectionNFT.send( + notOwner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (not owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + }); +}; + +export const testRoyalty = ( + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, +) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Royalty cases", () => { + it("should send royalty msg correctly", async () => { + const { collectionNFT, owner, royaltyParams } = await setup(); + const queryId = 0n; + + const msg: GetRoyaltyParams = { + $$type: "GetRoyaltyParams", + queryId: BigInt(queryId), + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (royalty msg)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + + const exceptedMsg: Cell = beginCell() + .storeUint(Operations.ReportRoyaltyParams, 32) + .storeUint(queryId, 64) + .storeUint(royaltyParams.nominator, 16) + .storeUint(royaltyParams.dominator, 16) + .storeAddress(royaltyParams.owner) + .endCell(); + expect(trxResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: owner.address, + body: exceptedMsg, + }); + }); + + it("should get royalty params correctly", async () => { + const { collectionNFT, royaltyParams } = await setup(); + const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); + expect( + beginCell() + .store(storeRoyaltyParams(currRoyaltyParams)) + .asSlice(), + ).toEqualSlice( + beginCell().store(storeRoyaltyParams(royaltyParams)).asSlice(), + ); + }); + }); +}; + +export const testDeploy = ( + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + describe("NFT deploy cases", () => { + it("should deploy NFTItem correctly", async () => { + // checking in beforeEach + }); + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + /** + * Helper function to deploy an NFT item + * @param itemIndex - Index of the NFT to deploy + * @param collectionNFT - Collection contract instance + * @param sender - Sender of the deployment transaction + * @param owner - Owner of the deployed NFT + * @returns Promise resolving to the NFT item contract and transaction result + */ + const deployNFT = async ( + itemIndex: bigint, + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + defaultNFTContent: Cell, + blockchain: Blockchain, + ): Promise<[SandboxContract, SendMessageResult]> => { + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + const mintMsg: DeployNFT = { + $$type: "DeployNFT", + queryId: 1n, + itemIndex: itemIndex, + amount: Storage.NftMintAmount, + initNFTBody: beginCell() + .store(storeInitNFTBody(initNFTBody)) + .endCell(), + }; + + const itemNFT = blockchain.openContract( + await fromInitItem( + null, + null, + collectionNFT.address, + itemIndex, + ), + ); + + const trxResult = await collectionNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + mintMsg, + ); + return [itemNFT, trxResult]; + }; + + it("should mint NFTItem correctly", async () => { + const { collectionNFT, owner, defaultNFTContent, blockchain } = + await setup(); + const nextItemIndex = await getNextItemIndex(collectionNFT); + const [itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + defaultNFTContent, + blockchain, + ); + const nftData = await itemNFT.getGetNftData(); + + await step( + "Check that nftData.content equals defaultNFTContent", + () => { + expect(nftData.content).toEqualCell(defaultNFTContent); + }, + ); + await step("Check that nftData.owner equals owner.address", () => { + expect(nftData.owner).toEqualAddress(owner.address); + }); + await step("Check that nftData.itemIndex is nextItemIndex", () => { + expect(nftData.itemIndex).toBe(nextItemIndex); + }); + await step( + "Check that nftData.collectionAddress equals collectionNFT.address", + () => { + expect(nftData.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }, + ); + }); + + it("should not mint NFTItem if not owner", async () => { + const { collectionNFT, defaultNFTContent, blockchain, notOwner } = + await setup(); + const nextItemIndex = await getNextItemIndex(collectionNFT); + const [_itemNFT, trx] = await deployNFT( + nextItemIndex, + collectionNFT, + notOwner, + notOwner, + defaultNFTContent, + blockchain, + ); + await step( + "Check that trx.transactions has correct transaction (not owner mint)", + () => { + expect(trx.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + + it("should not deploy previous nft", async () => { + const { collectionNFT, owner, defaultNFTContent, blockchain } = + await setup(); + let nextItemIndex: bigint = await getNextItemIndex(collectionNFT); + for (let i = 0; i < 10; i++) { + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + defaultNFTContent, + blockchain, + ); + nextItemIndex++; + } + const [_itemNFT, trx] = await deployNFT( + 0n, + collectionNFT, + owner, + owner, + defaultNFTContent, + blockchain, + ); + await step( + "Check that trx.transactions has correct transaction (should not deploy previous nft)", + () => { + expect(trx.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: _itemNFT.address, + deploy: false, + success: false, + exitCode: ErrorCodes.InvalidData, + }); + }, + ); + }); + + it("shouldn't mint item itemIndex > nextItemIndex", async () => { + const { collectionNFT, owner, defaultNFTContent, blockchain } = + await setup(); + const nextItemIndex = await getNextItemIndex(collectionNFT); + const [_itemNFT, trx] = await deployNFT( + nextItemIndex + 1n, + collectionNFT, + owner, + owner, + defaultNFTContent, + blockchain, + ); + await step( + "Check that trx.transactions has correct transaction (itemIndex > nextItemIndex)", + () => { + expect(trx.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.IncorrectIndex, + }); + }, + ); + }); + + it("should get nft by itemIndex correctly", async () => { + const { collectionNFT, owner, defaultNFTContent, blockchain } = + await setup(); + const nextItemIndex = await getNextItemIndex(collectionNFT); + // deploy new nft to get itemIndex + const [_itemNFT, _trx] = await deployNFT( + nextItemIndex, + collectionNFT, + owner, + owner, + defaultNFTContent, + blockchain, + ); + const nftAddress = + await collectionNFT.getGetNftAddressByIndex(nextItemIndex); + const newNFT = blockchain.getContract(nftAddress); + const getData = await (await newNFT).get("get_nft_data"); + const dataNFT = loadGetterTupleNFTData(getData.stack); + await step("Check that dataNFT.itemIndex is nextItemIndex", () => { + expect(dataNFT.itemIndex).toBe(nextItemIndex); + }); + await step( + "Check that dataNFT.collectionAddress equals collectionNFT.address", + () => { + expect(dataNFT.collectionAddress).toEqualAddress( + collectionNFT.address, + ); + }, + ); + }); + }); +}; + +export const testBatchDeploy = ( + fromInitCollection: ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, + ) => Promise, + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Batch mint cases", () => { + /** + * Helper function to batch mint NFTs + * @param collectionNFT - Collection contract instance + * @param sender - Sender of the batch mint transaction + * @param owner - Owner of the minted NFTs + * @param count - Number of NFTs to mint + * @param extra - Optional extra index to mint + * @returns Promise resolving to the transaction result + */ + const batchMintNFTProcess = async ( + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + defaultNFTContent: Cell, + count: bigint, + extra: bigint = -1n, + ): Promise => { + const dct = Dictionary.empty( + Dictionary.Keys.BigUint(64), + dictDeployNFTItem, + ); + let i: bigint = 0n; + + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + while (i < count) { + dct.set(i, { + amount: Storage.NftMintAmount, + initNFTBody: initNFTBody, + }); + i += 1n; + } + + if (extra != -1n) { + dct.set(extra, { + amount: Storage.NftMintAmount, + initNFTBody: initNFTBody, + }); + } + + const batchMintNFT: BatchDeploy = { + $$type: "BatchDeploy", + queryId: 0n, + deployList: beginCell().storeDictDirect(dct).endCell(), + }; + + return await collectionNFT.send( + sender.getSender(), + { + value: + Storage.BatchDeployAmount * + (count + TestValues.ExtraValues.BatchMultiplier), + }, + batchMintNFT, + ); + }; + + it.skip("test max batch mint", async () => { + const { collectionNFT, owner, defaultNFTContent } = await setup(); + let L = 1n; + let R = 1000n; + while (R - L > 1) { + const M = (L + R) / 2n; + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + defaultNFTContent, + M, + ); + try { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + L = M; + } catch { + R = M; + } + } + console.log("maximum batch amount is", L); + }); + + it("should batch mint correctly", async () => { + const { collectionNFT, owner, defaultNFTContent, blockchain } = + await setup(); + const count = 100n; + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + defaultNFTContent, + count, + ); + + await step( + "Check that trxResult.transactions has correct transaction (batch mint success)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + const itemNFT = blockchain.openContract( + await fromInitItem( + null, + null, + collectionNFT.address, + count - 1n, + ), + ); + + // it was deployed, that's why we can get it + await step( + "Check that itemNFT.getGetNftData() has property itemIndex", + async () => { + expect(await itemNFT.getGetNftData()).toHaveProperty( + "itemIndex", + count - 1n, + ); + }, + ); + }); + + it("shouldn't batch mint more than 250 items", async () => { + const { collectionNFT, owner, defaultNFTContent } = await setup(); + const trxResult = await batchMintNFTProcess( + collectionNFT, + owner, + owner, + defaultNFTContent, + 260n, + ); + + await step( + "Check that trxResult.transactions has correct transaction (should not batch mint more than 250)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: false, + }); + }, + ); + }); + + it("should return error if not owner tries to batch mint", async () => { + const { collectionNFT, owner, defaultNFTContent, notOwner } = + await setup(); + const trxResult = await batchMintNFTProcess( + collectionNFT, + notOwner, + owner, + defaultNFTContent, + 10n, + ); + + await step( + "Check that trxResult.transactions has correct transaction (not owner batch mint)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + }); +}; diff --git a/src/benchmarks/nft/tests/item.ts b/src/benchmarks/nft/tests/item.ts new file mode 100644 index 0000000000..20270bf53b --- /dev/null +++ b/src/benchmarks/nft/tests/item.ts @@ -0,0 +1,240 @@ +import type { Address, Cell } from "@ton/core"; +import { beginCell } from "@ton/core"; +import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; +import { Blockchain } from "@ton/sandbox"; +import type { + GetStaticData, + InitNFTBody, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { + storeInitNFTBody, + type NFTItem, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; +import "@ton/test-utils"; +import { setStoragePrices } from "@/test/utils/gasUtils"; +import { step } from "@/test/allure/allure"; +import { + Storage, + ErrorCodes, + getItemOwner, + sendTransfer, +} from "@/benchmarks/nft/tests/utils"; + +const globalSetup = async ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + const blockchain = await Blockchain.create(); + const config = blockchain.config; + blockchain.setConfig( + setStoragePrices(config, { + unixTimeSince: 0, + bitPricePerSecond: 0n, + cellPricePerSecond: 0n, + masterChainBitPricePerSecond: 0n, + masterChainCellPricePerSecond: 0n, + }), + ); + const owner = await blockchain.treasury("owner"); + const notOwner = await blockchain.treasury("notOwner"); + const emptyAddress = null; + const defaultContent = beginCell().endCell(); + + const itemNFT = blockchain.openContract( + await fromInitItem(null, null, owner.address, 0n), + ); + + const deployItemMsg: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultContent, + }; + + const deployResult = await itemNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), + ); + + await step( + "Check that deployResult.transactions has correct transaction", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + deploy: true, + success: true, + }); + }, + ); + return { + blockchain, + itemNFT, + owner, + notOwner, + defaultContent, + emptyAddress, + } as const; +}; + +export const testItem = ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + const setup = async () => { + return await globalSetup(fromInitItem); + }; + + describe("NFT Item Contract", () => { + const messageGetStaticData = async ( + sender: SandboxContract, + itemNFT: SandboxContract, + ) => { + const msg: GetStaticData = { + $$type: "GetStaticData", + queryId: 1n, + }; + const trxResult = await itemNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + return trxResult; + }; + + it("should deploy correctly", async () => { + // check on setup + }); + + it("should get nft data correctly", async () => { + const { itemNFT, owner, defaultContent } = await setup(); + const staticData = await itemNFT.getGetNftData(); + await step("Check that staticData.init is -1", () => { + expect(staticData.init).toBe(-1n); + }); + await step("Check that staticData.itemIndex is 0", () => { + expect(staticData.itemIndex).toBe(0n); + }); + await step( + "Check that staticData.collectionAddress equals owner.address", + () => { + expect(staticData.collectionAddress).toEqualAddress( + owner.address, + ); + }, + ); + await step( + "Check that staticData.owner equals owner.address", + () => { + expect(staticData.owner).toEqualAddress(owner.address); + }, + ); + await step( + "Check that staticData.content equals defaultContent", + () => { + expect(staticData.content).toEqualCell(defaultContent); + }, + ); + }); + it("should get static data correctly", async () => { + const { itemNFT, owner } = await setup(); + const trxResult = await messageGetStaticData(owner, itemNFT); + await step( + "Check that trxResult.transactions has correct transaction (get static data)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + }); + + it("should assign ownership correctly", async () => { + const { itemNFT, owner, notOwner } = await setup(); + const oldOwner = await getItemOwner(itemNFT); + await step("Check that oldOwner equals owner.address", () => { + expect(oldOwner).toEqualAddress(owner.address); + }); + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + owner.address, + 1n, + ); + const newOwner = await getItemOwner(itemNFT); + await step("Check that newOwner equals notOwner.address", () => { + expect(newOwner).toEqualAddress(notOwner.address); + }); + await step( + "Check that trxRes.transactions has correct transaction (ownership assigned)", + () => { + expect(trxRes.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + }); + it("should transfer ownership without any messages", async () => { + const { itemNFT, owner, notOwner, emptyAddress } = await setup(); + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + 0n, + ); + const newOwner = await getItemOwner(itemNFT); + await step( + "Check that newOwner equals notOwner.address (no messages)", + () => { + expect(newOwner).toEqualAddress(notOwner.address); + }, + ); + await step( + "Check that trxRes.transactions does NOT have transaction from itemNFT.address (no messages)", + () => { + expect(trxRes.transactions).not.toHaveTransaction({ + from: itemNFT.address, + }); + }, + ); + }); + it("should return error if not owner tries to transfer ownership", async () => { + const { itemNFT, notOwner, emptyAddress } = await setup(); + const trxResult = await sendTransfer( + itemNFT, + notOwner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + 0n, + ); + await step( + "Check that trxResult.transactions has correct transaction (not owner should not be able to transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + }); +}; diff --git a/src/benchmarks/nft/tests/test.ts b/src/benchmarks/nft/tests/test.ts deleted file mode 100644 index dc028b1762..0000000000 --- a/src/benchmarks/nft/tests/test.ts +++ /dev/null @@ -1,1458 +0,0 @@ -// Type imports -import type { - Address, - Slice, - ContractProvider, - Cell, - Sender, - Builder, - TupleItem, - TupleItemInt, - TupleItemSlice, - TupleItemCell, -} from "@ton/core"; -// Value imports -import { beginCell, toNano, Dictionary } from "@ton/core"; - -import type { - SandboxContract, - TreasuryContract, - SendMessageResult, -} from "@ton/sandbox"; - -import { Blockchain } from "@ton/sandbox"; - -// NFT Collection imports -import type { - DeployNFT, - GetRoyaltyParams, - GetStaticData, - BatchDeploy, - RoyaltyParams, - InitNFTBody, - ChangeOwner, -} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; -import { - storeRoyaltyParams, - loadInitNFTBody, - NFTCollection, -} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; - -// NFT Item imports -import type { - Transfer, - NFTData, -} from "@/benchmarks/nft/tact/output/item_NFTItem"; -import { - storeInitNFTBody, - NFTItem, -} from "@/benchmarks/nft/tact/output/item_NFTItem"; - -import "@ton/test-utils"; -import { randomInt } from "crypto"; - -import { setStoragePrices } from "@/test/utils/gasUtils"; - -import { step } from "@/test/allure/allure"; - -/** Operation codes for NFT contract messages */ -const Operations = { - TransferNft: 0x5fcc3d14, - OwnershipAssignment: 0x05138d91, - Excess: 0xd53276db, - GetStaticData: 0x2fcb26a2, - ReportStaticData: 0x8b771735, - GetRoyaltyParams: 0x693d3950, - ReportRoyaltyParams: 0xa8cb00ad, - EditContent: 0x1a0b9d51, - TransferEditorship: 0x1c04412a, - EditorshipAssigned: 0x511a4463, -} as const; - -/** Storage and transaction related constants */ -const Storage = { - /** Minimum amount of TONs required for storage */ - MinTons: 50000000n, - /** Amount of TONs for deployment operations */ - DeployAmount: toNano("0.1"), - /** Amount of TONs for transfer operations */ - TransferAmount: toNano("1"), - /** Amount of TONs for batch deployment */ - BatchDeployAmount: toNano("100"), - /** Amount of TONs for ownership change */ - ChangeOwnerAmount: 100000000n, - /** Amount of TONs for NFT minting */ - NftMintAmount: 10000000n, - /** Forward fee values */ - ForwardFee: { - /** Base forward fee for single message */ - Base: 623605n, - /** Forward fee for double message */ - Double: 729606n, - }, -} as const; - -/** Error codes */ -const ErrorCodes = { - /** Error code for not initialized contract */ - NotInit: 9, - /** Error code for not owner */ - NotOwner: 401, - /** Error code for invalid fees */ - InvalidFees: 402, - /** Error code for incorrect index */ - IncorrectIndex: 402, - /** Error code for invalid data */ - InvalidData: 65535, -} as const; - -/** Test related constants */ -const TestValues = { - /** Default item index used in tests */ - ItemIndex: 100n, - /** Batch operation sizes */ - BatchSize: { - Min: 1n, - Max: 250n, - Default: 50n, - OverLimit: 260n, - Small: 10n, - }, - /** Range for random number generation */ - RandomRange: 1337, - /** Royalty parameters */ - Royalty: { - Nominator: 1n, - Dominator: 100n, - }, - /** Bit sizes for different data types */ - BitSizes: { - Uint1: 1, - Uint8: 8, - Uint16: 16, - Uint32: 32, - Uint64: 64, - }, - /** Additional test values */ - ExtraValues: { - BatchMultiplier: 10n, - }, -} as const; - -/** Dictionary type for NFT deployment data */ -export type dictDeployNFT = { - amount: bigint; - initNFTBody: InitNFTBody; -}; - -/** Dictionary value parser for NFT deployment */ -export const dictDeployNFTItem = { - serialize: (src: dictDeployNFT, builder: Builder) => { - builder - .storeCoins(src.amount) - .storeRef( - beginCell().store(storeInitNFTBody(src.initNFTBody)).endCell(), - ); - }, - parse: (src: Slice) => { - return { - amount: src.loadCoins(), - initNFTBody: loadInitNFTBody(src.loadRef().asSlice()), - }; - }, -}; - -const minTonsForStorage = Storage.MinTons; - -/** - * Sends a transfer message to an NFT item contract - * @param itemNFT - The NFT item contract instance - * @param from - The sender of the transfer - * @param value - Amount of TONs to send - * @param newOwner - Address of the new owner - * @param responseDestination - Address to send the response to - * @param forwardAmount - Amount of TONs to forward - * @param forwardPayload - Optional payload to forward - * @returns Promise resolving to the transaction result - */ -const sendTransfer = async ( - itemNFT: SandboxContract, - from: Sender, - value: bigint, - newOwner: Address, - responseDestination: Address | null, - forwardAmount: bigint, - forwardPayload: Slice = beginCell().storeUint(0, 1).asSlice(), -): Promise => { - const msg: Transfer = { - $$type: "Transfer", - queryId: 0n, - newOwner: newOwner, - responseDestination: responseDestination, - customPayload: null, - forwardAmount: forwardAmount, - forwardPayload: forwardPayload, - }; - - return await itemNFT.send(from, { value }, msg); -}; - -/** - * Helper function to load NFT data from a tuple of contract getter results - * @param source - Array of tuple items containing NFT data - * @returns Parsed NFT data object - */ -function loadGetterTupleNFTData(source: TupleItem[]): NFTData { - const _init = (source[0] as TupleItemInt).value; - const _index = (source[1] as TupleItemInt).value; - const _collectionAddress = (source[2] as TupleItemSlice).cell - .asSlice() - .loadAddress(); - const _owner = (source[3] as TupleItemSlice).cell.asSlice().loadAddress(); - const _content = (source[4] as TupleItemCell).cell; - return { - $$type: "NFTData" as const, - init: _init, - itemIndex: _index, - collectionAddress: _collectionAddress, - owner: _owner, - content: _content, - }; -} - -/** - * Extends NFTItem interface with owner getter functionality - */ -declare module "@/benchmarks/nft/tact/output/item_NFTItem" { - interface NFTItem { - /** - * Gets the current owner of the NFT - * @param provider - Contract provider instance - * @returns Promise resolving to owner's address or null if not initialized - */ - getOwner(provider: ContractProvider): Promise
; - } -} - -// function getOwner(provider: ContractProvider): Promise
{ -// return provider.get("getOwner", []); -// } - -/** - * Extends NFTCollection interface with additional getter functionality - */ -declare module "@/benchmarks/nft/tact/output/collection_NFTCollection" { - interface NFTCollection { - /** - * Gets the next available item index for minting - * @param provider - Contract provider instance - * @returns Promise resolving to the next item index - */ - getNextItemIndex(provider: ContractProvider): Promise; - - /** - * Gets the current owner of the collection - * @param provider - Contract provider instance - * @returns Promise resolving to owner's address - */ - getOwner(provider: ContractProvider): Promise
; - } -} - -NFTItem.prototype.getOwner = async function ( - this: NFTItem, - provider: ContractProvider, -): Promise
{ - const res = await this.getGetNftData(provider); - return res.owner; -}; - -export function test( - fromInitCollection: ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) => Promise, - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) { - describe("NFT Item Contract", () => { - let blockchain: Blockchain; - let itemNFT: SandboxContract; - let owner: SandboxContract; - let notOwner: SandboxContract; - let defaultContent: Cell; - let emptyAddress: Address | null; - - beforeEach(async () => { - blockchain = await Blockchain.create(); - - const config = blockchain.config; - blockchain.setConfig( - setStoragePrices(config, { - unixTimeSince: 0, - bitPricePerSecond: 0n, - cellPricePerSecond: 0n, - masterChainBitPricePerSecond: 0n, - masterChainCellPricePerSecond: 0n, - }), - ); - - owner = await blockchain.treasury("owner"); - notOwner = await blockchain.treasury("notOwner"); - - emptyAddress = null; - defaultContent = beginCell().endCell(); // just some content ( doesn't matter ) - - itemNFT = blockchain.openContract( - await fromInitItem(null, null, owner.address, 0n), - ); - const deployItemMsg: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultContent, - }; - - const deployResult = await itemNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), - ); - - await step( - "Check that deployResult.transactions has correct transaction", - () => { - expect(deployResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - deploy: true, - success: true, - }); - }, - ); - }); - - const messageGetStaticData = async ( - sender: SandboxContract, - itemNFT: SandboxContract, - ) => { - const msg: GetStaticData = { - $$type: "GetStaticData", - queryId: 1n, - }; - - const trxResult = await itemNFT.send( - sender.getSender(), - { value: Storage.DeployAmount }, - msg, - ); - return trxResult; - }; - - it("should deploy correctly", async () => { - // checking in beforeEach - }); - - it("should get nft data correctly", async () => { - const staticData = await itemNFT.getGetNftData(); - - await step("Check that staticData.init is -1", () => { - expect(staticData.init).toBe(-1n); - }); - await step("Check that staticData.itemIndex is 0", () => { - expect(staticData.itemIndex).toBe(0n); - }); - await step( - "Check that staticData.collectionAddress equals owner.address", - () => { - expect(staticData.collectionAddress).toEqualAddress( - owner.address, - ); - }, - ); - await step( - "Check that staticData.owner equals owner.address", - () => { - expect(staticData.owner).toEqualAddress(owner.address); - }, - ); - await step( - "Check that staticData.content equals defaultContent", - () => { - expect(staticData.content).toEqualCell(defaultContent); - }, - ); - }); - - it("should get static data correctly", async () => { - const trxResult = await messageGetStaticData(owner, itemNFT); - await step( - "Check that trxResult.transactions has correct transaction (get static data)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - }); - - describe("Transfer ownership Fee cases", () => { - let balance: bigint; - let fwdFee: bigint; - - beforeEach(async () => { - balance = await ( - await blockchain.getContract(itemNFT.address) - ).balance; - fwdFee = Storage.ForwardFee.Base; - }); - - it("Transfer forward amount too much", async () => { - // NFT should reject transfer if balance lower than forward_amount + message forward fee + minimal storage fee - // Sending message with forward_amount of 1 TON and balance 0.1 TON - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - emptyAddress, - Storage.TransferAmount, - ); - await step( - "Check that trxResult.transactions has correct transaction (transfer forward amount too much)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - }, - ); - }); - - it("test transfer storage fee", async () => { - // Now let's try forward_amount exactly equal to balance and fwd_fee 0 - // 1 TON Balance forward_amount:1 TON fwd_fee:0 (just add to transfer value) verifying that minimal storage comes into play - // Should fail with no actions - - // [] and {} just kinds of () for more understandable description - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + fwdFee, - notOwner.address, - emptyAddress, - Storage.TransferAmount + balance, - ); // balance + 1ton + fwd - (1ton + balance) = [0] + {fwdFee} and [0] < [minTonsForStorage] - await step( - "Check that trxResult.transactions has correct transaction (test transfer storage fee)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - }, - ); - }); - it("test transfer forward fee 2.0", async () => { - // Let's verify that storage fee was an error trigger by increasing balance by min_storage - // Expect success - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + minTonsForStorage + fwdFee, - notOwner.address, - emptyAddress, - Storage.TransferAmount + balance, - ); // balance + 1ton + minTonsForStorage + fwdFee - (1ton + balance) = [minTonsForStorage] + {fwdFee} - - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - await step( - "Check that trxResult.transactions has correct transaction (test transfer forward fee 2.0)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - balance = await ( - await blockchain.getContract(itemNFT.address) - ).balance; - await step( - "Check that balance is less than minTonsForStorage (test transfer forward fee 2.0)", - () => { - expect(balance).toBeLessThan(minTonsForStorage); - }, - ); - }); - - it("test transfer forward fee single", async () => { - // If transfer is successful NFT supposed to send up to 2 messages - // 1)To the owner_address with forward_amount of coins - // 2)To the response_addr with forward_payload if response_addr is not addr_none - // Each of those messages costs fwd_fee - // In this case we test scenario where only single message required to be sent; - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + Storage.ForwardFee.Base, - notOwner.address, - emptyAddress, - Storage.TransferAmount + balance - minTonsForStorage, - beginCell() - .storeUint(1, 1) - .storeStringTail("testing") - .asSlice(), - ); // balance + 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} - - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - await step( - "Check that trxResult.transactions has correct transaction (test transfer forward fee single)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - }); - - describe("test transfer forward fee double", function () { - beforeEach(() => { - fwdFee = Storage.ForwardFee.Double; - }); - it("should false with only one fwd fee on balance", async () => { - // If transfer is successful NFT supposed to send up to 2 messages - // 1)To the owner_address with forward_amount of coins - // 2)To the response_addr with forward_payload if response_addr is not addr_none - // Each of those messages costs fwd_fee - // In this case we test scenario where both messages required to be sent but balance has funs only for single message - // To do so resp_dst has be a valid address not equal to addr_none - - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + fwdFee, - notOwner.address, - owner.address, - Storage.TransferAmount + balance - Storage.MinTons, - beginCell() - .storeUint(1, 1) - .storeStringTail("testing") - .asSlice(), - ); - - // 1ton + fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {fwdFee} and {fwdFee} < {2 * fwdFee} - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - await step( - "Check that trxResult.transactions has correct transaction (double forward fee, not enough for both)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.InvalidFees, - }); - }, - ); - }); - - // let now check if we have 2 fwdFees on balance - it("should work with 2 fwdFee on balance", async () => { - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.TransferAmount + 2n * fwdFee, - notOwner.address, - owner.address, - Storage.TransferAmount + balance - minTonsForStorage, - beginCell() - .storeUint(1, 1) - .storeStringTail("testing") - .asSlice(), - ); - // 1ton + 2 * fwdFee - (1ton + balance - minTonsForStorage) = [minTonsForStorage] + {2 * fwdFee} - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - balance = await ( - await blockchain.getContract(itemNFT.address) - ).balance; - expect(balance).toBeLessThan(minTonsForStorage); - await step( - "Check that trxResult.transactions has correct transaction (double forward fee, enough for both)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - await step( - "Check that balance is less than minTonsForStorage (double forward fee)", - () => { - expect(balance).toBeLessThan(minTonsForStorage); - }, - ); - }); - }); - // int __test_transfer_success_forward_no_response testing in next test suite - }); - - describe("Transfer Ownership Tests", () => { - it("Test ownership assigned", async () => { - const oldOwner = await itemNFT.getOwner(); - await step("Check that oldOwner equals owner.address", () => { - expect(oldOwner).toEqualAddress(owner.address); - }); - const trxRes = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - owner.address, - 1n, - ); - - const newOwner = await itemNFT.getOwner(); - await step( - "Check that newOwner equals notOwner.address", - () => { - expect(newOwner).toEqualAddress(notOwner.address); - }, - ); - await step( - "Check that trxRes.transactions has correct transaction (ownership assigned)", - () => { - expect(trxRes.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: true, - }); - }, - ); - }); - - it("Test transfer ownership without any messages", async () => { - const trxRes = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - emptyAddress, - 0n, - ); - const newOwner = await itemNFT.getOwner(); - await step( - "Check that newOwner equals notOwner.address (no messages)", - () => { - expect(newOwner).toEqualAddress(notOwner.address); - }, - ); - await step( - "Check that trxRes.transactions does NOT have transaction from itemNFT.address (no messages)", - () => { - expect(trxRes.transactions).not.toHaveTransaction({ - from: itemNFT.address, - }); - }, - ); - }); - - it("Not owner should not be able to transfer ownership", async () => { - const trxResult = await sendTransfer( - itemNFT, - notOwner.getSender(), - Storage.DeployAmount, - notOwner.address, - emptyAddress, - 0n, - ); - await step( - "Check that trxResult.transactions has correct transaction (not owner should not be able to transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - }); - - describe("NOT INITIALIZED TESTS", () => { - const itemIndex: bigint = 100n; - beforeEach(async () => { - itemNFT = blockchain.openContract( - await fromInitItem(null, null, owner.address, itemIndex), - ); - const _deployResult = await itemNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - beginCell().asSlice(), - ); - }); - - it("should not get static data", async () => { - const staticData = await itemNFT.getGetNftData(); - await step( - "Check that staticData.init is 0 (not initialized)", - () => { - expect(staticData.init).toBe(0n); - }, - ); - await step( - "Check that staticData.collectionAddress equals owner.address (not initialized)", - () => { - expect(staticData.collectionAddress).toEqualAddress( - owner.address, - ); - }, - ); - await step( - "Check that staticData.owner is null (not initialized)", - () => { - expect(staticData.owner).toBeNull(); - }, - ); - await step( - "Check that staticData.itemIndex is itemIndex (not initialized)", - () => { - expect(staticData.itemIndex).toBe(itemIndex); - }, - ); - await step( - "Check that staticData.content is null (not initialized)", - () => { - expect(staticData.content).toBeNull(); - }, - ); - }); - - it("should not transfer ownership", async () => { - const trxResult = await sendTransfer( - itemNFT, - owner.getSender(), - Storage.DeployAmount, - notOwner.address, - owner.address, - 0n, - ); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotInit, - }); - }); - - it("should not get static data message", async () => { - const trxResult = await messageGetStaticData(owner, itemNFT); - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotInit, - }); - }); - }); - }); - - NFTCollection.prototype.getNextItemIndex = async function ( - this: NFTCollection, - provider: ContractProvider, - ): Promise { - const res = await this.getGetCollectionData(provider); - return res.nextItemIndex; - }; - - NFTCollection.prototype.getOwner = async function ( - this: NFTCollection, - provider: ContractProvider, - ): Promise
{ - const res = await this.getGetCollectionData(provider); - return res.owner; - }; - - describe("NFT Collection Contract", () => { - let blockchain: Blockchain; - let collectionNFT: SandboxContract; - let itemNFT: SandboxContract; - - let owner: SandboxContract; - let notOwner: SandboxContract; - - let defaultContent: Cell; - let defaultCommonContent: Cell; - let defaultCollectionContent: Cell; - let defaultNFTContent: Cell; - let royaltyParams: RoyaltyParams; - - beforeEach(async () => { - blockchain = await Blockchain.create(); - owner = await blockchain.treasury("owner"); - notOwner = await blockchain.treasury("notOwner"); - - defaultCommonContent = beginCell() - .storeStringTail("common") - .endCell(); - defaultCollectionContent = beginCell() - .storeStringTail("collectionContent") - .endCell(); - - defaultNFTContent = beginCell().storeStringTail("1.json").endCell(); - - defaultContent = beginCell() - .storeRef(defaultCollectionContent) - .storeRef(defaultCommonContent) - .endCell(); - - royaltyParams = { - $$type: "RoyaltyParams", - nominator: 1n, - dominator: 100n, - owner: owner.address, - }; - - collectionNFT = blockchain.openContract( - await fromInitCollection( - owner.address, - 0n, - defaultContent, - royaltyParams, - ), - ); - const deployCollectionMsg: GetRoyaltyParams = { - $$type: "GetRoyaltyParams", - queryId: 0n, - }; - - const deployResult = await collectionNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - deployCollectionMsg, - ); - await step( - "Check that deployResult.transactions has correct transaction (collection)", - () => { - expect(deployResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - deploy: true, - success: true, - }); - }, - ); - }); - - it("should deploy correctly", async () => { - // checking in beforeEach - }); - - it("should get static data correctly", async () => { - const staticData = await collectionNFT.getGetCollectionData(); - await step( - "Check that staticData.owner equals owner.address (collection)", - () => { - expect(staticData.owner).toEqualAddress(owner.address); - }, - ); - await step( - "Check that staticData.nextItemIndex is 0 (collection)", - () => { - expect(staticData.nextItemIndex).toBe(0n); - }, - ); - await step( - "Check that staticData.collectionContent equals defaultCollectionContent (collection)", - () => { - expect(staticData.collectionContent).toEqualCell( - defaultCollectionContent, - ); - }, - ); - }); - - it("should get nft content correctly", async () => { - const content = await collectionNFT.getGetNftContent( - 0n, - defaultContent, - ); - const expectedContent = beginCell() - .storeUint(1, 8) - .storeSlice(defaultCommonContent.asSlice()) - .storeRef(defaultContent) - .endCell(); - await step( - "Check that content equals expectedContent (nft content)", - () => { - expect(content).toEqualCell(expectedContent); - }, - ); - }); - - describe("ROYALTY TESTS", () => { - it("test royalty msg", async () => { - const queryId = randomInt(TestValues.RandomRange) + 1; - - const msg: GetRoyaltyParams = { - $$type: "GetRoyaltyParams", - queryId: BigInt(queryId), - }; - - const trxResult = await collectionNFT.send( - owner.getSender(), - { value: Storage.DeployAmount }, - msg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (royalty msg)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - }, - ); - - const exceptedMsg: Cell = beginCell() - .storeUint(Operations.ReportRoyaltyParams, 32) - .storeUint(queryId, 64) - .storeUint(royaltyParams.nominator, 16) - .storeUint(royaltyParams.dominator, 16) - .storeAddress(royaltyParams.owner) - .endCell(); - expect(trxResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: owner.address, - body: exceptedMsg, - }); - }); - - it("test royalty getter", async () => { - const currRoyaltyParams = - await collectionNFT.getRoyaltyParams(); - expect( - beginCell() - .store(storeRoyaltyParams(currRoyaltyParams)) - .asSlice(), - ).toEqualSlice( - beginCell() - .store(storeRoyaltyParams(royaltyParams)) - .asSlice(), - ); - }); - }); - - describe("NFT DEPLOY TESTS", () => { - it("should deploy NFTItem correctly", async () => { - // checking in beforeEach - }); - - /** - * Helper function to deploy an NFT item - * @param itemIndex - Index of the NFT to deploy - * @param collectionNFT - Collection contract instance - * @param sender - Sender of the deployment transaction - * @param owner - Owner of the deployed NFT - * @returns Promise resolving to the NFT item contract and transaction result - */ - const deployNFT = async ( - itemIndex: bigint, - collectionNFT: SandboxContract, - sender: SandboxContract, - owner: SandboxContract, - ): Promise<[SandboxContract, SendMessageResult]> => { - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - const mintMsg: DeployNFT = { - $$type: "DeployNFT", - queryId: 1n, - itemIndex: itemIndex, - amount: Storage.NftMintAmount, - initNFTBody: beginCell() - .store(storeInitNFTBody(initNFTBody)) - .endCell(), - }; - - const itemNFT = blockchain.openContract( - await fromInitItem( - null, - null, - collectionNFT.address, - itemIndex, - ), - ); - - const trxResult = await collectionNFT.send( - sender.getSender(), - { value: Storage.DeployAmount }, - mintMsg, - ); - return [itemNFT, trxResult]; - }; - - it("should mint NFTItem correctly", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [itemNFT, _trx] = await deployNFT( - nextItemIndex, - collectionNFT, - owner, - owner, - ); - const nftData = await itemNFT.getGetNftData(); - - await step( - "Check that nftData.content equals defaultNFTContent", - () => { - expect(nftData.content).toEqualCell(defaultNFTContent); - }, - ); - await step( - "Check that nftData.owner equals owner.address", - () => { - expect(nftData.owner).toEqualAddress(owner.address); - }, - ); - await step( - "Check that nftData.itemIndex is nextItemIndex", - () => { - expect(nftData.itemIndex).toBe(nextItemIndex); - }, - ); - await step( - "Check that nftData.collectionAddress equals collectionNFT.address", - () => { - expect(nftData.collectionAddress).toEqualAddress( - collectionNFT.address, - ); - }, - ); - }); - - it("should not mint NFTItem if not owner", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [_itemNFT, trx] = await deployNFT( - nextItemIndex, - collectionNFT, - notOwner, - notOwner, - ); - await step( - "Check that trx.transactions has correct transaction (not owner mint)", - () => { - expect(trx.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - - it("should not deploy previous nft", async () => { - let nextItemIndex: bigint = - await collectionNFT.getNextItemIndex(); - for (let i = 0; i < 10; i++) { - const [_itemNFT, _trx] = await deployNFT( - nextItemIndex, - collectionNFT, - owner, - owner, - ); - nextItemIndex++; - } - const [_itemNFT, trx] = await deployNFT( - 0n, - collectionNFT, - owner, - owner, - ); - await step( - "Check that trx.transactions has correct transaction (should not deploy previous nft)", - () => { - expect(trx.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: _itemNFT.address, - deploy: false, - success: false, - exitCode: ErrorCodes.InvalidData, - }); - }, - ); - }); - - it("shouldn't mint item itemIndex > nextItemIndex", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - const [_itemNFT, trx] = await deployNFT( - nextItemIndex + 1n, - collectionNFT, - owner, - owner, - ); - await step( - "Check that trx.transactions has correct transaction (itemIndex > nextItemIndex)", - () => { - expect(trx.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.IncorrectIndex, - }); - }, - ); - }); - - it("test get nft by itemIndex", async () => { - const nextItemIndex = await collectionNFT.getNextItemIndex(); - // deploy new nft to get itemIndex - const [_itemNFT, _trx] = await deployNFT( - nextItemIndex, - collectionNFT, - owner, - owner, - ); - const nftAddress = - await collectionNFT.getGetNftAddressByIndex(nextItemIndex); - const newNFT = blockchain.getContract(nftAddress); - const getData = await (await newNFT).get("get_nft_data"); - const dataNFT = loadGetterTupleNFTData(getData.stack); - await step( - "Check that dataNFT.itemIndex is nextItemIndex", - () => { - expect(dataNFT.itemIndex).toBe(nextItemIndex); - }, - ); - await step( - "Check that dataNFT.collectionAddress equals collectionNFT.address", - () => { - expect(dataNFT.collectionAddress).toEqualAddress( - collectionNFT.address, - ); - }, - ); - }); - }); - - describe("BATCH MINT TESTS", () => { - /** - * Helper function to batch mint NFTs - * @param collectionNFT - Collection contract instance - * @param sender - Sender of the batch mint transaction - * @param owner - Owner of the minted NFTs - * @param count - Number of NFTs to mint - * @param extra - Optional extra index to mint - * @returns Promise resolving to the transaction result - */ - const batchMintNFTProcess = async ( - collectionNFT: SandboxContract, - sender: SandboxContract, - owner: SandboxContract, - count: bigint, - extra: bigint = -1n, - ): Promise => { - const dct = Dictionary.empty( - Dictionary.Keys.BigUint(64), - dictDeployNFTItem, - ); - let i: bigint = 0n; - - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - while (i < count) { - dct.set(i, { - amount: Storage.NftMintAmount, - initNFTBody: initNFTBody, - }); - i += 1n; - } - - if (extra != -1n) { - dct.set(extra, { - amount: Storage.NftMintAmount, - initNFTBody: initNFTBody, - }); - } - - const batchMintNFT: BatchDeploy = { - $$type: "BatchDeploy", - queryId: 0n, - deployList: beginCell().storeDictDirect(dct).endCell(), - }; - - return await collectionNFT.send( - sender.getSender(), - { - value: - Storage.BatchDeployAmount * - (count + TestValues.ExtraValues.BatchMultiplier), - }, - batchMintNFT, - ); - }; - beforeEach(async () => {}); - - it.skip("test max batch mint", async () => { - let L = 1n; - let R = 1000n; - while (R - L > 1) { - const M = (L + R) / 2n; - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - M, - ); - try { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - L = M; - } catch { - R = M; - } - } - console.log("maximum batch amount is", L); - }); - - it("Should batch mint correctly", async () => { - const count = 50n; - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - count, - ); - - await step( - "Check that trxResult.transactions has correct transaction (batch mint success)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - }, - ); - itemNFT = blockchain.openContract( - await fromInitItem( - null, - null, - collectionNFT.address, - count - 1n, - ), - ); - - // it was deployed, that's why we can get it - await step( - "Check that itemNFT.getGetNftData() has property itemIndex", - async () => { - expect(await itemNFT.getGetNftData()).toHaveProperty( - "itemIndex", - count - 1n, - ); - }, - ); - }); - - it("Shouldn't batch mint more than 250 items", async () => { - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - 260n, - ); - - await step( - "Check that trxResult.transactions has correct transaction (should not batch mint more than 250)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - }); - }, - ); - }); - - it("Should not batch mint not owner", async () => { - const trxResult = await batchMintNFTProcess( - collectionNFT, - notOwner, - owner, - 10n, - ); - - await step( - "Check that trxResult.transactions has correct transaction (not owner batch mint)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - describe("!!--DIFF TEST---!!", () => { - it("Should HAVE message in batchDeploy with previous indexes", async () => { - await batchMintNFTProcess(collectionNFT, owner, owner, 50n); - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - 50n, - ); - - itemNFT = blockchain.openContract( - await fromInitItem( - null, - null, - collectionNFT.address, - 10n, - ), - ); // random number - - await step( - "Check that trxResult.transactions has correct transaction (batchDeploy with previous indexes)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: itemNFT.address, - }); - }, - ); - }); - - it("Should THROW if we have index > nextItemIndex", async () => { - const trxResult = await batchMintNFTProcess( - collectionNFT, - owner, - owner, - 50n, - 70n, - ); - - await step( - "Check that trxResult.transactions has correct transaction (index > nextItemIndex)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: false, - }); - }, - ); - }); - }); - }); - - describe("TRANSFER OWNERSHIP TEST", () => { - it("Owner should be able to transfer ownership", async () => { - const changeOwnerMsg: ChangeOwner = { - $$type: "ChangeOwner", - queryId: 1n, - newOwner: notOwner.address, - }; - - const trxResult = await collectionNFT.send( - owner.getSender(), - { value: Storage.ChangeOwnerAmount }, - changeOwnerMsg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (owner transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - }, - ); - await step( - "Check that collectionNFT.getOwner() equals notOwner.address", - async () => { - expect(await collectionNFT.getOwner()).toEqualAddress( - notOwner.address, - ); - }, - ); - }); - it("Not owner should not be able to transfer ownership", async () => { - const changeOwnerMsg: ChangeOwner = { - $$type: "ChangeOwner", - queryId: 1n, - newOwner: owner.address, - }; - - const trxResult = await collectionNFT.send( - notOwner.getSender(), - { value: Storage.ChangeOwnerAmount }, - changeOwnerMsg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (not owner transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - }); - }); -} diff --git a/src/benchmarks/nft/tests/transfer-fee.ts b/src/benchmarks/nft/tests/transfer-fee.ts new file mode 100644 index 0000000000..f877b4b032 --- /dev/null +++ b/src/benchmarks/nft/tests/transfer-fee.ts @@ -0,0 +1,313 @@ +import type { Address, Cell } from "@ton/core"; +import { beginCell } from "@ton/core"; +import { Blockchain } from "@ton/sandbox"; +import type { InitNFTBody } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { + storeInitNFTBody, + type NFTItem, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; +import "@ton/test-utils"; +import { step } from "@/test/allure/allure"; +import { setStoragePrices } from "@/test/utils/gasUtils"; +import { + Storage, + ErrorCodes, + sendTransfer, +} from "@/benchmarks/nft/tests/utils"; + +const globalSetup = async ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + const blockchain = await Blockchain.create(); + const config = blockchain.config; + blockchain.setConfig( + setStoragePrices(config, { + unixTimeSince: 0, + bitPricePerSecond: 0n, + cellPricePerSecond: 0n, + masterChainBitPricePerSecond: 0n, + masterChainCellPricePerSecond: 0n, + }), + ); + const owner = await blockchain.treasury("owner"); + const notOwner = await blockchain.treasury("notOwner"); + const emptyAddress = null; + const defaultContent = beginCell().endCell(); + const itemNFT = blockchain.openContract( + await fromInitItem(null, null, owner.address, 0n), + ); + const deployItemMsg: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultContent, + }; + const deployResult = await itemNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), + ); + await step( + "Check that deployResult.transactions has correct transaction", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + deploy: true, + success: true, + }); + }, + ); + + const balance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + const fwdFee = Storage.ForwardFee.Base; + const fwdFeeDouble = Storage.ForwardFee.Double; + + return { + blockchain, + itemNFT, + owner, + notOwner, + defaultContent, + emptyAddress, + balance, + fwdFee, + fwdFeeDouble, + }; +}; + +export const testTransferFee = ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + async function setup() { + return await globalSetup(fromInitItem); + } + + describe("Transfer ownership Fee cases", () => { + it("should return error if forward amount is too much", async () => { + const { itemNFT, owner, notOwner, emptyAddress } = await setup(); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + Storage.TransferAmount, + ); + await step( + "Check that trxResult.transactions has correct transaction (transfer forward amount too much)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); + }); + it("should return error if storage fee is not enough", async () => { + const { itemNFT, owner, notOwner, emptyAddress, balance, fwdFee } = + await setup(); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + fwdFee, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance, + ); + await step( + "Check that trxResult.transactions has correct transaction (test transfer storage fee)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); + }); + it("should work with 2 fwdFee on balance", async () => { + const { + balance, + fwdFee, + itemNFT, + owner, + notOwner, + emptyAddress, + blockchain, + } = await setup(); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + Storage.MinTons + fwdFee, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance, + ); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + await step( + "Check that trxResult.transactions has correct transaction (test transfer forward fee 2.0)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + const newBalance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + await step( + "Check that balance is less than Storage.MinTons (test transfer forward fee 2.0)", + () => { + expect(newBalance).toBeLessThan(Storage.MinTons); + }, + ); + }); + it("should work with 1 fwdFee on balance", async () => { + const { itemNFT, owner, notOwner, emptyAddress, balance } = + await setup(); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + Storage.ForwardFee.Base, + notOwner.address, + emptyAddress, + Storage.TransferAmount + balance - Storage.MinTons, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + await step( + "Check that trxResult.transactions has correct transaction (test transfer forward fee single)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + }); + }); +}; + +export const testTransferForwardFeeDouble = ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + describe("Transfer forward fee double cases", function () { + const setup = async () => { + return await globalSetup(fromInitItem); + }; + it("should false with only one fwd fee on balance", async () => { + const { itemNFT, owner, notOwner, balance, fwdFeeDouble } = + await setup(); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + fwdFeeDouble, + notOwner.address, + owner.address, + Storage.TransferAmount + balance - Storage.MinTons, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + await step( + "Check that trxResult.transactions has correct transaction (double forward fee, not enough for both)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidFees, + }); + }, + ); + }); + it("should work with 2 fwdFee on balance", async () => { + const { + itemNFT, + owner, + notOwner, + balance, + fwdFeeDouble, + blockchain, + } = await setup(); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.TransferAmount + 2n * fwdFeeDouble, + notOwner.address, + owner.address, + Storage.TransferAmount + balance - Storage.MinTons, + beginCell() + .storeUint(1, 1) + .storeStringTail("testing") + .asSlice(), + ); + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + const newBalance = await ( + await blockchain.getContract(itemNFT.address) + ).balance; + expect(newBalance).toBeLessThan(Storage.MinTons); + await step( + "Check that trxResult.transactions has correct transaction (double forward fee, enough for both)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, + ); + await step( + "Check that balance is less than Storage.MinTons (double forward fee)", + () => { + expect(newBalance).toBeLessThan(Storage.MinTons); + }, + ); + }); + }); +}; diff --git a/src/benchmarks/nft/tests/utils.ts b/src/benchmarks/nft/tests/utils.ts new file mode 100644 index 0000000000..34389e749d --- /dev/null +++ b/src/benchmarks/nft/tests/utils.ts @@ -0,0 +1,203 @@ +// Type imports +import type { + Address, + Slice, + Builder, + TupleItem, + TupleItemInt, + TupleItemSlice, + TupleItemCell, + Sender, +} from "@ton/core"; +// Value imports +import { beginCell, toNano } from "@ton/core"; + +import type { SandboxContract, SendMessageResult } from "@ton/sandbox"; +// NFT Collection imports +import type { + InitNFTBody, + NFTCollection, + Transfer, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { loadInitNFTBody } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; + +// NFT Item imports +import type { NFTData } from "@/benchmarks/nft/tact/output/item_NFTItem"; +import { + storeInitNFTBody, + type NFTItem, +} from "@/benchmarks/nft/tact/output/item_NFTItem"; + +import "@ton/test-utils"; + +/** Operation codes for NFT contract messages */ +export const Operations = { + TransferNft: 0x5fcc3d14, + OwnershipAssignment: 0x05138d91, + Excess: 0xd53276db, + GetStaticData: 0x2fcb26a2, + ReportStaticData: 0x8b771735, + GetRoyaltyParams: 0x693d3950, + ReportRoyaltyParams: 0xa8cb00ad, + EditContent: 0x1a0b9d51, + TransferEditorship: 0x1c04412a, + EditorshipAssigned: 0x511a4463, +} as const; + +/** Storage and transaction related constants */ +export const Storage = { + /** Minimum amount of TONs required for storage */ + MinTons: 50000000n, + /** Amount of TONs for deployment operations */ + DeployAmount: toNano("0.1"), + /** Amount of TONs for transfer operations */ + TransferAmount: toNano("1"), + /** Amount of TONs for batch deployment */ + BatchDeployAmount: toNano("100"), + /** Amount of TONs for ownership change */ + ChangeOwnerAmount: 100000000n, + /** Amount of TONs for NFT minting */ + NftMintAmount: 10000000n, + /** Forward fee values */ + ForwardFee: { + /** Base forward fee for single message */ + Base: 623605n, + /** Forward fee for double message */ + Double: 729606n, + }, +} as const; + +/** Error codes */ +export const ErrorCodes = { + /** Error code for not initialized contract */ + NotInit: 9, + /** Error code for not owner */ + NotOwner: 401, + /** Error code for invalid fees */ + InvalidFees: 402, + /** Error code for incorrect index */ + IncorrectIndex: 402, + /** Error code for invalid data */ + InvalidData: 65535, +} as const; + +/** Test related constants */ +export const TestValues = { + /** Default item index used in tests */ + ItemIndex: 100n, + /** Batch operation sizes */ + BatchSize: { + Min: 1n, + Max: 250n, + Default: 50n, + OverLimit: 260n, + Small: 10n, + }, + /** Range for random number generation */ + RandomRange: 1337, + /** Royalty parameters */ + Royalty: { + Nominator: 1n, + Dominator: 100n, + }, + /** Bit sizes for different data types */ + BitSizes: { + Uint1: 1, + Uint8: 8, + Uint16: 16, + Uint32: 32, + Uint64: 64, + }, + /** Additional test values */ + ExtraValues: { + BatchMultiplier: 10n, + }, +} as const; + +/** Dictionary type for NFT deployment data */ +export type dictDeployNFT = { + amount: bigint; + initNFTBody: InitNFTBody; +}; + +/** Dictionary value parser for NFT deployment */ +export const dictDeployNFTItem = { + serialize: (src: dictDeployNFT, builder: Builder) => { + builder + .storeCoins(src.amount) + .storeRef( + beginCell().store(storeInitNFTBody(src.initNFTBody)).endCell(), + ); + }, + parse: (src: Slice) => { + return { + amount: src.loadCoins(), + initNFTBody: loadInitNFTBody(src.loadRef().asSlice()), + }; + }, +}; + +/** + * Helper function to load NFT data from a tuple of contract getter results + * @param source - Array of tuple items containing NFT data + * @returns Parsed NFT data object + */ +export function loadGetterTupleNFTData(source: TupleItem[]): NFTData { + const _init = (source[0] as TupleItemInt).value; + const _index = (source[1] as TupleItemInt).value; + const _collectionAddress = (source[2] as TupleItemSlice).cell + .asSlice() + .loadAddress(); + const _owner = (source[3] as TupleItemSlice).cell.asSlice().loadAddress(); + const _content = (source[4] as TupleItemCell).cell; + return { + $$type: "NFTData" as const, + init: _init, + itemIndex: _index, + collectionAddress: _collectionAddress, + owner: _owner, + content: _content, + }; +} + +export const getOwner = async ( + collection: SandboxContract, +): Promise
=> { + const res = await collection.getGetCollectionData(); + return res.owner; +}; + +export const getNextItemIndex = async ( + collection: SandboxContract, +): Promise => { + const res = await collection.getGetCollectionData(); + return res.nextItemIndex; +}; + +export const getItemOwner = async ( + item: SandboxContract, +): Promise
=> { + const res = await item.getGetNftData(); + return res.owner!; +}; + +export const sendTransfer = async ( + itemNFT: SandboxContract, + from: Sender, + value: bigint, + newOwner: Address, + responseDestination: Address | null, + forwardAmount: bigint, + forwardPayload: Slice = beginCell().storeUint(0, 1).asSlice(), +): Promise => { + const msg: Transfer = { + $$type: "Transfer", + queryId: 0n, + newOwner: newOwner, + responseDestination: responseDestination, + customPayload: null, + forwardAmount: forwardAmount, + forwardPayload: forwardPayload, + }; + return await itemNFT.send(from, { value }, msg); +}; diff --git a/src/benchmarks/notcoin/notcoin.spec.ts b/src/benchmarks/notcoin/bench.spec.ts similarity index 100% rename from src/benchmarks/notcoin/notcoin.spec.ts rename to src/benchmarks/notcoin/bench.spec.ts diff --git a/src/benchmarks/sbt/sbt.spec.ts b/src/benchmarks/sbt/bench.spec.ts similarity index 100% rename from src/benchmarks/sbt/sbt.spec.ts rename to src/benchmarks/sbt/bench.spec.ts diff --git a/src/benchmarks/update.build.ts b/src/benchmarks/update.build.ts index ace3788bc2..ee7a3de29a 100644 --- a/src/benchmarks/update.build.ts +++ b/src/benchmarks/update.build.ts @@ -216,7 +216,7 @@ const updateCodeSizeResultsFile = async ( const main = async () => { try { - const benchmarkPaths = globSync(["**/*.spec.ts"], { + const benchmarkPaths = globSync(["**/bench.spec.ts"], { cwd: __dirname, }).filter((path) => !path.includes(".test.")); diff --git a/src/benchmarks/wallet-v4/wallet-v4.spec.ts b/src/benchmarks/wallet-v4/bench.spec.ts similarity index 100% rename from src/benchmarks/wallet-v4/wallet-v4.spec.ts rename to src/benchmarks/wallet-v4/bench.spec.ts diff --git a/src/benchmarks/wallet-v5/wallet-v5.spec.ts b/src/benchmarks/wallet-v5/bench.spec.ts similarity index 100% rename from src/benchmarks/wallet-v5/wallet-v5.spec.ts rename to src/benchmarks/wallet-v5/bench.spec.ts From 1b984baedcf52e3482c8bd2dc646149773582cd9 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Sun, 25 May 2025 17:00:11 +0300 Subject: [PATCH 12/22] fix --- src/benchmarks/nft/run.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/benchmarks/nft/run.ts b/src/benchmarks/nft/run.ts index 18dc5f41c3..8f6d83e336 100644 --- a/src/benchmarks/nft/run.ts +++ b/src/benchmarks/nft/run.ts @@ -60,7 +60,7 @@ const fromInitCollection = ( return Promise.resolve(new NFTCollection(address, __gen_init)); }; -export const fromInitItem = ( +const fromInitItem = ( _owner: Address | null, _content: Cell | null, collectionAddress: Address, From 23ba1b2e3bd3c6e350f877a33c8a15229a9ece0b Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Sun, 25 May 2025 17:02:34 +0300 Subject: [PATCH 13/22] fix x2 --- src/benchmarks/nft/tests/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/benchmarks/nft/tests/utils.ts b/src/benchmarks/nft/tests/utils.ts index 34389e749d..e94c21b239 100644 --- a/src/benchmarks/nft/tests/utils.ts +++ b/src/benchmarks/nft/tests/utils.ts @@ -115,7 +115,7 @@ export const TestValues = { } as const; /** Dictionary type for NFT deployment data */ -export type dictDeployNFT = { +type dictDeployNFT = { amount: bigint; initNFTBody: InitNFTBody; }; From 730a19083fc1eac45c3dde4b30349415006edbca Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Sun, 25 May 2025 19:55:27 +0300 Subject: [PATCH 14/22] fix --- src/benchmarks/nft/bench.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/benchmarks/nft/bench.spec.ts b/src/benchmarks/nft/bench.spec.ts index 8110dfe0d0..6e5629af9e 100644 --- a/src/benchmarks/nft/bench.spec.ts +++ b/src/benchmarks/nft/bench.spec.ts @@ -365,6 +365,5 @@ function bench( }); } - import { run } from "@/benchmarks/nft/run"; -run(bench); \ No newline at end of file +run(bench); From a35372702f4bee6390caef680385631a94887ab8 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Wed, 28 May 2025 00:14:34 +0300 Subject: [PATCH 15/22] temp --- package.json | 5 - src/benchmarks/nft/bench.spec.ts | 4 +- src/benchmarks/nft/run.ts | 4 +- src/benchmarks/nft/tact/item.tact | 6 +- src/benchmarks/nft/tests/item.ts | 352 +++++++++++++++++++---- src/benchmarks/nft/tests/transfer-fee.ts | 9 +- src/benchmarks/nft/tests/utils.ts | 4 + 7 files changed, 321 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 2ca9836fff..1f3365a6e3 100644 --- a/package.json +++ b/package.json @@ -45,13 +45,8 @@ "test:fast": "jest --config=./jest-fast.config.js", "test:allure": "rimraf ./allure-results && yarn test && allure serve allure-results", "bench": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --testMatch=\"**/src/benchmarks/**/bench.spec.ts\"", -<<<<<<< HEAD - "bench:test": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --testMatch=\"**/src/benchmarks/**/test.spec.ts\"", - "bench:ci": "yarn gen:contracts:benchmarks && yarn bench && yarn bench:test", -======= "bench:test": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=false jest --testMatch=\"**/src/benchmarks/**/test.spec.ts\"", "bench:ci": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true jest --testMatch=\"**/src/benchmarks/**/bench.spec.ts\" && cross-env PRINT_TABLE=false jest --testMatch=\"**/src/benchmarks/**/test.spec.ts\"", ->>>>>>> origin/main "bench:update": "yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true ts-node src/benchmarks/update.build.ts", "bench:add": "ts-node src/benchmarks/prompt.build.ts && yarn gen:contracts:benchmarks && cross-env PRINT_TABLE=true ADD=true ts-node src/benchmarks/update.build.ts", "coverage": "cross-env COVERAGE=true NODE_OPTIONS=--max_old_space_size=5120 jest --config=./jest-ci.config.js", diff --git a/src/benchmarks/nft/bench.spec.ts b/src/benchmarks/nft/bench.spec.ts index 0ae5185c1e..149f34dfed 100644 --- a/src/benchmarks/nft/bench.spec.ts +++ b/src/benchmarks/nft/bench.spec.ts @@ -42,8 +42,8 @@ import { storeInitNFTBody, } from "@/benchmarks/nft/tact/output/collection_NFTItem"; -import benchmarkResults from "@/benchmarks/nft/results_gas.json"; -import benchmarkCodeSizeResults from "@/benchmarks/nft/results_code_size.json"; +import benchmarkResults from "@/benchmarks/nft/gas.json"; +import benchmarkCodeSizeResults from "@/benchmarks/nft/size.json"; type dictDeployNFT = { amount: bigint; diff --git a/src/benchmarks/nft/run.ts b/src/benchmarks/nft/run.ts index 8f6d83e336..f3c91ce9b2 100644 --- a/src/benchmarks/nft/run.ts +++ b/src/benchmarks/nft/run.ts @@ -17,8 +17,8 @@ import { NFTCollection } from "@/benchmarks/nft/tact/output/collection_NFTCollec import type { RoyaltyParams } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; import { NFTItem } from "@/benchmarks/nft/tact/output/collection_NFTItem"; -import benchmarkResults from "@/benchmarks/nft/results_gas.json"; -import benchmarkCodeSizeResults from "@/benchmarks/nft/results_code_size.json"; +import benchmarkResults from "@/benchmarks/nft/gas.json"; +import benchmarkCodeSizeResults from "@/benchmarks/nft/size.json"; const loadFunCNFTBoc = () => { const bocCollection = readFileSync( diff --git a/src/benchmarks/nft/tact/item.tact b/src/benchmarks/nft/tact/item.tact index 33fc9eab2e..64cf18c916 100644 --- a/src/benchmarks/nft/tact/item.tact +++ b/src/benchmarks/nft/tact/item.tact @@ -49,7 +49,7 @@ contract NFTItem( throwUnless(NotInit, self.owner != null); throwUnless(IncorrectSender, sender() == self.owner); throwUnless(IncorrectForwardPayload, msg.forwardPayload.bits() >= 1); - forceBasechain(msg.newOwner); + forceBasechainFunc(msg.newOwner); let fwdFees = context().readForwardFee(); @@ -80,7 +80,7 @@ contract NFTItem( } if (needResponse) { - forceBasechain(msg.responseDestination!!); + forceBasechainFunc(msg.responseDestination!!); sendMsg( msg.responseDestination!!, restAmount, @@ -105,6 +105,8 @@ contract NFTItem( } } +asm fun forceBasechainFunc(address: Address) { REWRITESTDADDR DROP 333 THROWIF } + inline fun sendMsg(toAddress: Address, amount: Int, op: Int, queryId: Int, payload: Builder, sendMode: Int) { message(MessageParameters { bounce: false, diff --git a/src/benchmarks/nft/tests/item.ts b/src/benchmarks/nft/tests/item.ts index 20270bf53b..d078a2014c 100644 --- a/src/benchmarks/nft/tests/item.ts +++ b/src/benchmarks/nft/tests/item.ts @@ -1,4 +1,4 @@ -import type { Address, Cell } from "@ton/core"; +import { Address, type Cell } from "@ton/core"; import { beginCell } from "@ton/core"; import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; import { Blockchain } from "@ton/sandbox"; @@ -9,6 +9,8 @@ import type { import { storeInitNFTBody, type NFTItem, + IncorrectForwardPayload, + IncorrectDeployer, } from "@/benchmarks/nft/tact/output/item_NFTItem"; import "@ton/test-utils"; import { setStoragePrices } from "@/test/utils/gasUtils"; @@ -20,6 +22,22 @@ import { sendTransfer, } from "@/benchmarks/nft/tests/utils"; +const messageGetStaticData = async ( + sender: SandboxContract, + itemNFT: SandboxContract, +) => { + const msg: GetStaticData = { + $$type: "GetStaticData", + queryId: 1n, + }; + const trxResult = await itemNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + return trxResult; +}; + const globalSetup = async ( fromInitItem: ( owner: Address | null, @@ -71,6 +89,13 @@ const globalSetup = async ( }); }, ); + + const notInitItem = blockchain.openContract( + await fromInitItem(null, null, owner.address, 1n), + ); + + await messageGetStaticData(owner, notInitItem); // deploy in sandbox + return { blockchain, itemNFT, @@ -78,10 +103,12 @@ const globalSetup = async ( notOwner, defaultContent, emptyAddress, - } as const; + notInitItem, + }; }; -export const testItem = ( + +const testGetStaticData = ( fromInitItem: ( owner: Address | null, content: Cell | null, @@ -93,29 +120,55 @@ export const testItem = ( return await globalSetup(fromInitItem); }; - describe("NFT Item Contract", () => { - const messageGetStaticData = async ( - sender: SandboxContract, - itemNFT: SandboxContract, - ) => { - const msg: GetStaticData = { - $$type: "GetStaticData", - queryId: 1n, - }; - const trxResult = await itemNFT.send( - sender.getSender(), - { value: Storage.DeployAmount }, - msg, + describe("Get Static Data", () => { + it("should get static data correctly", async () => { + const { itemNFT, owner } = await setup(); + const trxResult = await messageGetStaticData(owner, itemNFT); + await step( + "Check that trxResult.transactions has correct transaction (get static data)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: true, + }); + }, ); - return trxResult; - }; - - it("should deploy correctly", async () => { - // check on setup }); + it("should throw exit code if nft not initialized", async () => { + const { notInitItem, owner } = await setup(); + const trxResult = await messageGetStaticData(owner, notInitItem); + await step( + "Check that trxResult.transactions has correct transaction (not initialized)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: notInitItem.address, + success: false, + exitCode: ErrorCodes.NotInit, + }); + }, + ); + }); + }); +}; - it("should get nft data correctly", async () => { +const testGetNftData = ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + const setup = async () => { + return await globalSetup(fromInitItem); + }; + + describe("Get Nft Data", () => { + it("should get nft data correctly when item is initialized", async () => { const { itemNFT, owner, defaultContent } = await setup(); + const staticData = await itemNFT.getGetNftData(); await step("Check that staticData.init is -1", () => { expect(staticData.init).toBe(-1n); @@ -144,16 +197,225 @@ export const testItem = ( }, ); }); - it("should get static data correctly", async () => { + + it("should get nft data correctly when item is not initialized", async () => { + const { notInitItem, owner } = await setup(); + + const staticData = await notInitItem.getGetNftData(); + + await step("Check that staticData.init is 0", () => { + expect(staticData.init).toBe(0n); + }); + + await step("Check that staticData.itemIndex is 1", () => { + expect(staticData.itemIndex).toBe(1n); + }); + + await step( + "Check that staticData.collectionAddress equals owner.address", + () => { + expect(staticData.collectionAddress).toEqualAddress( + owner.address, + ); + }, + ); + + await step( + "Check that staticData.owner equals owner.address", + () => { + expect(staticData.owner).toEqual(null); + }, + ); + await step( + "Check that staticData.content equals defaultContent", + () => { + expect(staticData.content).toEqual(null); + }, + ); + }); + }); +}; + +const testDeploy = ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + const setup = async () => { + return await globalSetup(fromInitItem); + }; + + describe("Deploy", () => { + it("should deploy correctly", async () => { + await setup(); + }); + + it("should throw exit code if item is already initialized", async () => { + const { itemNFT, owner, defaultContent } = await setup(); + + const deployItemMsg: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultContent, + }; + + const trxResult = await itemNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), + ); + + await step( + "Check that trxResult.transactions has correct transaction (item is already initialized)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidData, + }); + }, + ); + }); + + it("should throw exit code if deploy not from collection", async () => { + const { notInitItem, notOwner, defaultContent } = await setup(); + + const deployItemMsg: InitNFTBody = { + $$type: "InitNFTBody", + owner: notOwner.address, + content: defaultContent, + }; + + const deployResult = await notInitItem.send( + notOwner.getSender(), + { value: Storage.DeployAmount }, + beginCell().store(storeInitNFTBody(deployItemMsg)).asSlice(), + ); + + await step( + "Check that trxResult.transactions has correct transaction (deploy not from collection)", + () => { + expect(deployResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: notInitItem.address, + success: false, + exitCode: Number(IncorrectDeployer), + }); + }, + ); + }); + }); +}; + +const testTransfer = ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + const setup = async () => { + return await globalSetup(fromInitItem); + }; + + describe("Transfer", () => { + it("should throw exit code if item is not initialized", async () => { + const { notInitItem, owner } = await setup(); + const trxResult = await sendTransfer( + notInitItem, + owner.getSender(), + Storage.DeployAmount, + owner.address, + null, + 0n, + ); + await step( + "Check that trxResult.transactions has correct transaction (item is not initialized)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: notInitItem.address, + success: false, + exitCode: ErrorCodes.NotInit, + }); + }, + ); + }); + + it("should return error if not owner tries to transfer ownership", async () => { + const { itemNFT, notOwner, emptyAddress } = await setup(); + const trxResult = await sendTransfer( + itemNFT, + notOwner.getSender(), + Storage.DeployAmount, + notOwner.address, + emptyAddress, + 0n, + ); + await step( + "Check that trxResult.transactions has correct transaction (not owner should not be able to transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + + it("should throw exit code if forward payload is less than 1", async () => { const { itemNFT, owner } = await setup(); - const trxResult = await messageGetStaticData(owner, itemNFT); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + owner.address, + null, + 0n, + beginCell().asSlice(), + ); + await step( - "Check that trxResult.transactions has correct transaction (get static data)", + "Check that trxResult.transactions has correct transaction (forward payload is less than 1)", () => { expect(trxResult.transactions).toHaveTransaction({ from: owner.address, to: itemNFT.address, - success: true, + success: false, + exitCode: Number(IncorrectForwardPayload), + }); + }, + ); + }); + + it("should throw exit code if newOwner isnot from basechain", async () => { + const { itemNFT, owner } = await setup(); + const trxResult = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + Address.parse( + "Ef8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAU", + ), + null, + 0n, + ); + + await step( + "Check that trxResult.transactions has correct transaction (newOwner is not from basechain)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: itemNFT.address, + success: false, + exitCode: ErrorCodes.InvalidDestinationWorkchain, }); }, ); @@ -214,27 +476,21 @@ export const testItem = ( }, ); }); - it("should return error if not owner tries to transfer ownership", async () => { - const { itemNFT, notOwner, emptyAddress } = await setup(); - const trxResult = await sendTransfer( - itemNFT, - notOwner.getSender(), - Storage.DeployAmount, - notOwner.address, - emptyAddress, - 0n, - ); - await step( - "Check that trxResult.transactions has correct transaction (not owner should not be able to transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: itemNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); }); }; + +export const testItem = ( + fromInitItem: ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, + ) => Promise, +) => { + describe("NFT Item Contract", () => { + testGetStaticData(fromInitItem); + testGetNftData(fromInitItem); + testDeploy(fromInitItem); + testTransfer(fromInitItem); + }); +}; \ No newline at end of file diff --git a/src/benchmarks/nft/tests/transfer-fee.ts b/src/benchmarks/nft/tests/transfer-fee.ts index f877b4b032..761b6a383c 100644 --- a/src/benchmarks/nft/tests/transfer-fee.ts +++ b/src/benchmarks/nft/tests/transfer-fee.ts @@ -5,6 +5,7 @@ import type { InitNFTBody } from "@/benchmarks/nft/tact/output/collection_NFTCol import { storeInitNFTBody, type NFTItem, + InvalidFees, } from "@/benchmarks/nft/tact/output/item_NFTItem"; import "@ton/test-utils"; import { step } from "@/test/allure/allure"; @@ -112,7 +113,7 @@ export const testTransferFee = ( from: owner.address, to: itemNFT.address, success: false, - exitCode: ErrorCodes.InvalidFees, + exitCode: Number(InvalidFees), }); }, ); @@ -135,7 +136,7 @@ export const testTransferFee = ( from: owner.address, to: itemNFT.address, success: false, - exitCode: ErrorCodes.InvalidFees, + exitCode: Number(InvalidFees), }); }, ); @@ -248,7 +249,7 @@ export const testTransferForwardFeeDouble = ( from: owner.address, to: itemNFT.address, success: false, - exitCode: ErrorCodes.InvalidFees, + exitCode: Number(InvalidFees), }); await step( "Check that trxResult.transactions has correct transaction (double forward fee, not enough for both)", @@ -257,7 +258,7 @@ export const testTransferForwardFeeDouble = ( from: owner.address, to: itemNFT.address, success: false, - exitCode: ErrorCodes.InvalidFees, + exitCode: Number(InvalidFees), }); }, ); diff --git a/src/benchmarks/nft/tests/utils.ts b/src/benchmarks/nft/tests/utils.ts index e94c21b239..5b57152824 100644 --- a/src/benchmarks/nft/tests/utils.ts +++ b/src/benchmarks/nft/tests/utils.ts @@ -77,8 +77,12 @@ export const ErrorCodes = { InvalidFees: 402, /** Error code for incorrect index */ IncorrectIndex: 402, + /** Error code for incorrect deployer */ + IncorrectDeployer: 401, /** Error code for invalid data */ InvalidData: 65535, + /** Error code for invalid destination workchain */ + InvalidDestinationWorkchain: 333, } as const; /** Test related constants */ From 5d0407cbc127073e408cc32bf9b3cff095f22dde Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Wed, 28 May 2025 18:17:20 +0300 Subject: [PATCH 16/22] upd --- spell/cspell-list.txt | 1 + src/benchmarks/nft/test.spec.ts | 51 +-- src/benchmarks/nft/tests/collection.ts | 536 ++++++++++++++--------- src/benchmarks/nft/tests/item.ts | 157 ++++--- src/benchmarks/nft/tests/transfer-fee.ts | 6 +- 5 files changed, 435 insertions(+), 316 deletions(-) diff --git a/spell/cspell-list.txt b/spell/cspell-list.txt index 11b6b8b52d..e4bf8af745 100644 --- a/spell/cspell-list.txt +++ b/spell/cspell-list.txt @@ -1,3 +1,4 @@ +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAU Aksakov Aliaksandr alnum diff --git a/src/benchmarks/nft/test.spec.ts b/src/benchmarks/nft/test.spec.ts index e94454db8c..922876ddec 100644 --- a/src/benchmarks/nft/test.spec.ts +++ b/src/benchmarks/nft/test.spec.ts @@ -1,62 +1,19 @@ -import type { Address, Cell } from "@ton/core"; - -import type { NFTItem } from "@/benchmarks/nft/tact/output/collection_NFTItem"; -import type { - NFTCollection, - RoyaltyParams, -} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; - -import { testItem } from "@/benchmarks/nft/tests/item"; -import { - testTransferFee, - testTransferForwardFeeDouble, -} from "@/benchmarks/nft/tests/transfer-fee"; +import { type FromInitItem, testItem } from "@/benchmarks/nft/tests/item"; import { + type FromInitCollection, testCollection, - testDeploy, - testRoyalty, - testBatchDeploy, } from "@/benchmarks/nft/tests/collection"; import type { BenchmarkResult, CodeSizeResult } from "@/benchmarks/utils/gas"; -type FromInitItem = ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, -) => Promise; -type FromInitCollection = ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, -) => Promise; - -const testNFTItem = (fromInitItem: FromInitItem) => { - testItem(fromInitItem); - testTransferFee(fromInitItem); - testTransferForwardFeeDouble(fromInitItem); -}; - -const testNFTCollection = ( - fromInitCollection: FromInitCollection, - fromInitItem: FromInitItem, -) => { - testCollection(fromInitCollection); - testRoyalty(fromInitCollection); - testDeploy(fromInitCollection, fromInitItem); - testBatchDeploy(fromInitCollection, fromInitItem); -}; - export const testNFT = ( _benchmarkResults: BenchmarkResult, _codeSizeResults: CodeSizeResult, fromInitCollection: FromInitCollection, fromInitItem: FromInitItem, ) => { - testNFTItem(fromInitItem); - testNFTCollection(fromInitCollection, fromInitItem); + testItem(fromInitItem); + testCollection(fromInitCollection, fromInitItem); }; import { run } from "@/benchmarks/nft/run"; diff --git a/src/benchmarks/nft/tests/collection.ts b/src/benchmarks/nft/tests/collection.ts index eb268e953d..f47df118e8 100644 --- a/src/benchmarks/nft/tests/collection.ts +++ b/src/benchmarks/nft/tests/collection.ts @@ -35,14 +35,21 @@ import { dictDeployNFTItem, } from "@/benchmarks/nft/tests/utils"; -const globalSetup = async ( - fromInitCollection: ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) => Promise, -) => { +type FromInitItem = ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, +) => Promise; + +export type FromInitCollection = ( + owner: Address, + index: bigint, + content: Cell, + royaltyParams: RoyaltyParams, +) => Promise; + +const globalSetup = async (fromInitCollection: FromInitCollection) => { const blockchain = await Blockchain.create(); const owner = await blockchain.treasury("owner"); const notOwner = await blockchain.treasury("notOwner"); @@ -108,162 +115,21 @@ const globalSetup = async ( }; }; -export const testCollection = ( - fromInitCollection: ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) => Promise, -) => { - const setup = async () => { - return await globalSetup(fromInitCollection); - }; - - describe("NFT Collection Contract", () => { - it("should deploy correctly", async () => { - await setup(); - // checking in setup - }); - - it("should get static data correctly", async () => { - const { collectionNFT, owner, defaultCollectionContent } = - await setup(); - const staticData = await collectionNFT.getGetCollectionData(); - await step( - "Check that staticData.owner equals owner.address (collection)", - () => { - expect(staticData.owner).toEqualAddress(owner.address); - }, - ); - await step( - "Check that staticData.nextItemIndex is 0 (collection)", - () => { - expect(staticData.nextItemIndex).toBe(0n); - }, - ); - await step( - "Check that staticData.collectionContent equals defaultCollectionContent (collection)", - () => { - expect(staticData.collectionContent).toEqualCell( - defaultCollectionContent, - ); - }, - ); - }); - - it("should get nft content correctly", async () => { - const { collectionNFT, defaultContent, defaultCommonContent } = - await setup(); - const content = await collectionNFT.getGetNftContent( - 0n, - defaultContent, - ); - const expectedContent = beginCell() - .storeUint(1, 8) - .storeSlice(defaultCommonContent.asSlice()) - .storeRef(defaultContent) - .endCell(); - await step( - "Check that content equals expectedContent (nft content)", - () => { - expect(content).toEqualCell(expectedContent); - }, - ); - }); - - it("should transfer ownership correctly", async () => { - const { collectionNFT, owner, notOwner } = await setup(); - const changeOwnerMsg: ChangeOwner = { - $$type: "ChangeOwner", - queryId: 1n, - newOwner: notOwner.address, - }; - - const trxResult = await collectionNFT.send( - owner.getSender(), - { value: Storage.ChangeOwnerAmount }, - changeOwnerMsg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (owner transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: owner.address, - to: collectionNFT.address, - success: true, - }); - }, - ); - await step( - "Check that collectionNFT.getOwner() equals notOwner.address", - async () => { - expect(await getOwner(collectionNFT)).toEqualAddress( - notOwner.address, - ); - }, - ); - }); - it("should return error if not owner tries to transfer ownership", async () => { - const { collectionNFT, owner, notOwner } = await setup(); - const changeOwnerMsg: ChangeOwner = { - $$type: "ChangeOwner", - queryId: 1n, - newOwner: owner.address, - }; - - const trxResult = await collectionNFT.send( - notOwner.getSender(), - { value: Storage.ChangeOwnerAmount }, - changeOwnerMsg, - ); - - await step( - "Check that trxResult.transactions has correct transaction (not owner transfer ownership)", - () => { - expect(trxResult.transactions).toHaveTransaction({ - from: notOwner.address, - to: collectionNFT.address, - success: false, - exitCode: ErrorCodes.NotOwner, - }); - }, - ); - }); - }); -}; - -export const testRoyalty = ( - fromInitCollection: ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) => Promise, -) => { +const testEmptyMessages = (fromInitCollection: FromInitCollection) => { const setup = async () => { return await globalSetup(fromInitCollection); }; - describe("Royalty cases", () => { - it("should send royalty msg correctly", async () => { - const { collectionNFT, owner, royaltyParams } = await setup(); - const queryId = 0n; - - const msg: GetRoyaltyParams = { - $$type: "GetRoyaltyParams", - queryId: BigInt(queryId), - }; - + describe("Empty messages cases", () => { + it("should ignore empty messages", async () => { + const { collectionNFT, owner } = await setup(); const trxResult = await collectionNFT.send( owner.getSender(), { value: Storage.DeployAmount }, - msg, + null, ); - await step( - "Check that trxResult.transactions has correct transaction (royalty msg)", + "Check that trxResult.transactions has correct transaction (empty messages)", () => { expect(trxResult.transactions).toHaveTransaction({ from: owner.address, @@ -272,48 +138,13 @@ export const testRoyalty = ( }); }, ); - - const exceptedMsg: Cell = beginCell() - .storeUint(Operations.ReportRoyaltyParams, 32) - .storeUint(queryId, 64) - .storeUint(royaltyParams.nominator, 16) - .storeUint(royaltyParams.dominator, 16) - .storeAddress(royaltyParams.owner) - .endCell(); - expect(trxResult.transactions).toHaveTransaction({ - from: collectionNFT.address, - to: owner.address, - body: exceptedMsg, - }); - }); - - it("should get royalty params correctly", async () => { - const { collectionNFT, royaltyParams } = await setup(); - const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); - expect( - beginCell() - .store(storeRoyaltyParams(currRoyaltyParams)) - .asSlice(), - ).toEqualSlice( - beginCell().store(storeRoyaltyParams(royaltyParams)).asSlice(), - ); }); }); }; -export const testDeploy = ( - fromInitCollection: ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) => Promise, - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, +const testDeployItem = ( + fromInitCollection: FromInitCollection, + fromInitItem: FromInitItem, ) => { describe("NFT deploy cases", () => { it("should deploy NFTItem correctly", async () => { @@ -528,19 +359,69 @@ export const testDeploy = ( }); }; -export const testBatchDeploy = ( - fromInitCollection: ( - owner: Address, - index: bigint, - content: Cell, - royaltyParams: RoyaltyParams, - ) => Promise, - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, +const testRoyalty = (fromInitCollection: FromInitCollection) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Royalty cases", () => { + it("should send royalty msg correctly", async () => { + const { collectionNFT, owner, royaltyParams } = await setup(); + const queryId = 0n; + + const msg: GetRoyaltyParams = { + $$type: "GetRoyaltyParams", + queryId: BigInt(queryId), + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.DeployAmount }, + msg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (royalty msg)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + + const exceptedMsg: Cell = beginCell() + .storeUint(Operations.ReportRoyaltyParams, 32) + .storeUint(queryId, 64) + .storeUint(royaltyParams.nominator, 16) + .storeUint(royaltyParams.dominator, 16) + .storeAddress(royaltyParams.owner) + .endCell(); + expect(trxResult.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: owner.address, + body: exceptedMsg, + }); + }); + + it("should get royalty params correctly", async () => { + const { collectionNFT, royaltyParams } = await setup(); + const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); + expect( + beginCell() + .store(storeRoyaltyParams(currRoyaltyParams)) + .asSlice(), + ).toEqualSlice( + beginCell().store(storeRoyaltyParams(royaltyParams)).asSlice(), + ); + }); + }); +}; + +const testBatchDeploy = ( + fromInitCollection: FromInitCollection, + fromInitItem: FromInitItem, ) => { const setup = async () => { return await globalSetup(fromInitCollection); @@ -725,3 +606,246 @@ export const testBatchDeploy = ( }); }); }; + +const testChangeOwner = (fromInitCollection: FromInitCollection) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Change owner cases", () => { + it("should transfer ownership correctly", async () => { + const { collectionNFT, owner, notOwner } = await setup(); + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: notOwner.address, + }; + + const trxResult = await collectionNFT.send( + owner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: owner.address, + to: collectionNFT.address, + success: true, + }); + }, + ); + await step( + "Check that collectionNFT.getOwner() equals notOwner.address", + async () => { + expect(await getOwner(collectionNFT)).toEqualAddress( + notOwner.address, + ); + }, + ); + }); + + it("should return error if not owner tries to transfer ownership", async () => { + const { collectionNFT, owner, notOwner } = await setup(); + const changeOwnerMsg: ChangeOwner = { + $$type: "ChangeOwner", + queryId: 1n, + newOwner: owner.address, + }; + + const trxResult = await collectionNFT.send( + notOwner.getSender(), + { value: Storage.ChangeOwnerAmount }, + changeOwnerMsg, + ); + + await step( + "Check that trxResult.transactions has correct transaction (not owner transfer ownership)", + () => { + expect(trxResult.transactions).toHaveTransaction({ + from: notOwner.address, + to: collectionNFT.address, + success: false, + exitCode: ErrorCodes.NotOwner, + }); + }, + ); + }); + }); +}; + +const testGetCollectionData = (fromInitCollection: FromInitCollection) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Get collection data cases", () => { + it("should get collection data correctly", async () => { + const { collectionNFT, owner, defaultCollectionContent } = + await setup(); + const staticData = await collectionNFT.getGetCollectionData(); + await step( + "Check that staticData.owner equals owner.address", + () => { + expect(staticData.owner).toEqualAddress(owner.address); + }, + ); + await step("Check that staticData.nextItemIndex is 0", () => { + expect(staticData.nextItemIndex).toBe(0n); + }); + await step( + "Check that staticData.collectionContent equals defaultCollectionContent", + () => { + expect(staticData.collectionContent).toEqualCell( + defaultCollectionContent, + ); + }, + ); + }); + }); +}; + +const testGetNftAddressByIndex = ( + fromInitCollection: FromInitCollection, + fromInitItem: FromInitItem, +) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Get nft address by index cases", () => { + const deployNFT = async ( + itemIndex: bigint, + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + defaultNFTContent: Cell, + blockchain: Blockchain, + ): Promise<[SandboxContract, SendMessageResult]> => { + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + const mintMsg: DeployNFT = { + $$type: "DeployNFT", + queryId: 1n, + itemIndex: itemIndex, + amount: Storage.NftMintAmount, + initNFTBody: beginCell() + .store(storeInitNFTBody(initNFTBody)) + .endCell(), + }; + + const itemNFT = blockchain.openContract( + await fromInitItem( + null, + null, + collectionNFT.address, + itemIndex, + ), + ); + + const trxResult = await collectionNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + mintMsg, + ); + return [itemNFT, trxResult]; + }; + + it("should get nft address by index correctly", async () => { + const { collectionNFT, owner, defaultNFTContent, blockchain } = + await setup(); + const nftAddress = await collectionNFT.getGetNftAddressByIndex(0n); + + const [_itemNFT, trxDeploy] = await deployNFT( + 0n, + collectionNFT, + owner, + owner, + defaultNFTContent, + blockchain, + ); + + await step( + "Check that trxDeploy.transactions has correct transaction (deploy item)", + () => { + expect(trxDeploy.transactions).toHaveTransaction({ + from: collectionNFT.address, + to: nftAddress, + success: true, + }); + }, + ); + }); + }); +}; + +const testGetRoyaltyParams = (fromInitCollection: FromInitCollection) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Get royalty params cases", () => { + it("should get royalty params correctly", async () => { + const { collectionNFT, royaltyParams } = await setup(); + const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); + expect( + beginCell() + .store(storeRoyaltyParams(currRoyaltyParams)) + .asSlice(), + ).toEqualSlice( + beginCell().store(storeRoyaltyParams(royaltyParams)).asSlice(), + ); + }); + }); +}; + +const testGetNftContent = (fromInitCollection: FromInitCollection) => { + const setup = async () => { + return await globalSetup(fromInitCollection); + }; + + describe("Get nft content cases", () => { + it("should get nft content correctly", async () => { + const { collectionNFT, defaultContent, defaultCommonContent } = + await setup(); + const content = await collectionNFT.getGetNftContent( + 0n, + defaultContent, + ); + const expectedContent = beginCell() + .storeUint(1, 8) + .storeSlice(defaultCommonContent.asSlice()) + .storeRef(defaultContent) + .endCell(); + await step( + "Check that content equals expectedContent (nft content)", + () => { + expect(content).toEqualCell(expectedContent); + }, + ); + }); + }); +}; + +export const testCollection = ( + fromInitCollection: FromInitCollection, + fromInitItem: FromInitItem, +) => { + describe("NFT Collection Contract", () => { + testEmptyMessages(fromInitCollection); + testDeployItem(fromInitCollection, fromInitItem); + testRoyalty(fromInitCollection); + testBatchDeploy(fromInitCollection, fromInitItem); + testChangeOwner(fromInitCollection); + testGetCollectionData(fromInitCollection); + testGetNftAddressByIndex(fromInitCollection, fromInitItem); + testGetRoyaltyParams(fromInitCollection); + testGetNftContent(fromInitCollection); + }); +}; diff --git a/src/benchmarks/nft/tests/item.ts b/src/benchmarks/nft/tests/item.ts index d078a2014c..90ec54af5a 100644 --- a/src/benchmarks/nft/tests/item.ts +++ b/src/benchmarks/nft/tests/item.ts @@ -1,16 +1,18 @@ -import { Address, type Cell } from "@ton/core"; +import { Address, toNano, type Cell } from "@ton/core"; import { beginCell } from "@ton/core"; import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; import { Blockchain } from "@ton/sandbox"; -import type { - GetStaticData, - InitNFTBody, +import { + OwnershipAssigned, + type GetStaticData, + type InitNFTBody, } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; import { storeInitNFTBody, type NFTItem, - IncorrectForwardPayload, + IncorrectForwardPayload, IncorrectDeployer, + Excesses, } from "@/benchmarks/nft/tact/output/item_NFTItem"; import "@ton/test-utils"; import { setStoragePrices } from "@/test/utils/gasUtils"; @@ -22,6 +24,18 @@ import { sendTransfer, } from "@/benchmarks/nft/tests/utils"; +import { + testTransferFee, + testTransferForwardFeeDouble, +} from "@/benchmarks/nft/tests/transfer-fee"; + +export type FromInitItem = ( + owner: Address | null, + content: Cell | null, + collectionAddress: Address, + itemIndex: bigint, +) => Promise; + const messageGetStaticData = async ( sender: SandboxContract, itemNFT: SandboxContract, @@ -38,14 +52,7 @@ const messageGetStaticData = async ( return trxResult; }; -const globalSetup = async ( - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) => { +const globalSetup = async (fromInitItem: FromInitItem) => { const blockchain = await Blockchain.create(); const config = blockchain.config; blockchain.setConfig( @@ -94,7 +101,7 @@ const globalSetup = async ( await fromInitItem(null, null, owner.address, 1n), ); - await messageGetStaticData(owner, notInitItem); // deploy in sandbox + await messageGetStaticData(owner, notInitItem); // deploy in sandbox return { blockchain, @@ -107,15 +114,7 @@ const globalSetup = async ( }; }; - -const testGetStaticData = ( - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) => { +const testGetStaticData = (fromInitItem: FromInitItem) => { const setup = async () => { return await globalSetup(fromInitItem); }; @@ -153,14 +152,7 @@ const testGetStaticData = ( }); }; -const testGetNftData = ( - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) => { +const testGetNftData = (fromInitItem: FromInitItem) => { const setup = async () => { return await globalSetup(fromInitItem); }; @@ -236,14 +228,7 @@ const testGetNftData = ( }); }; -const testDeploy = ( - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) => { +const testDeploy = (fromInitItem: FromInitItem) => { const setup = async () => { return await globalSetup(fromInitItem); }; @@ -289,7 +274,7 @@ const testDeploy = ( owner: notOwner.address, content: defaultContent, }; - + const deployResult = await notInitItem.send( notOwner.getSender(), { value: Storage.DeployAmount }, @@ -311,14 +296,7 @@ const testDeploy = ( }); }; -const testTransfer = ( - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) => { +const testTransfer = (fromInitItem: FromInitItem) => { const setup = async () => { return await globalSetup(fromInitItem); }; @@ -395,7 +373,7 @@ const testTransfer = ( ); }); - it("should throw exit code if newOwner isnot from basechain", async () => { + it("should throw exit code if newOwner isn't from basechain", async () => { const { itemNFT, owner } = await setup(); const trxResult = await sendTransfer( itemNFT, @@ -450,6 +428,7 @@ const testTransfer = ( }, ); }); + it("should transfer ownership without any messages", async () => { const { itemNFT, owner, notOwner, emptyAddress } = await setup(); const trxRes = await sendTransfer( @@ -476,21 +455,83 @@ const testTransfer = ( }, ); }); + + it("should transfer ownership with forward payload", async () => { + const { itemNFT, owner, notOwner } = await setup(); + const forwardAmount = toNano(0.1); + const forwardPayload = beginCell() + .storeStringTail("test forward payload") + .asSlice(); + + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + null, + forwardAmount, + forwardPayload, + ); + + const expectedBody = beginCell() + .storeUint(OwnershipAssigned, 32) + .storeUint(0n, 64) + .storeAddress(owner.address) + .storeSlice(forwardPayload) + .endCell(); + + await step( + "Check that trxRes.transactions has correct transaction (ownership assigned with forward payload)", + () => { + expect(trxRes.transactions).toHaveTransaction({ + from: itemNFT.address, + to: notOwner.address, + value: forwardAmount, + body: expectedBody, + }); + }, + ); + }); + + it("should transfer ownership with response destination", async () => { + const { itemNFT, owner, notOwner } = await setup(); + const forwardAmount = 0n; + + const trxRes = await sendTransfer( + itemNFT, + owner.getSender(), + Storage.DeployAmount, + notOwner.address, + owner.address, + forwardAmount, + ); + + const expectedBody = beginCell() + .storeUint(Excesses, 32) + .storeUint(0n, 64) + .endCell(); + + await step( + "Check that trxRes.transactions has correct transaction (ownership assigned with response destination)", + () => { + expect(trxRes.transactions).toHaveTransaction({ + from: itemNFT.address, + to: owner.address, + body: expectedBody, + }); + }, + ); + }); }); }; -export const testItem = ( - fromInitItem: ( - owner: Address | null, - content: Cell | null, - collectionAddress: Address, - itemIndex: bigint, - ) => Promise, -) => { +export const testItem = (fromInitItem: FromInitItem) => { describe("NFT Item Contract", () => { testGetStaticData(fromInitItem); testGetNftData(fromInitItem); testDeploy(fromInitItem); testTransfer(fromInitItem); + testTransferFee(fromInitItem); + testTransferForwardFeeDouble(fromInitItem); }); -}; \ No newline at end of file +}; diff --git a/src/benchmarks/nft/tests/transfer-fee.ts b/src/benchmarks/nft/tests/transfer-fee.ts index 761b6a383c..f26209f39c 100644 --- a/src/benchmarks/nft/tests/transfer-fee.ts +++ b/src/benchmarks/nft/tests/transfer-fee.ts @@ -10,11 +10,7 @@ import { import "@ton/test-utils"; import { step } from "@/test/allure/allure"; import { setStoragePrices } from "@/test/utils/gasUtils"; -import { - Storage, - ErrorCodes, - sendTransfer, -} from "@/benchmarks/nft/tests/utils"; +import { Storage, sendTransfer } from "@/benchmarks/nft/tests/utils"; const globalSetup = async ( fromInitItem: ( From a35260e9a2905723c7fc78c2709cb5954d46f07f Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Wed, 28 May 2025 18:19:11 +0300 Subject: [PATCH 17/22] delete unused filter --- src/benchmarks/update.build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/benchmarks/update.build.ts b/src/benchmarks/update.build.ts index 783a499e16..1cd0ece75a 100644 --- a/src/benchmarks/update.build.ts +++ b/src/benchmarks/update.build.ts @@ -218,7 +218,7 @@ const main = async () => { try { const benchmarkPaths = globSync(["**/bench.spec.ts"], { cwd: __dirname, - }).filter((path) => !path.includes(".test.")); + }); const benchmarkName = process.argv[2]; From fee14a1a1a9f7ffa537167f406536f23462012a4 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Fri, 6 Jun 2025 13:09:10 +0300 Subject: [PATCH 18/22] small refactor --- src/benchmarks/nft/run.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/benchmarks/nft/run.ts b/src/benchmarks/nft/run.ts index f3c91ce9b2..0054c518f1 100644 --- a/src/benchmarks/nft/run.ts +++ b/src/benchmarks/nft/run.ts @@ -67,16 +67,16 @@ const fromInitItem = ( itemIndex: bigint, ) => { const nftData = loadFunCNFTBoc(); - const __code = Cell.fromBoc(nftData.bocItem)[0]!; + const code = Cell.fromBoc(nftData.bocItem)[0]!; - const __data = beginCell() + const data = beginCell() .storeUint(itemIndex, 64) .storeAddress(collectionAddress) .endCell(); - const __gen_init = { code: __code, data: __data }; - const address = contractAddress(0, __gen_init); - return Promise.resolve(new NFTItem(address, __gen_init)); + const init = { code, data }; + const address = contractAddress(0, init); + return Promise.resolve(new NFTItem(address, init)); }; export const run = ( From bc739c203cdd57e7837319a4df9cbf6d5035ea75 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Mon, 23 Jun 2025 14:06:12 +0300 Subject: [PATCH 19/22] review --- src/benchmarks/nft/tests/collection.ts | 242 ++++++++++------------- src/benchmarks/nft/tests/item.ts | 3 +- src/benchmarks/nft/tests/transfer-fee.ts | 4 +- src/benchmarks/nft/tests/utils.ts | 21 +- 4 files changed, 113 insertions(+), 157 deletions(-) diff --git a/src/benchmarks/nft/tests/collection.ts b/src/benchmarks/nft/tests/collection.ts index f47df118e8..e59384b7d3 100644 --- a/src/benchmarks/nft/tests/collection.ts +++ b/src/benchmarks/nft/tests/collection.ts @@ -11,10 +11,12 @@ import type { GetRoyaltyParams, BatchDeploy, RoyaltyParams, + ReportRoyaltyParams, InitNFTBody, ChangeOwner, } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; import { + storeReportRoyaltyParams, storeRoyaltyParams, type NFTCollection, } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; @@ -28,7 +30,6 @@ import { getOwner, getNextItemIndex, loadGetterTupleNFTData, - Operations, Storage, ErrorCodes, TestValues, @@ -51,8 +52,10 @@ export type FromInitCollection = ( const globalSetup = async (fromInitCollection: FromInitCollection) => { const blockchain = await Blockchain.create(); + const owner = await blockchain.treasury("owner"); const notOwner = await blockchain.treasury("notOwner"); + const defaultCommonContent = beginCell() .storeStringTail("common") .endCell(); @@ -142,72 +145,72 @@ const testEmptyMessages = (fromInitCollection: FromInitCollection) => { }); }; +const setupWithItem = async (fromInitCollection: FromInitCollection, fromInitItem: FromInitItem) => { + /** + * Helper function to deploy an NFT item + * @param itemIndex - Index of the NFT to deploy + * @param collectionNFT - Collection contract instance + * @param sender - Sender of the deployment transaction + * @param owner - Owner of the deployed NFT + * @returns Promise resolving to the NFT item contract and transaction result + */ + const deployNFT = async ( + itemIndex: bigint, + collectionNFT: SandboxContract, + sender: SandboxContract, + owner: SandboxContract, + defaultNFTContent: Cell, + blockchain: Blockchain, + ) => { + const initNFTBody: InitNFTBody = { + $$type: "InitNFTBody", + owner: owner.address, + content: defaultNFTContent, + }; + + const mintMsg: DeployNFT = { + $$type: "DeployNFT", + queryId: 1n, + itemIndex: itemIndex, + amount: Storage.NftMintAmount, + initNFTBody: beginCell() + .store(storeInitNFTBody(initNFTBody)) + .endCell(), + }; + + const itemNFT = blockchain.openContract( + await fromInitItem( + null, + null, + collectionNFT.address, + itemIndex, + ), + ); + + const trxResult = await collectionNFT.send( + sender.getSender(), + { value: Storage.DeployAmount }, + mintMsg, + ); + return {itemNFT, trxResult}; + }; + return {...await globalSetup(fromInitCollection), deployNFT}; +} + const testDeployItem = ( fromInitCollection: FromInitCollection, fromInitItem: FromInitItem, ) => { describe("NFT deploy cases", () => { - it("should deploy NFTItem correctly", async () => { - // checking in beforeEach - }); const setup = async () => { - return await globalSetup(fromInitCollection); - }; - - /** - * Helper function to deploy an NFT item - * @param itemIndex - Index of the NFT to deploy - * @param collectionNFT - Collection contract instance - * @param sender - Sender of the deployment transaction - * @param owner - Owner of the deployed NFT - * @returns Promise resolving to the NFT item contract and transaction result - */ - const deployNFT = async ( - itemIndex: bigint, - collectionNFT: SandboxContract, - sender: SandboxContract, - owner: SandboxContract, - defaultNFTContent: Cell, - blockchain: Blockchain, - ): Promise<[SandboxContract, SendMessageResult]> => { - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - const mintMsg: DeployNFT = { - $$type: "DeployNFT", - queryId: 1n, - itemIndex: itemIndex, - amount: Storage.NftMintAmount, - initNFTBody: beginCell() - .store(storeInitNFTBody(initNFTBody)) - .endCell(), - }; - - const itemNFT = blockchain.openContract( - await fromInitItem( - null, - null, - collectionNFT.address, - itemIndex, - ), - ); - - const trxResult = await collectionNFT.send( - sender.getSender(), - { value: Storage.DeployAmount }, - mintMsg, - ); - return [itemNFT, trxResult]; + return await setupWithItem(fromInitCollection, fromInitItem); }; it("should mint NFTItem correctly", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain } = + const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); - const [itemNFT, _trx] = await deployNFT( + const {itemNFT}= await deployNFT( nextItemIndex, collectionNFT, owner, @@ -240,10 +243,10 @@ const testDeployItem = ( }); it("should not mint NFTItem if not owner", async () => { - const { collectionNFT, defaultNFTContent, blockchain, notOwner } = + const { collectionNFT, defaultNFTContent, blockchain, notOwner, deployNFT } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); - const [_itemNFT, trx] = await deployNFT( + const {trxResult} = await deployNFT( nextItemIndex, collectionNFT, notOwner, @@ -254,7 +257,7 @@ const testDeployItem = ( await step( "Check that trx.transactions has correct transaction (not owner mint)", () => { - expect(trx.transactions).toHaveTransaction({ + expect(trxResult.transactions).toHaveTransaction({ from: notOwner.address, to: collectionNFT.address, success: false, @@ -265,11 +268,11 @@ const testDeployItem = ( }); it("should not deploy previous nft", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain } = + const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = await setup(); let nextItemIndex: bigint = await getNextItemIndex(collectionNFT); for (let i = 0; i < 10; i++) { - const [_itemNFT, _trx] = await deployNFT( + await deployNFT( nextItemIndex, collectionNFT, owner, @@ -279,7 +282,7 @@ const testDeployItem = ( ); nextItemIndex++; } - const [_itemNFT, trx] = await deployNFT( + const {itemNFT, trxResult} = await deployNFT( 0n, collectionNFT, owner, @@ -290,9 +293,9 @@ const testDeployItem = ( await step( "Check that trx.transactions has correct transaction (should not deploy previous nft)", () => { - expect(trx.transactions).toHaveTransaction({ + expect(trxResult.transactions).toHaveTransaction({ from: collectionNFT.address, - to: _itemNFT.address, + to: itemNFT.address, deploy: false, success: false, exitCode: ErrorCodes.InvalidData, @@ -302,10 +305,10 @@ const testDeployItem = ( }); it("shouldn't mint item itemIndex > nextItemIndex", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain } = + const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); - const [_itemNFT, trx] = await deployNFT( + const {trxResult} = await deployNFT( nextItemIndex + 1n, collectionNFT, owner, @@ -316,7 +319,7 @@ const testDeployItem = ( await step( "Check that trx.transactions has correct transaction (itemIndex > nextItemIndex)", () => { - expect(trx.transactions).toHaveTransaction({ + expect(trxResult.transactions).toHaveTransaction({ from: owner.address, to: collectionNFT.address, success: false, @@ -327,11 +330,12 @@ const testDeployItem = ( }); it("should get nft by itemIndex correctly", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain } = + const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); + // deploy new nft to get itemIndex - const [_itemNFT, _trx] = await deployNFT( + await deployNFT( nextItemIndex, collectionNFT, owner, @@ -339,14 +343,17 @@ const testDeployItem = ( defaultNFTContent, blockchain, ); + const nftAddress = await collectionNFT.getGetNftAddressByIndex(nextItemIndex); const newNFT = blockchain.getContract(nftAddress); const getData = await (await newNFT).get("get_nft_data"); const dataNFT = loadGetterTupleNFTData(getData.stack); + await step("Check that dataNFT.itemIndex is nextItemIndex", () => { expect(dataNFT.itemIndex).toBe(nextItemIndex); }); + await step( "Check that dataNFT.collectionAddress equals collectionNFT.address", () => { @@ -391,30 +398,27 @@ const testRoyalty = (fromInitCollection: FromInitCollection) => { }, ); - const exceptedMsg: Cell = beginCell() - .storeUint(Operations.ReportRoyaltyParams, 32) - .storeUint(queryId, 64) - .storeUint(royaltyParams.nominator, 16) - .storeUint(royaltyParams.dominator, 16) - .storeAddress(royaltyParams.owner) - .endCell(); + const expectedMsg: ReportRoyaltyParams = + { + $$type: "ReportRoyaltyParams", + queryId, + params: royaltyParams + } + expect(trxResult.transactions).toHaveTransaction({ from: collectionNFT.address, to: owner.address, - body: exceptedMsg, + body: beginCell().store(storeReportRoyaltyParams(expectedMsg)).endCell(), }); }); it("should get royalty params correctly", async () => { const { collectionNFT, royaltyParams } = await setup(); const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); - expect( - beginCell() - .store(storeRoyaltyParams(currRoyaltyParams)) - .asSlice(), - ).toEqualSlice( - beginCell().store(storeRoyaltyParams(royaltyParams)).asSlice(), - ); + + const currCell = beginCell().store(storeRoyaltyParams(currRoyaltyParams)).endCell(); + const expectedCell = beginCell().store(storeRoyaltyParams(royaltyParams)).endCell(); + expect(currCell).toEqualCell(expectedCell); }); }); }; @@ -445,7 +449,7 @@ const testBatchDeploy = ( count: bigint, extra: bigint = -1n, ): Promise => { - const dct = Dictionary.empty( + const dict = Dictionary.empty( Dictionary.Keys.BigUint(64), dictDeployNFTItem, ); @@ -458,15 +462,15 @@ const testBatchDeploy = ( }; while (i < count) { - dct.set(i, { + dict.set(i, { amount: Storage.NftMintAmount, initNFTBody: initNFTBody, }); - i += 1n; + i++; } - if (extra != -1n) { - dct.set(extra, { + if (extra != -1n) { // helper condition if we wanna deploy some extra nft + dict.set(extra, { amount: Storage.NftMintAmount, initNFTBody: initNFTBody, }); @@ -475,7 +479,7 @@ const testBatchDeploy = ( const batchMintNFT: BatchDeploy = { $$type: "BatchDeploy", queryId: 0n, - deployList: beginCell().storeDictDirect(dct).endCell(), + deployList: beginCell().storeDictDirect(dict).endCell(), }; return await collectionNFT.send( @@ -491,8 +495,8 @@ const testBatchDeploy = ( it.skip("test max batch mint", async () => { const { collectionNFT, owner, defaultNFTContent } = await setup(); - let L = 1n; - let R = 1000n; + let L = 1n; // left border + let R = 1000n; // right border while (R - L > 1) { const M = (L + R) / 2n; const trxResult = await batchMintNFTProcess( @@ -566,7 +570,7 @@ const testBatchDeploy = ( owner, owner, defaultNFTContent, - 260n, + 250n + 1n, ); await step( @@ -712,57 +716,16 @@ const testGetNftAddressByIndex = ( fromInitItem: FromInitItem, ) => { const setup = async () => { - return await globalSetup(fromInitCollection); + return await setupWithItem(fromInitCollection, fromInitItem); }; describe("Get nft address by index cases", () => { - const deployNFT = async ( - itemIndex: bigint, - collectionNFT: SandboxContract, - sender: SandboxContract, - owner: SandboxContract, - defaultNFTContent: Cell, - blockchain: Blockchain, - ): Promise<[SandboxContract, SendMessageResult]> => { - const initNFTBody: InitNFTBody = { - $$type: "InitNFTBody", - owner: owner.address, - content: defaultNFTContent, - }; - - const mintMsg: DeployNFT = { - $$type: "DeployNFT", - queryId: 1n, - itemIndex: itemIndex, - amount: Storage.NftMintAmount, - initNFTBody: beginCell() - .store(storeInitNFTBody(initNFTBody)) - .endCell(), - }; - - const itemNFT = blockchain.openContract( - await fromInitItem( - null, - null, - collectionNFT.address, - itemIndex, - ), - ); - - const trxResult = await collectionNFT.send( - sender.getSender(), - { value: Storage.DeployAmount }, - mintMsg, - ); - return [itemNFT, trxResult]; - }; - it("should get nft address by index correctly", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain } = + const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = await setup(); const nftAddress = await collectionNFT.getGetNftAddressByIndex(0n); - const [_itemNFT, trxDeploy] = await deployNFT( + const {trxResult} = await deployNFT( 0n, collectionNFT, owner, @@ -774,7 +737,7 @@ const testGetNftAddressByIndex = ( await step( "Check that trxDeploy.transactions has correct transaction (deploy item)", () => { - expect(trxDeploy.transactions).toHaveTransaction({ + expect(trxResult.transactions).toHaveTransaction({ from: collectionNFT.address, to: nftAddress, success: true, @@ -818,11 +781,14 @@ const testGetNftContent = (fromInitCollection: FromInitCollection) => { 0n, defaultContent, ); + + // standard detail const expectedContent = beginCell() .storeUint(1, 8) .storeSlice(defaultCommonContent.asSlice()) .storeRef(defaultContent) .endCell(); + await step( "Check that content equals expectedContent (nft content)", () => { diff --git a/src/benchmarks/nft/tests/item.ts b/src/benchmarks/nft/tests/item.ts index 90ec54af5a..6cc47f93ec 100644 --- a/src/benchmarks/nft/tests/item.ts +++ b/src/benchmarks/nft/tests/item.ts @@ -55,7 +55,8 @@ const messageGetStaticData = async ( const globalSetup = async (fromInitItem: FromInitItem) => { const blockchain = await Blockchain.create(); const config = blockchain.config; - blockchain.setConfig( + + blockchain.setConfig( // set StorageFee to 0 in blockchain setStoragePrices(config, { unixTimeSince: 0, bitPricePerSecond: 0n, diff --git a/src/benchmarks/nft/tests/transfer-fee.ts b/src/benchmarks/nft/tests/transfer-fee.ts index f26209f39c..7726fa5576 100644 --- a/src/benchmarks/nft/tests/transfer-fee.ts +++ b/src/benchmarks/nft/tests/transfer-fee.ts @@ -32,6 +32,7 @@ const globalSetup = async ( }), ); const owner = await blockchain.treasury("owner"); + const notOwner = await blockchain.treasury("notOwner"); const emptyAddress = null; const defaultContent = beginCell().endCell(); @@ -91,9 +92,10 @@ export const testTransferFee = ( return await globalSetup(fromInitItem); } - describe("Transfer ownership Fee cases", () => { + describe("Transfer ownership Fee cases", () => { // implementation detail it("should return error if forward amount is too much", async () => { const { itemNFT, owner, notOwner, emptyAddress } = await setup(); + const trxResult = await sendTransfer( itemNFT, owner.getSender(), diff --git a/src/benchmarks/nft/tests/utils.ts b/src/benchmarks/nft/tests/utils.ts index 5b57152824..7b5768a171 100644 --- a/src/benchmarks/nft/tests/utils.ts +++ b/src/benchmarks/nft/tests/utils.ts @@ -30,20 +30,6 @@ import { import "@ton/test-utils"; -/** Operation codes for NFT contract messages */ -export const Operations = { - TransferNft: 0x5fcc3d14, - OwnershipAssignment: 0x05138d91, - Excess: 0xd53276db, - GetStaticData: 0x2fcb26a2, - ReportStaticData: 0x8b771735, - GetRoyaltyParams: 0x693d3950, - ReportRoyaltyParams: 0xa8cb00ad, - EditContent: 0x1a0b9d51, - TransferEditorship: 0x1c04412a, - EditorshipAssigned: 0x511a4463, -} as const; - /** Storage and transaction related constants */ export const Storage = { /** Minimum amount of TONs required for storage */ @@ -65,7 +51,7 @@ export const Storage = { /** Forward fee for double message */ Double: 729606n, }, -} as const; +}; /** Error codes */ export const ErrorCodes = { @@ -83,7 +69,7 @@ export const ErrorCodes = { InvalidData: 65535, /** Error code for invalid destination workchain */ InvalidDestinationWorkchain: 333, -} as const; +}; /** Test related constants */ export const TestValues = { @@ -116,7 +102,7 @@ export const TestValues = { ExtraValues: { BatchMultiplier: 10n, }, -} as const; +}; /** Dictionary type for NFT deployment data */ type dictDeployNFT = { @@ -205,3 +191,4 @@ export const sendTransfer = async ( }; return await itemNFT.send(from, { value }, msg); }; + From 573c2aacb9f960f228a04e027962b942f1e10daa Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Mon, 23 Jun 2025 14:06:33 +0300 Subject: [PATCH 20/22] fmt --- src/benchmarks/nft/tests/collection.ts | 104 +++++++++++++++-------- src/benchmarks/nft/tests/item.ts | 3 +- src/benchmarks/nft/tests/transfer-fee.ts | 3 +- src/benchmarks/nft/tests/utils.ts | 1 - 4 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/benchmarks/nft/tests/collection.ts b/src/benchmarks/nft/tests/collection.ts index e59384b7d3..a1f0bfe3ed 100644 --- a/src/benchmarks/nft/tests/collection.ts +++ b/src/benchmarks/nft/tests/collection.ts @@ -145,7 +145,10 @@ const testEmptyMessages = (fromInitCollection: FromInitCollection) => { }); }; -const setupWithItem = async (fromInitCollection: FromInitCollection, fromInitItem: FromInitItem) => { +const setupWithItem = async ( + fromInitCollection: FromInitCollection, + fromInitItem: FromInitItem, +) => { /** * Helper function to deploy an NFT item * @param itemIndex - Index of the NFT to deploy @@ -179,12 +182,7 @@ const setupWithItem = async (fromInitCollection: FromInitCollection, fromInitIte }; const itemNFT = blockchain.openContract( - await fromInitItem( - null, - null, - collectionNFT.address, - itemIndex, - ), + await fromInitItem(null, null, collectionNFT.address, itemIndex), ); const trxResult = await collectionNFT.send( @@ -192,10 +190,10 @@ const setupWithItem = async (fromInitCollection: FromInitCollection, fromInitIte { value: Storage.DeployAmount }, mintMsg, ); - return {itemNFT, trxResult}; + return { itemNFT, trxResult }; }; - return {...await globalSetup(fromInitCollection), deployNFT}; -} + return { ...(await globalSetup(fromInitCollection)), deployNFT }; +}; const testDeployItem = ( fromInitCollection: FromInitCollection, @@ -207,10 +205,15 @@ const testDeployItem = ( }; it("should mint NFTItem correctly", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = - await setup(); + const { + collectionNFT, + owner, + defaultNFTContent, + blockchain, + deployNFT, + } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); - const {itemNFT}= await deployNFT( + const { itemNFT } = await deployNFT( nextItemIndex, collectionNFT, owner, @@ -243,10 +246,15 @@ const testDeployItem = ( }); it("should not mint NFTItem if not owner", async () => { - const { collectionNFT, defaultNFTContent, blockchain, notOwner, deployNFT } = - await setup(); + const { + collectionNFT, + defaultNFTContent, + blockchain, + notOwner, + deployNFT, + } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); - const {trxResult} = await deployNFT( + const { trxResult } = await deployNFT( nextItemIndex, collectionNFT, notOwner, @@ -268,8 +276,13 @@ const testDeployItem = ( }); it("should not deploy previous nft", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = - await setup(); + const { + collectionNFT, + owner, + defaultNFTContent, + blockchain, + deployNFT, + } = await setup(); let nextItemIndex: bigint = await getNextItemIndex(collectionNFT); for (let i = 0; i < 10; i++) { await deployNFT( @@ -282,7 +295,7 @@ const testDeployItem = ( ); nextItemIndex++; } - const {itemNFT, trxResult} = await deployNFT( + const { itemNFT, trxResult } = await deployNFT( 0n, collectionNFT, owner, @@ -305,10 +318,15 @@ const testDeployItem = ( }); it("shouldn't mint item itemIndex > nextItemIndex", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = - await setup(); + const { + collectionNFT, + owner, + defaultNFTContent, + blockchain, + deployNFT, + } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); - const {trxResult} = await deployNFT( + const { trxResult } = await deployNFT( nextItemIndex + 1n, collectionNFT, owner, @@ -330,8 +348,13 @@ const testDeployItem = ( }); it("should get nft by itemIndex correctly", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = - await setup(); + const { + collectionNFT, + owner, + defaultNFTContent, + blockchain, + deployNFT, + } = await setup(); const nextItemIndex = await getNextItemIndex(collectionNFT); // deploy new nft to get itemIndex @@ -398,17 +421,18 @@ const testRoyalty = (fromInitCollection: FromInitCollection) => { }, ); - const expectedMsg: ReportRoyaltyParams = - { + const expectedMsg: ReportRoyaltyParams = { $$type: "ReportRoyaltyParams", queryId, - params: royaltyParams - } + params: royaltyParams, + }; expect(trxResult.transactions).toHaveTransaction({ from: collectionNFT.address, to: owner.address, - body: beginCell().store(storeReportRoyaltyParams(expectedMsg)).endCell(), + body: beginCell() + .store(storeReportRoyaltyParams(expectedMsg)) + .endCell(), }); }); @@ -416,8 +440,12 @@ const testRoyalty = (fromInitCollection: FromInitCollection) => { const { collectionNFT, royaltyParams } = await setup(); const currRoyaltyParams = await collectionNFT.getRoyaltyParams(); - const currCell = beginCell().store(storeRoyaltyParams(currRoyaltyParams)).endCell(); - const expectedCell = beginCell().store(storeRoyaltyParams(royaltyParams)).endCell(); + const currCell = beginCell() + .store(storeRoyaltyParams(currRoyaltyParams)) + .endCell(); + const expectedCell = beginCell() + .store(storeRoyaltyParams(royaltyParams)) + .endCell(); expect(currCell).toEqualCell(expectedCell); }); }); @@ -469,7 +497,8 @@ const testBatchDeploy = ( i++; } - if (extra != -1n) { // helper condition if we wanna deploy some extra nft + if (extra != -1n) { + // helper condition if we wanna deploy some extra nft dict.set(extra, { amount: Storage.NftMintAmount, initNFTBody: initNFTBody, @@ -721,11 +750,16 @@ const testGetNftAddressByIndex = ( describe("Get nft address by index cases", () => { it("should get nft address by index correctly", async () => { - const { collectionNFT, owner, defaultNFTContent, blockchain, deployNFT } = - await setup(); + const { + collectionNFT, + owner, + defaultNFTContent, + blockchain, + deployNFT, + } = await setup(); const nftAddress = await collectionNFT.getGetNftAddressByIndex(0n); - const {trxResult} = await deployNFT( + const { trxResult } = await deployNFT( 0n, collectionNFT, owner, diff --git a/src/benchmarks/nft/tests/item.ts b/src/benchmarks/nft/tests/item.ts index 6cc47f93ec..216af62f01 100644 --- a/src/benchmarks/nft/tests/item.ts +++ b/src/benchmarks/nft/tests/item.ts @@ -56,7 +56,8 @@ const globalSetup = async (fromInitItem: FromInitItem) => { const blockchain = await Blockchain.create(); const config = blockchain.config; - blockchain.setConfig( // set StorageFee to 0 in blockchain + blockchain.setConfig( + // set StorageFee to 0 in blockchain setStoragePrices(config, { unixTimeSince: 0, bitPricePerSecond: 0n, diff --git a/src/benchmarks/nft/tests/transfer-fee.ts b/src/benchmarks/nft/tests/transfer-fee.ts index 7726fa5576..6bae62dd10 100644 --- a/src/benchmarks/nft/tests/transfer-fee.ts +++ b/src/benchmarks/nft/tests/transfer-fee.ts @@ -92,7 +92,8 @@ export const testTransferFee = ( return await globalSetup(fromInitItem); } - describe("Transfer ownership Fee cases", () => { // implementation detail + describe("Transfer ownership Fee cases", () => { + // implementation detail it("should return error if forward amount is too much", async () => { const { itemNFT, owner, notOwner, emptyAddress } = await setup(); diff --git a/src/benchmarks/nft/tests/utils.ts b/src/benchmarks/nft/tests/utils.ts index 7b5768a171..aa6c9e15b4 100644 --- a/src/benchmarks/nft/tests/utils.ts +++ b/src/benchmarks/nft/tests/utils.ts @@ -191,4 +191,3 @@ export const sendTransfer = async ( }; return await itemNFT.send(from, { value }, msg); }; - From f375be98bccfe6dd2f63cc1591d5381281049908 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Mon, 23 Jun 2025 17:44:37 +0300 Subject: [PATCH 21/22] final fix --- src/benchmarks/nft/tests/utils.ts | 49 ++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/benchmarks/nft/tests/utils.ts b/src/benchmarks/nft/tests/utils.ts index aa6c9e15b4..39504e5b8a 100644 --- a/src/benchmarks/nft/tests/utils.ts +++ b/src/benchmarks/nft/tests/utils.ts @@ -19,6 +19,15 @@ import type { NFTCollection, Transfer, } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; +import { + IncorrectDeployer, + IncorrectIndex, + IncorrectSender, + InvalidData, + InvalidDestinationWorkchain, + InvalidFees, + NotInit, +} from "@/benchmarks/nft/tact/output/collection_NFTCollection"; import { loadInitNFTBody } from "@/benchmarks/nft/tact/output/collection_NFTCollection"; // NFT Item imports @@ -56,19 +65,19 @@ export const Storage = { /** Error codes */ export const ErrorCodes = { /** Error code for not initialized contract */ - NotInit: 9, + NotInit: Number(NotInit), /** Error code for not owner */ - NotOwner: 401, + NotOwner: Number(IncorrectSender), /** Error code for invalid fees */ - InvalidFees: 402, + InvalidFees: Number(InvalidFees), /** Error code for incorrect index */ - IncorrectIndex: 402, + IncorrectIndex: Number(Number(IncorrectIndex)), /** Error code for incorrect deployer */ - IncorrectDeployer: 401, + IncorrectDeployer: Number(IncorrectDeployer), /** Error code for invalid data */ - InvalidData: 65535, + InvalidData: Number(InvalidData), /** Error code for invalid destination workchain */ - InvalidDestinationWorkchain: 333, + InvalidDestinationWorkchain: Number(InvalidDestinationWorkchain), }; /** Test related constants */ @@ -150,6 +159,11 @@ export function loadGetterTupleNFTData(source: TupleItem[]): NFTData { }; } +/** + * Retrieves the owner of the NFT collection. + * @param collection - The sandbox contract instance of the NFT collection. + * @returns The address of the collection owner. + */ export const getOwner = async ( collection: SandboxContract, ): Promise
=> { @@ -157,6 +171,11 @@ export const getOwner = async ( return res.owner; }; +/** + * Retrieves the next item index from the NFT collection. + * @param collection - The sandbox contract instance of the NFT collection. + * @returns The next item index to be minted. + */ export const getNextItemIndex = async ( collection: SandboxContract, ): Promise => { @@ -164,6 +183,11 @@ export const getNextItemIndex = async ( return res.nextItemIndex; }; +/** + * Retrieves the owner of a specific NFT item. + * @param item - The sandbox contract instance of the NFT item. + * @returns The address of the NFT item's owner. + */ export const getItemOwner = async ( item: SandboxContract, ): Promise
=> { @@ -171,6 +195,17 @@ export const getItemOwner = async ( return res.owner!; }; +/** + * Sends a transfer transaction for an NFT item. + * @param itemNFT - The sandbox contract instance of the NFT item to be transferred. + * @param from - The sender of the transaction. + * @param value - The value to be sent with the transaction. + * @param newOwner - The address of the new owner. + * @param responseDestination - The address where the response should be sent. + * @param forwardAmount - The amount of TONs to be forwarded to the new owner. + * @param forwardPayload - The payload to be forwarded to the new owner. + * @returns The result of the sent message. + */ export const sendTransfer = async ( itemNFT: SandboxContract, from: Sender, From 9e0fbca210a6d9ddc2e31f51aedab3b1b4284c20 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Mon, 30 Jun 2025 18:48:47 +0300 Subject: [PATCH 22/22] small fix --- src/benchmarks/nft/tests/transfer-fee.ts | 2 +- src/benchmarks/nft/tests/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/benchmarks/nft/tests/transfer-fee.ts b/src/benchmarks/nft/tests/transfer-fee.ts index 6bae62dd10..e6cfd452ee 100644 --- a/src/benchmarks/nft/tests/transfer-fee.ts +++ b/src/benchmarks/nft/tests/transfer-fee.ts @@ -229,7 +229,7 @@ export const testTransferForwardFeeDouble = ( const setup = async () => { return await globalSetup(fromInitItem); }; - it("should false with only one fwd fee on balance", async () => { + it("should fail with only one fwd fee on balance", async () => { const { itemNFT, owner, notOwner, balance, fwdFeeDouble } = await setup(); const trxResult = await sendTransfer( diff --git a/src/benchmarks/nft/tests/utils.ts b/src/benchmarks/nft/tests/utils.ts index 39504e5b8a..d195f0c21a 100644 --- a/src/benchmarks/nft/tests/utils.ts +++ b/src/benchmarks/nft/tests/utils.ts @@ -71,7 +71,7 @@ export const ErrorCodes = { /** Error code for invalid fees */ InvalidFees: Number(InvalidFees), /** Error code for incorrect index */ - IncorrectIndex: Number(Number(IncorrectIndex)), + IncorrectIndex: Number(IncorrectIndex), /** Error code for incorrect deployer */ IncorrectDeployer: Number(IncorrectDeployer), /** Error code for invalid data */