Origin-Byte / nft-protocol

Sui NFT Smart contracts maintained by the Origin Byte team
Other
147 stars 67 forks source link

Cannot mint NFT due to ExecutionError (InvalidChildObjectArgument) #294

Open cosmicpoet opened 1 year ago

cosmicpoet commented 1 year ago

I am not really sure if it's better to post this issue in the js-sdk repo or this one. But here we go.

What I have done:

  1. I generated a NFT smart contract using Gutenberg and then modified it a bit.
  2. Reading this doc, I assumed this was still true so I put the contract under /sources directory, along with the existing nft-protocol modules.
  3. I did change the addresses field in Move.toml from the existing one you had in your repo into 0x0, or otherwise I was NOT able to deploy it.
  4. I was able to deploy it to the devnet doing above. Here is the transaction log (along with the object types I noted if you scroll to the right) to help with the investigation.
----- Transaction Effects ----
Status : Success
Created Objects:

  - ID: 0x0af31b3e465fa6aa9154ba07598fcb0e83373018 , Owner: Object ID: ( 0xf34412a0779ecccd4ab94e1cf9f7cf18eba3fd4a )           fixed_price::FixedPriceMarket | Price: 50 | WL
  - ID: 0x123d032bc405afcd4c07baff9cc7f1e659537d4a , Owner: Object ID: ( 0xa549433fd8c3bdcd1e8f9cffd0122e8f1f31de16 )           royalty::RoyaltyDomain
  - ID: 0x2c0681925d23ca1cb78321181fcb85fc102fee48 , Owner: Object ID: ( 0x6d1ad44fc7fd1d1e3e5b7195ee768c0314ccb230 )           tags::ProfilePicture
  - ID: 0x2e884cf6c2970356578b49351dc4e424dd777c0b , Owner: Object ID: ( 0x776d50ef7e581dfe996d271ab9243b4baf5e992a )           royalty_strategy_bps::BpsRoyaltyStrategy
  - ID: 0x3d61ee56dadda356dbd68ca97028ec166eafd281 , Owner: Object ID: ( 0xc18a8186c7a177dc5359a29135e7c5fdd957d139 )               dynamic_field::Field<0x2::dynamic_object_field::Wrapper<0x2::object::ID>, 0x2::object::ID>
  - ID: 0x3eb3968cd02a9ff3cd6225465de9fa23ad382bd1 , Owner: Object ID: ( 0x474be3d63ecd613cc42515671ced2d75ae462042 )           warehouse::Warehouse
  - ID: 0x474be3d63ecd613cc42515671ced2d75ae462042 , Owner: Object ID: ( 0x63b74833f4f2bf3c951324c098dc948666cb542b )           inventory::Inventory
  - ID: 0x5bf2ee1d9130ba8e192ca56c762c2b92a35c1a72 , Owner: Object ID: ( 0xa549433fd8c3bdcd1e8f9cffd0122e8f1f31de16 )           display::UrlDomain
  - ID: 0x63b74833f4f2bf3c951324c098dc948666cb542b , Owner: Object ID: ( 0xe5dc24013de505f2bfe6cbbe2d6ac7cc1499b187 )               dynamic_field::Field<0x2::dynamic_object_field::Wrapper<0x2::object::ID>, 0x2::object::ID>
  - ID: 0x6c590ef2d18964ea6144cd6ca4fce14265375aeb , Owner: Object ID: ( 0x7a782dd7dc4fdc6d7959a7a4c733b3d93d3ac593 )           venue::Venue | Price: 100 | Non-WL
  - ID: 0x7087e3e2377203d9dcd513ce688869c13c932021 , Owner: Object ID: ( 0xa549433fd8c3bdcd1e8f9cffd0122e8f1f31de16 )           creators::CreatorsDomain
  - ID: 0x7a782dd7dc4fdc6d7959a7a4c733b3d93d3ac593 , Owner: Object ID: ( 0xc18a8186c7a177dc5359a29135e7c5fdd957d139 )               dynamic_field::Field<0x2::dynamic_object_field::Wrapper<0x2::object::ID>, 0x2::object::ID>
  - ID: 0x961b5d0fff47027335dbc348f8fde285a529f21c , Owner: Object ID: ( 0xa549433fd8c3bdcd1e8f9cffd0122e8f1f31de16 )           tags::TagDomain
  - ID: 0xa25c6ca9d3edab04b467e0c8f565908ce1f46dbb , Owner: Object ID: ( 0x694df98ff755b0b8c3c5d7042bb7666d50ad9b65 )               dynamic_field::Field<0x2::dynamic_object_field::Wrapper<0x1::type_name::TypeName>, 0x2::object::ID>
  - ID: 0xa549433fd8c3bdcd1e8f9cffd0122e8f1f31de16 , Owner: Shared                                          collection::Collection
  - ID: 0xadc450540b8ae8b643d615e5c390696663bfd89b , Owner: Shared                                          marketplace::Marketplace
  - ID: 0xb13cfd2f6e363f13562479532af98cca97a2d387 , Owner: Object ID: ( 0xa549433fd8c3bdcd1e8f9cffd0122e8f1f31de16 )           display::DisplayDomain
  - ID: 0xbda23b210924e00b2484f5fbe4401357ec9807bc , Owner: Object ID: ( 0x6c590ef2d18964ea6144cd6ca4fce14265375aeb )           fixed_price::FixedPriceMarket | Price: 100 | Non-WL
  - ID: 0xc0576d426c37d0996cc8e837243acf3429d938fb , Owner: Shared                                          listing::Listing
  - ID: 0xc468803dd47b518c1e90632bb2d7a7aa34e8e664 , Owner: Account Address ( 0xe42d4c5e5fa231009f5d2342461ab877599e5b70 )      mint_cap::MintCap
  - ID: 0xe289fc2252ae6a959a3a3adc08070eda7ea10a69 , Owner: Object ID: ( 0xa25c6ca9d3edab04b467e0c8f565908ce1f46dbb )           flat_fee::FlatFee
  - ID: 0xec64f96d1d707179ed759f118f0309117d294e39 , Owner: Object ID: ( 0xa549433fd8c3bdcd1e8f9cffd0122e8f1f31de16 )           display::SymbolDomain
  - ID: 0xf34412a0779ecccd4ab94e1cf9f7cf18eba3fd4a , Owner: Object ID: ( 0x3d61ee56dadda356dbd68ca97028ec166eafd281 )           venue::Venue | Price: 50 | WL
  - ID: 0xf370d5bec6fc0a781072d3485b73d23689f710e7 , Owner: Object ID: ( 0x6d1ad44fc7fd1d1e3e5b7195ee768c0314ccb230 )           tags::Art
  - ID: 0xf8be87a3546a1558ae81ab3ac7e37615433b8c55 , Owner: Immutable                                           Package
  1. I followed the examples on js-sdk repo. Since the Gutenberg-generated contract already defined marketplace, lising, etc. (hence the lengthy log above), I skipped step 1-4 and jumped to step 5 to mint the nfts.
export const mintNFt = async () => {
  const txs: MoveCallTransaction[] = [];
  for (let i = 1; i <= 10; i += 1) {
    txs.push(
      NftClient.biuldMintNft({ // 'biuld' seems to be a typo!
        name: `Test Planet Logo ${i}`,
        description: "This is a test",
        mintCap: MINT_CAP_ID,
        packageObjectId: PACKAGE_OBJECT_ID,
        warehouseId: WAREHOUSE_ID,
        moduleName: "slimezdevnettest",
        url: "ipfs://QmaH6pizJ6qHyjmAEneC1BVerVaawViiW8aJBbbyW7TUPs",
        attributes: {
          Image: "Planet",
          Text: "Red",
        },
      })
    );
  }
  const chunks = splitBy(
    txs.sort((a, b) => 0.5 - Math.random()),
    100
  );
  await Promise.all(chunks.map((chunk) => mintChunk(chunk)));
  //   const createMarketResult = await signer.executeMoveCall(transaction);
  // console.log('createMarketResult', JSON.stringify(createMarketResult));
};

mintNFt();
  1. I encountered the following error:
/home/romeotango/sui-move/originbyte/js-sdk/node_modules/@mysten/sui.js/src/signers/txn-data-serializers/rpc-txn-data-serializer.ts:182
      throw new Error(
            ^
Error: Encountered error when calling RpcTxnDataSerialize for a moveCall transaction for address e42d4c5e5fa231009f5d2342461ab877599e5b70 for transaction {
  "kind": "moveCall",
  "data": {
    "packageObjectId": "0xf8be87a3546a1558ae81ab3ac7e37615433b8c55",
    "module": "slimezdevnettest",
    "function": "mint_nft",
    "typeArguments": [],
    "arguments": [
      "Test Planet Logo 72",
      "This is a test",
      "ipfs://QmaH6pizJ6qHyjmAEneC1BVerVaawViiW8aJBbbyW7TUPs",
      [
        "Image",
        "Text"
      ],
      [
        "Planet",
        "Red"
      ],
      "0xc468803dd47b518c1e90632bb2d7a7aa34e8e664",
      "0x3eb3968cd02a9ff3cd6225465de9fa23ad382bd1"
    ],
    "gasBudget": 10000
  }
}: Error: RPC Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: InvalidChildObjectArgument(InvalidChildObjectArgument { child: 0x3eb3968cd02a9ff3cd6225465de9fa23ad382bd1, parent: 0x474be3d63ecd613cc42515671ced2d75ae462042 }), source: None } }
    at RpcTxnDataSerializer.<anonymous> (/home/romeotango/sui-move/originbyte/js-sdk/node_modules/@mysten/sui.js/src/signers/txn-data-serializers/rpc-txn-data-serializer.ts:182:13)
    at Generator.throw (<anonymous>)
    at rejected (/home/romeotango/sui-move/originbyte/js-sdk/node_modules/@mysten/sui.js/dist/index.js:73:29)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

It seems that this has to do with the inventory and warehouse objects. In general, I found the relationships between the two very confusing and there isn't enough documentation explaining them. But from the transaction log above and Sui Explorer, it seems that my inventory object 0x474be3d63ecd613cc42515671ced2d75ae462042 owns a dynamic field that is the warehouse object 0x3eb3968cd02a9ff3cd6225465de9fa23ad382bd1. My guess is that there is some incompatibility between what's generated from Gutenberg and js-sdk regarding these two objects.

Understanding this is a very lengthy issue post already and I tried not to overload information further. If necessary I can provide more info or push my repos to help with replication and investigation. Really have 0 clue on how to even debug this issue so I appreciate your guidance.

Suficio commented 1 year ago

Thank you for the detailed issue!

Inventory is basically a type-erased wrapper around Warehouse or Factory. Both Warehouse and Factory are objects from which NFTs can be withdrawn thus anyone interacting with an Inventory does not necessarily need to know what object theyre withdrawing from.

This property is useful for creating simple market interfaces as you can create venues that sell pre-minted NFTs or mint them on the fly.

Our developer docs might be able to provide you with some extra insight in the future https://origin-byte.github.io/inventory.html#Inventory

Going back to your issue, based on the transaction effects after publishing your contract I understand that your mint_nft endpoint is expecing a &mut Warehouse which you are correctly providing and its erroring out.

The reason this is erroring out is that you cannot make a direct mutable reference to a child object of another object. Since the Warehouse has been inserted into Inventory it is now a child object and Sui transaction rules will not allow you to reference to it directly without going through the Inventory.

You can modify your endpoint to take &mut Inventory instead and call inventory::deposit_nft but I suspect that this also wont work because you have also inserted the Inventory into the Listing which makes it a child object (same problem).

To resolve this you can modify mint_nft to call https://origin-byte.github.io/listing.html#add_nft which you will call with the inventory_id 0x474be3d63ecd613cc42515671ced2d75ae462042. Let me know whether that works!

To give you a bit more insight about why the mint_nft function is generated with &mut Warehouse in the first place. Calling listing::add_nft on Listing which is a shared object will force the transaction to go through shared consensus. Mass depositing your NFTs into Warehouse while it is still owned by your address allows you to avoid that. (Though there is a blocking issue for this functionality https://github.com/MystenLabs/sui/issues/8689)

cosmicpoet commented 1 year ago

Thank you @Suficio for the detailed explanation!

I redeployed my package using the updated mint_nft function as you suggested calling listing::add_nft, and it worked! I had to make my own MoveCallTransaction instead of relying on the NftClient.biuldMintNft() (a glaring typo here...) in your js-sdk but that's alright for testing purposes for now.

To give you a bit more insight about why the mint_nft function is generated with &mut Warehouse in the first place. Calling listing::add_nft on Listing which is a shared object will force the transaction to go through shared consensus. Mass depositing your NFTs into Warehouse while it is still owned by your address allows you to avoid that. (Though there is a blocking issue for this functionality https://github.com/MystenLabs/sui/issues/8689)

Based on your description, Gutenberg generates mint_nft function that way by design, but then it just violate Sui transaction rules? Are we expecting changes to Gutenberg generated contracts (which is normal I guess for anything in Sui at this stage)?

Lastly, is this document about deployment still accurate?

Suficio commented 1 year ago

Based on your description, Gutenberg generates mint_nft function that way by design, but then it just violate Sui transaction rules?

In your case you are minting NFTs after attaching the Warehouse to the Listing, whereas for many workflows we mint before attaching the Warehouse in which case minting to Warehouse is the correct approach. We will make sure that a function that allows minting into Listing is generated in the future for cases like yours.

I'll pass the info regarding the typo along, it is glaring...

Regarding deployment the info is accurate, however you can chose to reference the currently published contract of the protocol instead of publishing it yourself each time.

Thanks for the feedback!

cosmicpoet commented 1 year ago

Thanks again for your guidance.

Feel free to close it but just leaving some comments for others who are confused like me. Currently (0.26.0 / SUI 0.27.1) the best way for end-to-end deployment is to follow one of the example contracts in this repo (e.g. suimarines.move). After deploying the contract, head over the js-sdk and use the 0-9 scripts to initialize the warehouse, listing, venue etc.

Gutenberg-generated contracts don't seem to be updated to the current configuration and would probably need a lot more efforts to make them work.