neo-project / neo-devpack-dotnet

NEO Development Pack
MIT License
79 stars 102 forks source link

Map.toString() throwing Exception: An unhandled exception was thrown. Key not found in Map #860

Open lock9 opened 9 months ago

lock9 commented 9 months ago

Problem: When I call the toString() method in a map, the VM will throw an exception. The problems happen in runtime and not compile time. I'm not sure if this is a compiler error or not (probably is).

Compile and run the following code:

Map<String, object> myNft = new Map<String, object>();
myNft["name"] = "My NFT";
myNft["description"] = "My NFT Description";
var tokenString = myNft.ToString(); // The problem is here

Result:

 Invoke results:
Gas Consumed: 0.0295854 GAS
Exception: An unhandled exception was thrown. Key not found in Map
Result: [  ]

However, if I return the map and print it locally, it works.

cschuchardt88 commented 9 months ago

Use primitive types. For example string not String.

Map<string, string> myNft = new();
myNft["name"] = "My NFT";
myNft["description"] = "My NFT Description";
var tokenString = myNft;
Jim8y commented 9 months ago

I did not see the same exception.... tests pass

Jim8y commented 9 months ago

Screenshot 2024-01-16 at 12 34 35

cschuchardt88 commented 9 months ago

@Jim8y Its an ApplicationEngine Error. But It's because of non-primitive types.

Jim8y commented 9 months ago

I mean, why can't I reproduce the error.

cschuchardt88 commented 9 months ago

He said

However, if I return the map and print it locally, it works.

So try blockchain?

Jim8y commented 9 months ago

I'll wait for Rick to explain it.

lock9 commented 9 months ago

Hello.

Could it be my compiler version? What version are you using? I'm using Devpack 3.6.2

This doesn't work:

var nftProperties = new Map<string, string>();
nftProperties["key1"] = "value1";
nftProperties["key2"] = "value2";

var nftString = nftProperties.ToString();

This does work:

var nftProperties = new Map<string, string>();
nftProperties["key1"] = "value1";
nftProperties["key2"] = "value2";

var nftString = StdLib.Serialize(nftProperties);
Jim8y commented 9 months ago

I am using the master of devpack tested your code in one of the test contracts, nothing happens, build and run normally.

lock9 commented 9 months ago

I was using the analyzer branch. I switched to the master branch, rebuilt it and got the same results. Is there any chance that the error is in Neo Express?

Here is the file:

using System;
using System.ComponentModel;
using System.Numerics;
using Neo;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Native;
using Neo.SmartContract.Framework.Services;

namespace NFTCollection
{
    [DisplayName("CSharpNFT")]
    [ManifestExtra("Author", "Linkd Academy")]
    [ManifestExtra("Description", "NFT Contract Example")]
    [ContractPermission("*", "onNEP11Payment")]
    public class NFTCollection : SmartContract
    {
        static readonly byte[] KeyTotalSupply = { 0x01 };

        static readonly byte[] KeyOwner = { 0x02 };

        static readonly byte[] PrefixTokenId = { 0x03 };
        static readonly byte[] PrefixToken = { 0x04 };
        static readonly byte[] PrefixAccountToken = { 0x05 };

        static readonly byte[] PrefixBalance = { 0x06 };

        public delegate void OnTransferDelegate(UInt160 from, UInt160 to, BigInteger amount, ByteString tokenId);

        [DisplayName("Transfer")]
        public static event OnTransferDelegate OnTransfer;

        [Safe]
        public static string Symbol()
        {
            return "NFTS";
        }

        [Safe]
        public static int Decimals()
        {
            return 0;
        }

        [Safe]
        public static BigInteger TotalSupply()
        {
            return (BigInteger)Storage.Get(KeyTotalSupply);
        }

        [Safe]
        public static UInt160 OwnerOf(string tokenId)
        {
            return (UInt160)Storage.Get(PrefixToken.Concat(tokenId));
        }

        public class MyNFT
        {
            public string name;
            public string description;
            public string tokenId;
            public Map<string, string> properties = new Map<string, string>();
        }

        public static MyNFT CreateNFT()
        {
            var nftName = "My NFT";
            var nftDescription = "My NFT Description";
            var nftProperties = new Map<string, string>();
            nftProperties["key1"] = "value1";
            nftProperties["key2"] = "value2";

            var nftString = nftProperties.ToString();
            // var nftString = StdLib.Serialize(nftProperties);
            // Runtime.Notify("Debug", new object[] { nftString });

            // Generate Token ID
            var serializedProperties = StdLib.Serialize(nftProperties);
            var transactionHash = (Transaction)Runtime.ScriptContainer;
            var tokenHashInput = serializedProperties.Concat(transactionHash.Hash);
            var tokenId = CryptoLib.Ripemd160(tokenHashInput);

            var myNft = new MyNFT()
            {
                name = nftName,
                description = nftDescription,
                properties = nftProperties,
                tokenId = tokenId
            };

            return myNft;
        }

        public static object TestNFT()
        {
            var transaction = (Transaction)Runtime.ScriptContainer;
            var sender = transaction.Sender;

            var nft = CreateNFT();
            var serializedNFT = StdLib.Serialize(nft);

            var tokenDetailsKey = PrefixToken.Concat(nft.tokenId);
            //Persist the NFT
            Storage.Put(tokenDetailsKey, serializedNFT);

            //Persist the owner of the NFT
            Storage.Put(PrefixTokenId.Concat(nft.tokenId), sender);

            var existingTokens = Storage.Get(PrefixAccountToken.Concat(sender));

            List<ByteString> deserializedTokens;

            if (existingTokens is null)
            {
                deserializedTokens = new ByteString[] { };
            }
            else
            {
                deserializedTokens = (ByteString[])StdLib.Deserialize(existingTokens);
            }

            //Persist the NFT in the account
            deserializedTokens.Add(nft.tokenId);
            Storage.Put(PrefixAccountToken.Concat(sender), StdLib.Serialize(deserializedTokens));

            // Update the balance of the sender
            var balance = (BigInteger)Storage.Get(PrefixBalance.Concat(sender));
            var newBalance = balance + 1;
            Storage.Put(PrefixBalance.Concat(sender), newBalance);

            //Increase the total supply
            var totalSupply = (BigInteger)Storage.Get(KeyTotalSupply);
            var newTotalSupply = totalSupply + 1;
            Storage.Put(KeyTotalSupply, newTotalSupply);

            return nft;
        }

        public static object Properties(string tokenId)
        {
            var rawNFT = Storage.Get(PrefixToken.Concat(tokenId));
            if (rawNFT is null)
            {
                throw new Exception("Token does not exist");
            }

            var nft = (MyNFT)StdLib.Deserialize(rawNFT);
            return nft.properties;
        }

        public static Iterator TokensOf(UInt160 account)
        {
            if (account is null || !account.IsValid)
                throw new Exception("Invalid Parameter.");

            var accountKey = PrefixAccountToken.Concat(account);
            var tokenIds = (Iterator)StdLib.Deserialize(Storage.Get(accountKey));
            return tokenIds;
        }

        [DisplayName("_deploy")]
        public static void Deploy(object data, bool update)
        {
            if (update) return;
            Storage.Put(KeyTotalSupply, 0);

            var tx = (Transaction)Runtime.ScriptContainer;
            Storage.Put(KeyOwner, tx.Sender);
        }

        [Safe]
        public static BigInteger BalanceOf(UInt160 account)
        {
            if (account is null || !account.IsValid)
                throw new Exception("Invalid Parameter.");

            BigInteger balance = (BigInteger)Storage.Get(PrefixBalance.Concat(account));
            return balance;
        }

        public static bool Transfer(UInt160 from, UInt160 to, String tokenId, object data)
        {
            if (from is null || !from.IsValid)
                throw new Exception("Invalid from address.");
            if (to is null || !to.IsValid)
                throw new Exception("Invalid to address.");
            if (!Runtime.CheckWitness(from))
                throw new Exception("Invalid Signature");

            var tokenOwner = (UInt160)Storage.Get(PrefixTokenId.Concat(tokenId));

            if (tokenOwner is null)
                throw new Exception("Token does not exist");

            if (tokenOwner != from)
                throw new Exception("Not Authorized");

            if (from == to)
                return true;

            Storage.Put(PrefixTokenId.Concat(tokenId), to);
            Storage.Put(PrefixBalance.Concat(from), BalanceOf(from) - 1);
            Storage.Put(PrefixBalance.Concat(to), BalanceOf(to) + 1);

            if (ContractManagement.GetContract(to) is not null)
            {
                Contract.Call(to, "onNEP11Payment", CallFlags.All, from, 1, tokenId, data);
            }

            OnTransfer(from, to, 1, tokenId);

            return true;
        }

        public static void Update(ByteString nefFile, string manifest, object data)
        {
            if (Runtime.CheckWitness((UInt160)Storage.Get(KeyOwner)))
                ContractManagement.Update(nefFile, manifest, data);
        }
    }
}
cschuchardt88 commented 9 months ago

It works fine. Remove the .ToString. This was on TestNet T5 image

public static void Test()
{
    Map<string, string> myNft = new();
    myNft["name"] = "My NFT";
    myNft["description"] = "My NFT Description";
    var tokenString = myNft;
}
cschuchardt88 commented 9 months ago

This line is wrong var nftString = nftProperties.ToString(); You can't do that.

lock9 commented 9 months ago

It seems that there is a general issue with ToString(). The following code is also causing an error:

public static string TestString()
{
      return "Jimmy".ToString();
}
Invoke results:
Gas Consumed: 0.0246507 GAS
Exception: An unhandled exception was thrown. The value 2 is out of range.
Result: [  ]
cschuchardt88 commented 9 months ago

It seems that there is a general issue with ToString(). The following code is also causing an error:

public static string TestString()
{
      return "Jimmy".ToString();
}
Invoke results:
Gas Consumed: 0.0246507 GAS
Exception: An unhandled exception was thrown. The value 2 is out of range.
Result: [  ]

What don't you understand?

YOU CAN'T DO THAT!!!!!!!!!!!!

YOU CAST!!!!!!!!!!!

Jim8y commented 9 months ago

@cschuchardt88 I think what @lock9 want to say is it is normal C# operation, neo should allow it work that way. Not about how to make it work, but how it should work.

cschuchardt88 commented 9 months ago

Well than say that. After 20 times of telling him why he can't do something. He keeps saying I can't do this.

  1. It shows he doesn't read everything.
  2. It wastes our time.
lock9 commented 9 months ago

Well than say that. After 20 times of telling him why he can't do something. He keeps saying I can't do this.

  1. It shows he doesn't read everything.
  2. It wastes our time.
Screenshot 2024-01-17 at 08 42 03
lock9 commented 9 months ago

Hi @Jim8y , I've looked into the UT that you used. It's not checking the VM State.