From 4cd572f87837f2011eb880d1222f6ee646cd9afb Mon Sep 17 00:00:00 2001 From: "Lazar Prijovic @madrazzl3" Date: Mon, 6 Mar 2023 13:37:32 +0400 Subject: [PATCH 1/7] Implement NFT Tickets contract --- .gitignore | 1 + .../AddressExtensions.cs | 42 + .../NonFungibleToken.Tests/InMemoryState.cs | 85 ++ .../NonFungibleTokenContract.Tests.csproj | 21 + .../NonFungibleTokenTests.cs | 1331 +++++++++++++++++ .../NonFungibleToken.Tests/TransferResult.cs | 16 + .../NonFungibleToken/INonFungibleToken.cs | 99 ++ .../INonFungibleTokenMetadata.cs | 12 + .../INonFungibleTokenReceiver.cs | 22 + .../NonFungibleToken/ISupportsInterface.cs | 15 + .../NonFungibleToken/ITicketContract.cs | 10 + .../NonFungibleToken/NonFungibleToken.cs | 638 ++++++++ .../NonFungibleTokenContract.csproj | 12 + .../NonFungibleTokenContract.sln | 31 + Testnet/NonFungibleToken-Ticket/README.MD | 15 + 15 files changed, 2350 insertions(+) create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/AddressExtensions.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/InMemoryState.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenContract.Tests.csproj create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenTests.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/TransferResult.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleToken.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenMetadata.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenReceiver.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken/ISupportsInterface.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken/ITicketContract.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleTokenContract.csproj create mode 100644 Testnet/NonFungibleToken-Ticket/NonFungibleTokenContract.sln create mode 100644 Testnet/NonFungibleToken-Ticket/README.MD diff --git a/.gitignore b/.gitignore index 731df3ce..db0e7f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -260,3 +260,4 @@ paket-files/ __pycache__/ *.pyc Testnet/AddressMapper/readme.ps1 +.history \ No newline at end of file diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/AddressExtensions.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/AddressExtensions.cs new file mode 100644 index 00000000..8879db22 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/AddressExtensions.cs @@ -0,0 +1,42 @@ +using Stratis.SmartContracts; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NonFungibleTokenContract.Tests +{ + public static class AddressExtensions + { + private static byte[] HexStringToBytes(string val) + { + if (val.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + val = val.Substring(2); + + byte[] ret = new byte[val.Length / 2]; + for (int i = 0; i < val.Length; i = i + 2) + { + string hexChars = val.Substring(i, 2); + ret[i / 2] = byte.Parse(hexChars, System.Globalization.NumberStyles.HexNumber); + } + return ret; + } + + public static Address HexToAddress(this string hexString) + { + // uint160 only parses a big-endian hex string + var result = HexStringToBytes(hexString); + return CreateAddress(result); + } + + private static Address CreateAddress(byte[] bytes) + { + uint pn0 = BitConverter.ToUInt32(bytes, 0); + uint pn1 = BitConverter.ToUInt32(bytes, 4); + uint pn2 = BitConverter.ToUInt32(bytes, 8); + uint pn3 = BitConverter.ToUInt32(bytes, 12); + uint pn4 = BitConverter.ToUInt32(bytes, 16); + + return new Address(pn0, pn1, pn2, pn3, pn4); + } + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/InMemoryState.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/InMemoryState.cs new file mode 100644 index 00000000..f619b563 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/InMemoryState.cs @@ -0,0 +1,85 @@ +using Stratis.SmartContracts; +using System; +using System.Collections.Generic; + +namespace DividendTokenContract.Tests +{ + public class InMemoryState : IPersistentState + { + private readonly Dictionary storage = new Dictionary(); + public bool IsContractResult { get; set; } + public void Clear(string key) => storage.Remove(key); + + public bool ContainsKey(string key) => storage.ContainsKey(key); + + public T GetValue(string key) => (T)storage.GetValueOrDefault(key, default(T)); + + public void AddOrReplace(string key, object value) + { + if (!storage.TryAdd(key, value)) + storage[key] = value; + } + public Address GetAddress(string key) => GetValue
(key); + + public T[] GetArray(string key) => GetValue(key); + + public bool GetBool(string key) => GetValue(key); + + public byte[] GetBytes(byte[] key) => throw new NotImplementedException(); + + public byte[] GetBytes(string key) => GetValue(key); + + public char GetChar(string key) => GetValue(key); + + public int GetInt32(string key) => GetValue(key); + + public long GetInt64(string key) => GetValue(key); + + public string GetString(string key) => GetValue(key); + + public T GetStruct(string key) + where T : struct => GetValue(key); + + public uint GetUInt32(string key) => GetValue(key); + + public ulong GetUInt64(string key) => GetValue(key); + + public UInt128 GetUInt128(string key) => GetValue(key); + + public UInt256 GetUInt256(string key) => GetValue(key); + + public bool IsContract(Address address) => IsContractResult; + + public void SetAddress(string key, Address value) => AddOrReplace(key, value); + + public void SetArray(string key, Array a) => AddOrReplace(key, a); + + public void SetBool(string key, bool value) => AddOrReplace(key, value); + + public void SetBytes(byte[] key, byte[] value) + { + throw new NotImplementedException(); + } + + public void SetBytes(string key, byte[] value) => AddOrReplace(key, value); + + public void SetChar(string key, char value) => AddOrReplace(key, value); + + public void SetInt32(string key, int value) => AddOrReplace(key, value); + + public void SetInt64(string key, long value) => AddOrReplace(key, value); + + public void SetString(string key, string value) => AddOrReplace(key, value); + + public void SetStruct(string key, T value) + where T : struct => AddOrReplace(key, value); + + public void SetUInt32(string key, uint value) => AddOrReplace(key, value); + + public void SetUInt64(string key, ulong value) => AddOrReplace(key, value); + + public void SetUInt128(string key, UInt128 value) => AddOrReplace(key, value); + + public void SetUInt256(string key, UInt256 value) => AddOrReplace(key, value); + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenContract.Tests.csproj b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenContract.Tests.csproj new file mode 100644 index 00000000..df500981 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenContract.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenTests.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenTests.cs new file mode 100644 index 00000000..7a28850b --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/NonFungibleTokenTests.cs @@ -0,0 +1,1331 @@ +using DividendTokenContract.Tests; +using FluentAssertions; +using Moq; +using Moq.Language.Flow; +using NonFungibleTokenContract.Tests; +using Stratis.SmartContracts; +using System; +using System.Linq; +using Xunit; +using static NonFungibleToken; + +public class NonFungibleTokenTests +{ + private Mock smartContractStateMock; + private Mock contractLoggerMock; + private InMemoryState state; + private Mock transactionExecutorMock; + private Address contractAddress; + private string name; + private string symbol; + private bool ownerOnlyMinting; + + public NonFungibleTokenTests() + { + this.contractLoggerMock = new Mock(); + this.smartContractStateMock = new Mock(); + this.transactionExecutorMock = new Mock(); + this.state = new InMemoryState(); + this.smartContractStateMock.Setup(s => s.PersistentState).Returns(this.state); + this.smartContractStateMock.Setup(s => s.ContractLogger).Returns(this.contractLoggerMock.Object); + this.smartContractStateMock.Setup(x => x.InternalTransactionExecutor).Returns(this.transactionExecutorMock.Object); + this.contractAddress = "0x0000000000000000000000000000000000000001".HexToAddress(); + this.name = "Tickets Token"; + this.symbol = "TCKT"; + this.ownerOnlyMinting = true; + } + + public string GetTokenURI(UInt256 tokenId) => $"https://example.com/api/tokens/{tokenId}"; + + [Fact] + public void Constructor_Sets_Values() + { + var owner = "0x0000000000000000000000000000000000000005".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(owner); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.True(state.GetBool("SupportedInterface:1")); + Assert.True(state.GetBool("SupportedInterface:2")); + Assert.False(state.GetBool("SupportedInterface:3")); + Assert.True(state.GetBool("SupportedInterface:4")); + Assert.False(state.GetBool("SupportedInterface:5")); + Assert.True(state.GetBool("SupportedInterface:100")); + Assert.Equal(name, nonFungibleToken.Name); + Assert.Equal(symbol, nonFungibleToken.Symbol); + Assert.Equal(owner, nonFungibleToken.Owner); + Assert.Equal(ownerOnlyMinting, state.GetBool("OwnerOnlyMinting")); + } + + [Fact] + public void IsUsed_ReturnsFalseForNonExistingToken() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.False(nonFungibleToken.IsUsed(1)); + } + + [Fact] + public void IsUsed_ReturnsFalseForNonMarkedToken() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.False(nonFungibleToken.IsUsed(1)); + } + + [Fact] + public void MarkAsUsed_ChangesState() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.False(nonFungibleToken.IsUsed(1)); + + nonFungibleToken.MarkAsUsed(1); + + Assert.True(nonFungibleToken.IsUsed(1)); + } + + [Fact] + public void MarkAsUsed_IsSafeToRunMultipleTimes() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.False(nonFungibleToken.IsUsed(1)); + + for (int i = 0; i < 3; i++) + { + nonFungibleToken.MarkAsUsed(1); + Assert.True(nonFungibleToken.IsUsed(1)); + } + } + + [Fact] + public void TransferFrom_Success_ForNotUsedTicket() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.False(nonFungibleToken.IsUsed(1)); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + Assert.False(nonFungibleToken.IsUsed(1)); + } + + [Fact] + public void TransferFrom_TicketStaysUnusedAfterTransfer() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.False(nonFungibleToken.IsUsed(1)); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.False(nonFungibleToken.IsUsed(1)); + } + + [Fact] + public void TransferFrom_ForUsedTicket_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.MarkAsUsed(1); + + nonFungibleToken.Invoking(c => c.TransferFrom(ownerAddress, targetAddress, 1)) + .Should() + .ThrowExactly() + .WithMessage("Ticket was already used."); + } + + [Fact] + public void SetPendingOwner_Success() + { + var owner = "0x0000000000000000000000000000000000000002".HexToAddress(); + var newOwner = "0x0000000000000000000000000000000000000003".HexToAddress(); + + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, owner, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SetPendingOwner(newOwner); + + nonFungibleToken.Owner + .Should() + .Be(owner); + + nonFungibleToken.PendingOwner + .Should() + .Be(newOwner); + + var log = new OwnershipTransferRequestedLog { CurrentOwner = owner, PendingOwner = newOwner }; + contractLoggerMock.Verify(l => l.Log(It.IsAny(), log)); + } + + [Fact] + public void SetPendingOwner_Called_By_NonOwner_Fails() + { + var owner = "0x0000000000000000000000000000000000000002".HexToAddress(); + var newOwner = "0x0000000000000000000000000000000000000003".HexToAddress(); + + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, owner, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, newOwner, 0)); + + nonFungibleToken.Invoking(c => c.SetPendingOwner(newOwner)) + .Should() + .ThrowExactly() + .WithMessage("The method is owner only."); + } + + [Fact] + public void ClaimOwnership_Not_Called_By_NewOwner_Fails() + { + var owner = "0x0000000000000000000000000000000000000002".HexToAddress(); + var newOwner = "0x0000000000000000000000000000000000000003".HexToAddress(); + + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, owner, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SetPendingOwner(newOwner); + + nonFungibleToken.Invoking(c => c.ClaimOwnership()) + .Should() + .ThrowExactly() + .WithMessage("ClaimOwnership must be called by the new(pending) owner."); + } + + [Fact] + public void ClaimOwnership_Success() + { + var owner = "0x0000000000000000000000000000000000000002".HexToAddress(); + var newOwner = "0x0000000000000000000000000000000000000003".HexToAddress(); + + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, owner, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SetPendingOwner(newOwner); + + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, newOwner, 0)); + + nonFungibleToken.ClaimOwnership(); + + nonFungibleToken.Owner + .Should() + .Be(newOwner); + + nonFungibleToken.PendingOwner + .Should() + .Be(Address.Zero); + + var log = new OwnershipTransferedLog { PreviousOwner = owner, NewOwner = newOwner }; + contractLoggerMock.Verify(l => l.Log(It.IsAny(), log)); + } + + [Fact] + public void SupportsInterface_InterfaceSupported_ReturnsTrue() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.SupportsInterface(2); + + Assert.True(result); + } + + [Fact] + public void SupportsInterface_InterfaceSetToFalseSupported_ReturnsFalse() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + state.SetBool("SupportedInterface:2", false); + + var result = nonFungibleToken.SupportsInterface(3); + + Assert.False(result); + } + + [Fact] + public void SupportsInterface_InterfaceNotSupported_ReturnsFalse() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.SupportsInterface(6); + + Assert.False(result); + } + + + [Fact] + public void GetApproved_NotValidNFToken_OwnerAddressZero_ThrowsException() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.GetApproved(1)); + } + + [Fact] + public void GetApproved_ApprovalNotInStorage_ReturnsZeroAddress() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + state.SetAddress("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); + + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.GetApproved(1); + + Assert.Equal(Address.Zero, result); + } + + [Fact] + public void GetApproved_ApprovalInStorage_ReturnsAddress() + { + + var approvalAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); + state.SetAddress("IdToApproval:1", approvalAddress); + + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + var result = nonFungibleToken.GetApproved(1); + + Assert.Equal(approvalAddress, result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorInStateAsTrue_ReturnsTrue() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", true); + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.True(result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorInStateAsFalse_ReturnsFalse() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", false); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.False(result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorNotInState_ReturnsFalse() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.False(result); + } + + [Fact] + public void OwnerOf_IdToOwnerNotInStorage_ThrowsException() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.OwnerOf(1)); + } + + [Fact] + public void OwnerOf_NFTokenMappedToAddressZero_ThrowsException() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + state.SetAddress("IdToOwner:1", Address.Zero); + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.OwnerOf(1)); + } + + [Fact] + public void OwnerOf_NFTokenExistsWithOwner_ReturnsOwnerAddress() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.OwnerOf(1); + + Assert.Equal(ownerAddress, result); + } + + [Fact] + public void BalanceOf_OwnerZero_ThrowsException() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => { nonFungibleToken.BalanceOf(Address.Zero); }); + } + + [Fact] + public void BalanceOf_NftTokenCountNotInStorage_ReturnsZero() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.BalanceOf(ownerAddress); + + Assert.Equal(0, result); + } + + [Fact] + public void BalanceOf_OwnerNftTokenCountInStorage_ReturnsTokenCount() + { + var sender = "0x0000000000000000000000000000000000000002".HexToAddress(); + smartContractStateMock.SetupGet(m => m.Message).Returns(new Message(contractAddress, sender, 0)); + + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetUInt256($"Balance:{ownerAddress}", 15); + var nonFungibleToken = CreateNonFungibleToken(); + + var result = nonFungibleToken.BalanceOf(ownerAddress); + + Assert.Equal(15, result); + } + + [Fact] + public void SetApprovalForAll_SetsMessageSender_ToOperatorApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SetApprovalForAll(operatorAddress, true); + + Assert.True(state.GetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}")); + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalForAllLog { Owner = ownerAddress, Operator = operatorAddress, Approved = true })); + } + + [Fact] + public void Approve_TokenOwnerNotMessageSenderOrOperator_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); + } + + [Fact] + public void Approve_ValidApproval_SwitchesOwnerToApprovedForNFToken() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.Approve(someAddress, 1); + + Assert.Equal(state.GetAddress("IdToApproval:1"), someAddress); + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); + } + + [Fact] + public void Approve_NFTokenOwnerSameAsMessageSender_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(ownerAddress, 1)); + } + + [Fact] + public void Approve_ValidApproval_ByApprovedOperator_SwitchesOwnerToApprovedForNFToken() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.Approve(someAddress, 1); + + Assert.Equal(state.GetAddress("IdToApproval:1"), someAddress); + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); + } + + [Fact] + public void Approve_InvalidNFToken_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + state.SetAddress("IdToOwner:1", Address.Zero); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); + } + + [Fact] + public void TransferFrom_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void TransferFrom_NFTokenOwnerZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", Address.Zero); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(Address.Zero, targetAddress, 1)); + } + + + [Fact] + public void TransferFrom_ValidTokenTransfer_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetAddress("IdToApproval:1", approvalAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void TransferFrom_ValidTokenTransfer_MessageSenderApprovedOwnerToOperator_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.True(state.GetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void TransferFrom_MessageSenderNotAllowedToCall_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var invalidSenderAddress = "0x0000000000000000000000000000000000000015".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_TokenDoesNotBelongToFrom_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var notOwningAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(notOwningAddress, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(ownerAddress, Address.Zero, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractFalse_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + transactionExecutorMock.Verify(t => t.Call(It.IsAny(), It.IsAny
(), It.IsAny(), "OnNonFungibleTokenReceived", It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractFalse_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetAddress("IdToApproval:1", approvalAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + transactionExecutorMock.Verify(t => t.Call(It.IsAny(), It.IsAny
(), It.IsAny(), "OnNonFungibleTokenReceived", It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractFalse_ValidTokenTransfer_MessageSenderApprovedOwnerToOperator_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.True(state.GetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + + transactionExecutorMock.Verify(t => t.Call(It.IsAny(), It.IsAny
(), It.IsAny(), "OnNonFungibleTokenReceived", It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTrue_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + SetupForOnNonFungibleTokenReceived(targetAddress, ownerAddress, ownerAddress, 1).Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance::{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTrue_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetAddress("IdToApproval:1", approvalAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + SetupForOnNonFungibleTokenReceived(targetAddress, approvalAddress, ownerAddress, 1).Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTrue_ValidTokenTransfer_MessageSenderApprovedOwnerToOperator_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + SetupForOnNonFungibleTokenReceived(targetAddress, operatorAddress, ownerAddress, 1).Returns(TransferResult.Transferred(true)); + + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.True(state.GetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_MessageSenderNotAllowedToCall_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var invalidSenderAddress = "0x0000000000000000000000000000000000000015".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_NFTokenOwnerZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", Address.Zero); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(Address.Zero, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_TokenDoesNotBelongToFrom_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var notOwningAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(notOwningAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ValidTokenTransfer_ToContractReturnsFalse_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + SetupForOnNonFungibleTokenReceived(targetAddress, ownerAddress, ownerAddress, 1).Returns(TransferResult.Transferred(false)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + private IReturnsThrows SetupForOnNonFungibleTokenReceived(Address targetAddress, Address @operator, Address from, UInt256 tokenId) + { + return transactionExecutorMock.Setup(t => t.Call(It.IsAny(), targetAddress, 0, "OnNonFungibleTokenReceived", It.IsAny(), 0ul)) + .Callback((a, b, c, d, callParams, f) => + { + Assert.True(@operator.Equals(callParams[0])); + Assert.True(from.Equals(callParams[1])); + Assert.True(tokenId.Equals(callParams[2])); + Assert.True(callParams[3] is byte[] bytes && bytes.Length == 0); + }); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + SetupForOnNonFungibleTokenReceived(targetAddress, ownerAddress, ownerAddress, 1).Returns(TransferResult.Transferred(1)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, Address.Zero, 1)); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractFalse_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + transactionExecutorMock.Verify(t => t.Call(It.IsAny(), It.IsAny
(), It.IsAny(), "OnNonFungibleTokenReceived", It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractFalse_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetAddress("IdToApproval:1", approvalAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + transactionExecutorMock.Verify(t => t.Call(It.IsAny(), It.IsAny
(), It.IsAny(), "OnNonFungibleTokenReceived", It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractFalse_ValidTokenTransfer_MessageSenderApprovedOwnerToOperator_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.True(state.GetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + + transactionExecutorMock.Verify(t => t.Call(It.IsAny(), It.IsAny
(), It.IsAny(), "OnNonFungibleTokenReceived", It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTrue_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + var data = new byte[] { 12 }; + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (UInt256)1, data }; + + transactionExecutorMock.Setup(t => t.Call(It.IsAny(), targetAddress, 0, "OnNonFungibleTokenReceived", callParamsExpected, 0)) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, data); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTrue_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetAddress("IdToApproval:1", approvalAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + var data = new byte[] { 12 }; + var callParamsExpected = new object[] { approvalAddress, ownerAddress, (UInt256)1, data }; + + transactionExecutorMock.Setup(t => t.Call(It.IsAny(), targetAddress, 0, "OnNonFungibleTokenReceived", callParamsExpected, 0)) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, data); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTrue_ValidTokenTransfer_MessageSenderApprovedOwnerToOperator_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + var data = new byte[] { 12 }; + var callParamsExpected = new object[] { operatorAddress, ownerAddress, (UInt256)1, data }; + + transactionExecutorMock.Setup(t => t.Call(It.IsAny(), targetAddress, 0, "OnNonFungibleTokenReceived", callParamsExpected, 0)) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, data); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.True(state.GetBool($"OwnerToOperator:{ownerAddress}:{operatorAddress}")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_MessageSenderNotAllowedToCall_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var invalidSenderAddress = "0x0000000000000000000000000000000000000015".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_NFTokenOwnerZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", Address.Zero); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(Address.Zero, targetAddress, 1, new byte[1] { 0xff })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_TokenDoesNotBelongToFrom_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var notOwningAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(notOwningAddress, targetAddress, 1, new byte[1] { 0xff })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ValidTokenTransfer_ToContractReturnsFalse_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + var data = new byte[] { 12 }; + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (UInt256)1, data }; + + transactionExecutorMock.Setup(t => t.Call(It.IsAny(), targetAddress, 0, "OnNonFungibleTokenReceived", callParamsExpected, 0)) + .Returns(TransferResult.Transferred(false)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, data)); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.IsContractResult = true; + var nonFungibleToken = CreateNonFungibleToken(); + + + var data = new byte[] { 12 }; + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (UInt256)1, data }; + transactionExecutorMock.Setup(t => t.Call(It.IsAny(), targetAddress, 0, "OnNonFungibleTokenReceived", callParamsExpected, 0)) + .Returns(TransferResult.Transferred(1)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, data)); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, Address.Zero, 1, new byte[1] { 0xff })); + } + + [Fact] + public void Mint_CalledByNonOwner_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var userAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(userAddress); + + Assert.Throws(() => nonFungibleToken.Mint(userAddress, (UInt256)1, GetTokenURI(1))); + } + + [Fact] + public void Mint_ToAdressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Mint(Address.Zero, (UInt256)1, GetTokenURI(1))); + } + + [Fact] + public void Mint_MintingNewToken_Called_By_None_Owner_When_OwnerOnlyMintingFalse_Success() + { + ownerOnlyMinting = false; + + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(targetAddress); + nonFungibleToken.Mint(targetAddress, (UInt256)1, GetTokenURI(1)); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + Assert.Equal(GetTokenURI(1), nonFungibleToken.TokenURI(1)); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = Address.Zero, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void Mint_MintingNewToken_Success() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.Mint(targetAddress, (UInt256)1, GetTokenURI(1)); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + Assert.Equal(GetTokenURI(1), nonFungibleToken.TokenURI(1)); + Assert.Null(nonFungibleToken.TokenURI(2)); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = Address.Zero, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeMint_MintingNewToken_When_Destination_Is_Contract_Success() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + state.IsContractResult = true; + + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = CreateNonFungibleToken(); + var data = new byte[] { 12 }; + var parameter = new object[] { ownerAddress, Address.Zero, (UInt256)1, data }; + transactionExecutorMock.Setup(t => t.Call(It.IsAny(), targetAddress, 0, "OnNonFungibleTokenReceived", parameter, 0)) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeMint(targetAddress, (UInt256)1, GetTokenURI(1), data); + + Assert.Equal(targetAddress, state.GetAddress("IdToOwner:1")); + Assert.Equal(1, state.GetUInt256($"Balance:{targetAddress}")); + Assert.Equal(GetTokenURI(1), nonFungibleToken.TokenURI(1)); + Assert.Null(nonFungibleToken.TokenURI(2)); + + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = Address.Zero, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void Burn_NoneExistingToken_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Burn(0)); + } + + [Fact] + public void Burn_BurningNotOwnedToken_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var anotherTokenOwner = "0x0000000000000000000000000000000000000007".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.SetAddress("IdToOwner:0", anotherTokenOwner); + + var nonFungibleToken = CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Burn(0)); + } + + [Fact] + public void Burn_BurningAToken_Success() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + state.SetAddress("IdToOwner:1", ownerAddress); + state.SetUInt256($"Balance:{ownerAddress}", 1); + + var nonFungibleToken = CreateNonFungibleToken(); + + nonFungibleToken.Burn(1); + + Assert.Equal(Address.Zero, state.GetAddress("IdToOwner:1")); + Assert.Equal(0, state.GetUInt256($"Balance:{ownerAddress}")); + Assert.Null(nonFungibleToken.TokenURI(1)); + contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = Address.Zero, TokenId = 1 })); + } + + private NonFungibleToken CreateNonFungibleToken() + { + return new NonFungibleToken(smartContractStateMock.Object, name, symbol, ownerOnlyMinting); + } +} \ No newline at end of file diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/TransferResult.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/TransferResult.cs new file mode 100644 index 00000000..5a2ff0db --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken.Tests/TransferResult.cs @@ -0,0 +1,16 @@ +using Stratis.SmartContracts; + +namespace NonFungibleTokenContract.Tests +{ + public class TransferResult : ITransferResult + { + public object ReturnValue { get; private set; } + + public bool Success { get; private set; } + + public static TransferResult Failed() => new TransferResult { Success = false }; + + + public static TransferResult Transferred(object returnValue) => new TransferResult { Success = true, ReturnValue = returnValue }; + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleToken.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleToken.cs new file mode 100644 index 00000000..645a7a0a --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleToken.cs @@ -0,0 +1,99 @@ +namespace NonFungibleTokenContract +{ + using Stratis.SmartContracts; + + /// + /// Interface for a non-fungible token. + /// + public interface INonFungibleToken + { + /// + /// Transfers the ownership of an NFT from one address to another address. This function can + /// be changed to payable. + /// + /// Throws unless is the current owner, an authorized operator, or the + /// approved address for this NFT.Throws if 'from' is not the current owner.Throws if 'to' is + /// the zero address.Throws if 'tokenId' is not a valid NFT. When transfer is complete, this + /// function checks if 'to' is a smart contract. If so, it calls + /// 'OnNonFungibleTokenReceived' on 'to' and throws if the return value true. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + /// Additional data with no specified format, sent in call to 'to'. + void SafeTransferFrom(Address from, Address to, UInt256 tokenId, byte[] data); + + /// + /// Transfers the ownership of an NFT from one address to another address. This function can + /// be changed to payable. + /// + /// This works identically to the other function with an extra data parameter, except this + /// function just sets data to an empty byte array. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + void SafeTransferFrom(Address from, Address to, UInt256 tokenId); + + /// + /// Throws unless is the current owner, an authorized operator, or the approved + /// address for this NFT.Throws if is not the current owner.Throws if is the zero + /// address.Throws if is not a valid NFT. This function can be changed to payable. + /// + /// The caller is responsible to confirm that is capable of receiving NFTs or else + /// they maybe be permanently lost. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + void TransferFrom(Address from, Address to, UInt256 tokenId); + + /// + /// Set or reaffirm the approved address for an NFT. This function can be changed to payable. + /// + /// + /// The zero address indicates there is no approved address. Throws unless is + /// the current NFT owner, or an authorized operator of the current owner. + /// + /// Address to be approved for the given NFT ID. + /// ID of the token to be approved. + void Approve(Address approved, UInt256 tokenId); + + /// + /// Enables or disables approval for a third party ("operator") to manage all of + /// 's assets. It also Logs the ApprovalForAll event. + /// + /// This works even if sender doesn't own any tokens at the time. + /// Address to add to the set of authorized operators. + /// True if the operators is approved, false to revoke approval. + void SetApprovalForAll(Address operatorAddress, bool approved); + + /// + /// Returns the number of NFTs owned by 'owner'. NFTs assigned to the zero address are + /// considered invalid, and this function throws for queries about the zero address. + /// + /// Address for whom to query the balance. + /// Balance of owner. + UInt256 BalanceOf(Address owner); + + /// + /// Returns the address of the owner of the NFT. NFTs assigned to zero address are considered invalid, and queries about them do throw. + /// + /// The identifier for an NFT. + /// Address of tokenId owner. + Address OwnerOf(UInt256 tokenId); + + /// + /// Get the approved address for a single NFT. + /// + /// Throws if 'tokenId' is not a valid NFT. + /// ID of the NFT to query the approval of. + /// Address that tokenId is approved for. + Address GetApproved(UInt256 tokenId); + + /// + /// Checks if 'operator' is an approved operator for 'owner'. + /// + /// The address that owns the NFTs. + /// The address that acts on behalf of the owner. + /// True if approved for all, false otherwise. + bool IsApprovedForAll(Address owner, Address operatorAddress); + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenMetadata.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenMetadata.cs new file mode 100644 index 00000000..a32cd46d --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenMetadata.cs @@ -0,0 +1,12 @@ +using Stratis.SmartContracts; + +namespace NonFungibleTokenContract +{ + public interface INonFungibleTokenMetadata + { + string Name { get; } + string Symbol { get; } + + string TokenURI(UInt256 tokenId); + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenReceiver.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenReceiver.cs new file mode 100644 index 00000000..b3ceb3ce --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/INonFungibleTokenReceiver.cs @@ -0,0 +1,22 @@ +namespace NonFungibleTokenContract +{ + using Stratis.SmartContracts; + + /// + /// Interface for a non-fungible token receiver. + /// + public interface INonFungibleTokenReceiver + { + /// + /// Handle the receipt of a NFT. The smart contract calls this function on the + /// recipient after a transfer. This function MAY throw or return false to revert and reject the transfer. + /// Return true if the transfer is ok. + /// + /// The address which called safeTransferFrom function. + /// The address which previously owned the token. + /// The NFT identifier which is being transferred. + /// Additional data with no specified format. + /// A bool indicating the resulting operation. + bool OnNonFungibleTokenReceived(Address operatorAddress, Address fromAddress, UInt256 tokenId, byte[] data); + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/ISupportsInterface.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/ISupportsInterface.cs new file mode 100644 index 00000000..9ea804f7 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/ISupportsInterface.cs @@ -0,0 +1,15 @@ +namespace NonFungibleTokenContract +{ + /// + /// Interface for a class with interface indication support. + /// + public interface ISupportsInterface + { + /// + /// Function to check which interfaces are supported by this contract. + /// + /// Id of the interface. + /// True if is supported, false otherwise. + bool SupportsInterface(uint interfaceID); + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/ITicketContract.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/ITicketContract.cs new file mode 100644 index 00000000..c5d66098 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/ITicketContract.cs @@ -0,0 +1,10 @@ +using Stratis.SmartContracts; + +namespace TicketContract +{ + public interface ITicketContract + { + void MarkAsUsed(UInt256 tokenId); + bool IsUsed(UInt256 tokenId); + } +} diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs new file mode 100644 index 00000000..b01b3dc3 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs @@ -0,0 +1,638 @@ +using Stratis.SmartContracts; +/// +/// A non fungible token contract. +/// +public class NonFungibleToken : SmartContract +{ + + /// + /// Function to check which interfaces are supported by this contract. + /// + /// Id of the interface. + /// True if is supported, false otherwise. + public bool SupportsInterface(uint interfaceID) + { + return State.GetBool($"SupportedInterface:{interfaceID}"); + } + + /// + /// Sets the supported interface value. + /// + /// The interface id. + /// A value indicating if the interface id is supported. + private void SetSupportedInterfaces(TokenInterface interfaceId, bool value) => State.SetBool($"SupportedInterface:{(uint)interfaceId}", value); + + /// + /// Gets the key to the persistent state for the owner by NFT ID. + /// + /// The NFT ID. + /// The persistent storage key to get or set the NFT owner. + private string GetIdToOwnerKey(UInt256 id) => $"IdToOwner:{id}"; + + /// + /// Gets the address of the owner of the NFT ID. + /// + /// The ID of the NFT + ///The owner address. + private Address GetIdToOwner(UInt256 id) => State.GetAddress(GetIdToOwnerKey(id)); + + /// + /// Sets the owner to the NFT ID. + /// + /// The ID of the NFT + /// The address of the owner. + private void SetIdToOwner(UInt256 id, Address value) => State.SetAddress(GetIdToOwnerKey(id), value); + + /// + /// Gets the key to the persistent state for the approval address by NFT ID. + /// + /// The NFT ID. + /// The persistent storage key to get or set the NFT approval. + private string GetIdToApprovalKey(UInt256 id) => $"IdToApproval:{id}"; + + /// + /// Getting from NFT ID the approval address. + /// + /// The ID of the NFT + /// Address of the approval. + private Address GetIdToApproval(UInt256 id) => State.GetAddress(GetIdToApprovalKey(id)); + + /// + /// Setting to NFT ID to approval address. + /// + /// The ID of the NFT + /// The address of the approval. + private void SetIdToApproval(UInt256 id, Address value) => State.SetAddress(GetIdToApprovalKey(id), value); + + /// + /// Gets the amount of non fungible tokens the owner has. + /// + /// The address of the owner. + /// The amount of non fungible tokens. + private UInt256 GetBalance(Address address) => State.GetUInt256($"Balance:{address}"); + + /// + /// Sets the owner count of this non fungible tokens. + /// + /// The address of the owner. + /// The amount of tokens. + private void SetBalance(Address address, UInt256 value) => State.SetUInt256($"Balance:{address}", value); + + /// + /// Gets the permission value of the operator authorization to perform actions on behalf of the owner. + /// + /// The owner address of the NFT. + /// >Address of the authorized operators + /// A value indicating if the operator has permissions to act on behalf of the owner. + private bool GetOwnerToOperator(Address owner, Address operatorAddress) => State.GetBool($"OwnerToOperator:{owner}:{operatorAddress}"); + + /// + /// Sets the owner to operator permission. + /// + /// The owner address of the NFT. + /// >Address to add to the set of authorized operators. + /// The permission value. + private void SetOwnerToOperator(Address owner, Address operatorAddress, bool value) => State.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); + + /// + /// Owner of the contract is responsible to for minting/burning + /// + public Address Owner + { + get => State.GetAddress(nameof(Owner)); + private set => State.SetAddress(nameof(Owner), value); + } + + /// + /// Claimed new owner + /// + public Address PendingOwner + { + get => State.GetAddress(nameof(PendingOwner)); + private set => State.SetAddress(nameof(PendingOwner), value); + } + + /// + /// Name for non-fungible token contract + /// + public string Name + { + get => State.GetString(nameof(Name)); + private set => State.SetString(nameof(Name), value); + } + + /// + /// Symbol for non-fungible token contract + /// + public string Symbol + { + get => State.GetString(nameof(Symbol)); + private set => State.SetString(nameof(Symbol), value); + } + + private string GetIdToTokenURI(UInt256 tokenId) => State.GetString($"URI:{tokenId}"); + private void SetIdToTokenURI(UInt256 tokenId, string uri) => State.SetString($"URI:{tokenId}", uri); + + private bool OwnerOnlyMinting + { + get => State.GetBool(nameof(OwnerOnlyMinting)); + set => State.SetBool(nameof(OwnerOnlyMinting), value); + } + + /// + /// Constructor used to create a new instance of the non-fungible token. + /// + /// The execution state for the contract. + /// Name of the NFT Contract + /// Symbol of the NFT Contract + /// True, if only owner allowed to mint tokens + /// + public NonFungibleToken(ISmartContractState state, string name, string symbol, bool ownerOnlyMinting) : base(state) + { + this.SetSupportedInterfaces(TokenInterface.ISupportsInterface, true); // (ERC165) - ISupportsInterface + this.SetSupportedInterfaces(TokenInterface.INonFungibleToken, true); // (ERC721) - INonFungibleToken, + this.SetSupportedInterfaces(TokenInterface.INonFungibleTokenReceiver, false); // (ERC721) - INonFungibleTokenReceiver + this.SetSupportedInterfaces(TokenInterface.INonFungibleTokenMetadata, true); // (ERC721) - INonFungibleTokenMetadata + this.SetSupportedInterfaces(TokenInterface.INonFungibleTokenEnumerable, false); // (ERC721) - INonFungibleTokenEnumerable + this.SetSupportedInterfaces(TokenInterface.ITicketContract, true); + + this.Name = name; + this.Symbol = symbol; + this.Owner = Message.Sender; + this.OwnerOnlyMinting = ownerOnlyMinting; + } + + /// + /// Marks ticket as used. + /// + /// Ticket ID + public void MarkAsUsed(UInt256 tokenId) + { + State.SetBool($"IsUsed:{tokenId}", true); + } + + /// + /// Checks if ticket were used or not. + /// + /// Ticket ID + /// + public bool IsUsed(UInt256 tokenId) + { + return State.GetBool($"IsUsed:{tokenId}"); + } + + /// + /// Transfers the ownership of an NFT from one address to another address. This function can + /// be changed to payable. + /// + /// Throws unless is the current owner, an authorized operator, or the + /// approved address for this NFT.Throws if 'from' is not the current owner.Throws if 'to' is + /// the zero address.Throws if 'tokenId' is not a valid NFT. When transfer is complete, this + /// function checks if 'to' is a smart contract. If so, it calls + /// 'OnNonFungibleTokenReceived' on 'to' and throws if the return value true. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + /// Additional data with no specified format, sent in call to 'to'. + public void SafeTransferFrom(Address from, Address to, UInt256 tokenId, byte[] data) + { + SafeTransferFromInternal(from, to, tokenId, data); + } + + /// + /// Transfers the ownership of an NFT from one address to another address. This function can + /// be changed to payable. + /// + /// This works identically to the other function with an extra data parameter, except this + /// function just sets data to an empty byte array. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + public void SafeTransferFrom(Address from, Address to, UInt256 tokenId) + { + SafeTransferFromInternal(from, to, tokenId, new byte[0]); + } + + /// + /// Throws unless is the current owner, an authorized operator, or the approved + /// address for this NFT. + /// Throws if is not the current owner. + /// Throws if is the zero address. + /// Throws if is not a valid NFT. + /// + /// The caller is responsible to confirm that is capable of receiving NFTs or else + /// they maybe be permanently lost. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + public void TransferFrom(Address from, Address to, UInt256 tokenId) + { + EnsureAddressIsNotEmpty(to); + Address tokenOwner = GetIdToOwner(tokenId); + + EnsureAddressIsNotEmpty(tokenOwner); + CanTransfer(tokenOwner, tokenId); + + Assert(tokenOwner == from, "The from parameter is not token owner."); + + TransferInternal(tokenOwner, to, tokenId); + } + + /// + /// Set or reaffirm the approved address for an NFT. This function can be changed to payable. + /// + /// + /// The zero address indicates there is no approved address. Throws unless is + /// the current NFT owner, or an authorized operator of the current owner. + /// + /// Address to be approved for the given NFT ID. + /// ID of the token to be approved. + public void Approve(Address approved, UInt256 tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + + EnsureAddressIsNotEmpty(tokenOwner); + + CanOperate(tokenOwner); + + Assert(approved != tokenOwner, $"The {nameof(approved)} address is already token owner."); + + SetIdToApproval(tokenId, approved); + LogApproval(tokenOwner, approved, tokenId); + } + + /// + /// Enables or disables approval for a third party ("operator") to manage all of + /// 's assets. It also Logs the ApprovalForAll event. + /// + /// This works even if sender doesn't own any tokens at the time. + /// Address to add to the set of authorized operators. + /// True if the operators is approved, false to revoke approval. + public void SetApprovalForAll(Address operatorAddress, bool approved) + { + SetOwnerToOperator(Message.Sender, operatorAddress, approved); + LogApprovalForAll(Message.Sender, operatorAddress, approved); + } + + /// + /// Returns the number of NFTs owned by 'owner'. NFTs assigned to the zero address are + /// considered invalid, and this function throws for queries about the zero address. + /// + /// Address for whom to query the balance. + /// Balance of owner. + public UInt256 BalanceOf(Address owner) + { + EnsureAddressIsNotEmpty(owner); + return GetBalance(owner); + } + + /// + /// Returns the address of the owner of the NFT. NFTs assigned to zero address are considered invalid, and queries about them do throw. + /// + /// The identifier for an NFT. + /// Address of tokenId owner. + public Address OwnerOf(UInt256 tokenId) + { + Address owner = GetIdToOwner(tokenId); + EnsureAddressIsNotEmpty(owner); + return owner; + } + + /// + /// Get the approved address for a single NFT. + /// + /// Throws if 'tokenId' is not a valid NFT. + /// ID of the NFT to query the approval of. + /// Address that tokenId is approved for. + public Address GetApproved(UInt256 tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + + EnsureAddressIsNotEmpty(tokenOwner); + + return GetIdToApproval(tokenId); + } + + /// + /// Checks if 'operator' is an approved operator for 'owner'. + /// + /// The address that owns the NFTs. + /// The address that acts on behalf of the owner. + /// True if approved for all, false otherwise. + public bool IsApprovedForAll(Address owner, Address operatorAddress) + { + return GetOwnerToOperator(owner, operatorAddress); + } + + /// + /// Actually performs the transfer. + /// + /// Does NO checks. + /// The current owner of the NFT. + /// Address of a new owner. + /// The NFT that is being transferred. + private void TransferInternal(Address from, Address to, UInt256 tokenId) + { + ClearApproval(tokenId); + + RemoveToken(from, tokenId); + AddToken(to, tokenId); + + LogTransfer(from, to, tokenId); + } + + /// + /// Removes a NFT from owner. + /// + /// Use and override this function with caution. Wrong usage can have serious consequences. + /// Address from wich we want to remove the NFT. + /// Which NFT we want to remove. + private void RemoveToken(Address from, UInt256 tokenId) + { + var tokenCount = GetBalance(from); + SetBalance(from, tokenCount - 1); + SetIdToOwner(tokenId, Address.Zero); + } + + /// + /// Assignes a new NFT to owner. + /// + /// Use and override this function with caution. Wrong usage can have serious consequences. + /// Address to which we want to add the NFT. + /// Which NFT we want to add. + private void AddToken(Address to, UInt256 tokenId) + { + SetIdToOwner(tokenId, to); + var currentTokenAmount = GetBalance(to); + SetBalance(to, currentTokenAmount + 1); + } + + /// + /// Actually perform the safeTransferFrom. + /// + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + /// Additional data with no specified format, sent in call to 'to' if it is a contract. + private void SafeTransferFromInternal(Address from, Address to, UInt256 tokenId, byte[] data) + { + TransferFrom(from, to, tokenId); + + EnsureContractReceivedToken(from, to, tokenId, data); + } + + /// + /// Clears the current approval of a given NFT ID. + /// + /// ID of the NFT to be transferred + private void ClearApproval(UInt256 tokenId) + { + if (GetIdToApproval(tokenId) != Address.Zero) + { + SetIdToApproval(tokenId, Address.Zero); + } + } + + /// + /// This logs when ownership of any NFT changes by any mechanism. This event logs when NFTs are + /// created('from' == 0) and destroyed('to' == 0). Exception: during contract creation, any + /// number of NFTs may be created and assigned without logging Transfer.At the time of any + /// transfer, the approved Address for that NFT (if any) is reset to none. + /// + /// The from address. + /// The to address. + /// The NFT ID. + private void LogTransfer(Address from, Address to, UInt256 tokenId) + { + Log(new TransferLog() { From = from, To = to, TokenId = tokenId }); + } + + /// + /// This logs when the approved Address for an NFT is changed or reaffirmed. The zero + /// Address indicates there is no approved Address. When a Transfer logs, this also + /// indicates that the approved Address for that NFT (if any) is reset to none. + /// + /// The owner address. + /// The approved address. + /// The NFT ID. + private void LogApproval(Address owner, Address approved, UInt256 tokenId) + { + Log(new ApprovalLog() { Owner = owner, Approved = approved, TokenId = tokenId }); + } + + /// + /// This logs when an operator is enabled or disabled for an owner. The operator can manage all NFTs of the owner. + /// + /// The owner address + /// The operator address. + /// A boolean indicating if it has been approved. + private void LogApprovalForAll(Address owner, Address operatorAddress, bool approved) + { + Log(new ApprovalForAllLog() { Owner = owner, Operator = operatorAddress, Approved = approved }); + } + + + /// + /// Guarantees that the is an owner or operator of the given NFT. + /// + /// ID of the NFT to validate. + private void CanOperate(Address tokenOwner) + { + Assert(IsOwnerOrOperator(tokenOwner), "Caller is not owner nor approved."); + } + + private bool IsOwnerOrOperator(Address tokenOwner) + { + return tokenOwner == Message.Sender || GetOwnerToOperator(tokenOwner, Message.Sender); + } + + /// + /// Guarantees that the msg.sender is allowed to transfer NFT. + /// + /// ID of the NFT to transfer. + private void CanTransfer(Address tokenOwner, UInt256 tokenId) + { + Assert(IsOwnerOrOperator(tokenOwner) || GetIdToApproval(tokenId) == Message.Sender, "Caller is not owner nor approved for token."); + Assert(!IsUsed(tokenId), "Ticket was already used."); + } + + /// + /// Only owner can set new owner and new owner will be in pending state + /// till new owner will call method. + /// + /// The new owner which is going to be in pending state + public void SetPendingOwner(Address newOwner) + { + EnsureOwnerOnly(); + PendingOwner = newOwner; + + Log(new OwnershipTransferRequestedLog { CurrentOwner = Owner, PendingOwner = newOwner }); + } + + /// + /// Waiting be called after new owner is requested by call. + /// Pending owner will be new owner after successfull call. + /// + public void ClaimOwnership() + { + var newOwner = PendingOwner; + + Assert(newOwner == Message.Sender, "ClaimOwnership must be called by the new(pending) owner."); + + var oldOwner = Owner; + Owner = newOwner; + PendingOwner = Address.Zero; + + Log(new OwnershipTransferedLog { PreviousOwner = oldOwner, NewOwner = newOwner }); + } + + private void EnsureOwnerOnly() + { + Assert(Message.Sender == Owner, "The method is owner only."); + } + + /// + /// Mints new tokens + /// + /// The address that will own the minted NFT + /// The id of token + /// Metadata URI of the token + public UInt256 Mint(Address to, UInt256 tokenId, string uri) + { + MintToken(to, tokenId, uri); + + return tokenId; + } + + /// + /// Mints new tokens + /// + /// The address that will own the minted NFT + /// The id of token + /// Metadata URI of the token + /// The data param will passed destination contract + public UInt256 SafeMint(Address to, UInt256 tokenId, string uri, byte[] data) + { + MintToken(to, tokenId, uri); + + EnsureContractReceivedToken(Address.Zero, to, tokenId, data); + + return tokenId; + } + + private void MintToken(Address to, UInt256 tokenId, string uri) + { + if (OwnerOnlyMinting) + { + EnsureOwnerOnly(); + } + + var owner = GetIdToOwner(tokenId); + + Assert(owner == Address.Zero, "Token already minted."); + + EnsureAddressIsNotEmpty(to); + + AddToken(to, tokenId); + + SetIdToTokenURI(tokenId, uri); + + LogTransfer(Address.Zero, to, tokenId); + } + + /// + /// Notify contract for received token if destination(to) address is a contract. + /// Raise exception if notification fails. + /// + /// The address which previously owned the token. + /// Destination address that will receive the token + /// The token identifier which is being transferred. + /// Additional data with no specified format. + private void EnsureContractReceivedToken(Address from, Address to, UInt256 tokenId, byte[] data) + { + if (State.IsContract(to)) + { + var result = Call(to, 0, "OnNonFungibleTokenReceived", new object[] { Message.Sender, from, tokenId, data }, 0); + Assert((bool)result.ReturnValue, "OnNonFungibleTokenReceived call failed."); + } + } + + public void Burn(UInt256 tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + + Assert(tokenOwner == Message.Sender, "Only token owner can burn the token."); + + ClearApproval(tokenId); + RemoveToken(tokenOwner, tokenId); + + SetIdToTokenURI(tokenId, null); + + LogTransfer(tokenOwner, Address.Zero, tokenId); + } + + public string TokenURI(UInt256 tokenId) => GetIdToTokenURI(tokenId); + + private void EnsureAddressIsNotEmpty(Address address) + { + Assert(address != Address.Zero, "The address can not be zero."); + } + + private enum TokenInterface + { + ISupportsInterface = 1, + INonFungibleToken = 2, + INonFungibleTokenReceiver = 3, + INonFungibleTokenMetadata = 4, + INonFungibleTokenEnumerable = 5, + ITicketContract = 100, + } + + public struct TransferLog + { + [Index] + public Address From; + [Index] + public Address To; + [Index] + public UInt256 TokenId; + } + + public struct ApprovalLog + { + [Index] + public Address Owner; + [Index] + public Address Approved; + [Index] + public UInt256 TokenId; + } + + public struct ApprovalForAllLog + { + [Index] + public Address Owner; + [Index] + public Address Operator; + + public bool Approved; + } + + public struct OwnershipTransferedLog + { + [Index] + public Address PreviousOwner; + + [Index] + public Address NewOwner; + } + + public struct OwnershipTransferRequestedLog + { + [Index] + public Address CurrentOwner; + [Index] + public Address PendingOwner; + } + +} \ No newline at end of file diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleTokenContract.csproj b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleTokenContract.csproj new file mode 100644 index 00000000..b0475c52 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleTokenContract.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.1 + + + + + + + + diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleTokenContract.sln b/Testnet/NonFungibleToken-Ticket/NonFungibleTokenContract.sln new file mode 100644 index 00000000..e069dd04 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleTokenContract.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29201.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonFungibleTokenContract", "NonFungibleToken\NonFungibleTokenContract.csproj", "{D64B8959-5CC0-43D4-99B7-E07481222B5D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonFungibleTokenContract.Tests", "NonFungibleToken.Tests\NonFungibleTokenContract.Tests.csproj", "{855863D4-4F60-47D0-AD2A-164749950614}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D64B8959-5CC0-43D4-99B7-E07481222B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D64B8959-5CC0-43D4-99B7-E07481222B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D64B8959-5CC0-43D4-99B7-E07481222B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D64B8959-5CC0-43D4-99B7-E07481222B5D}.Release|Any CPU.Build.0 = Release|Any CPU + {855863D4-4F60-47D0-AD2A-164749950614}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {855863D4-4F60-47D0-AD2A-164749950614}.Debug|Any CPU.Build.0 = Debug|Any CPU + {855863D4-4F60-47D0-AD2A-164749950614}.Release|Any CPU.ActiveCfg = Release|Any CPU + {855863D4-4F60-47D0-AD2A-164749950614}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {70BE9CE1-C24C-48EC-861C-D3FDEA2628FD} + EndGlobalSection +EndGlobal diff --git a/Testnet/NonFungibleToken-Ticket/README.MD b/Testnet/NonFungibleToken-Ticket/README.MD new file mode 100644 index 00000000..ffe23034 --- /dev/null +++ b/Testnet/NonFungibleToken-Ticket/README.MD @@ -0,0 +1,15 @@ +# NFT Ticket Contract + +**Compiler Version** +``` +v2.0.0 +``` +**Contract Hash** +``` +TBD +``` + +**Contract Byte Code** +``` +TBD +``` From 0651f9dcf58e129f41f3bf5627665b021842baae Mon Sep 17 00:00:00 2001 From: "Lazar Prijovic @madrazzl3" Date: Mon, 6 Mar 2023 13:46:58 +0400 Subject: [PATCH 2/7] Add contract's hash & bytecode --- Testnet/NonFungibleToken-Ticket/README.MD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Testnet/NonFungibleToken-Ticket/README.MD b/Testnet/NonFungibleToken-Ticket/README.MD index ffe23034..5d8db83f 100644 --- a/Testnet/NonFungibleToken-Ticket/README.MD +++ b/Testnet/NonFungibleToken-Ticket/README.MD @@ -6,10 +6,10 @@ v2.0.0 ``` **Contract Hash** ``` -TBD +e8d9a79928cdad35fd05a78b10d968b74e8623698b7202c96704d12ed0a5172c ``` **Contract Byte Code** ``` -TBD +4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C0102003C11F7E60000000000000000E00022200B0130000022000000020000000000002A400000002000000060000000000010002000000002000004000000000000000400000000000000008000000002000000000000030040850000100000100000000010000010000000000000100000000000000000000000D83F00004F000000000000000000000000000000000000000000000000000000006000000C000000BC3F00001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E7465787400000030200000002000000022000000020000000000000000000000000000200000602E72656C6F6300000C0000000060000000020000002400000000000000000000000000004000004200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C400000000000004800000002000500882800003417000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007202280500000A7201000070038C0E000001280600000A6F0700000A2A7A02280500000A720100007003B88C0E000001280600000A046F0800000A2A46722F000070038C06000001280600000A2A4E02280500000A020328030000066F0900000A2A5202280500000A02032803000006046F0A00000A2A46724B000070038C06000001280600000A2A4E02280500000A020328060000066F0900000A2A5202280500000A02032806000006046F0A00000A2A7202280500000A726D000070038C07000001280600000A6F0B00000A2A7602280500000A726D000070038C07000001280600000A046F0C00000A2A8A02280500000A7285000070038C07000001048C07000001280D00000A6F0700000A2A8E02280500000A7285000070038C07000001048C07000001280D00000A056F0800000A2A4602280500000A72B50000706F0900000A2A4A02280500000A72B5000070036F0A00000A2A4602280500000A72C10000706F0900000A2A4A02280500000A72C1000070036F0A00000A2A4602280500000A72DB0000706F0E00000A2A4A02280500000A72DB000070036F0F00000A2A4602280500000A72E50000706F0E00000A2A4A02280500000A72E5000070036F0F00000A2A7202280500000A72F3000070038C06000001280600000A6F0E00000A2A7602280500000A72F3000070038C06000001280600000A046F0F00000A2A4602280500000A72030100706F0700000A2A4A02280500000A7203010070036F0800000A2A1330030060000000000000000203281000000A021717280200000602181728020000060219162802000006021A172802000006021B162802000006021F6417280200000602042812000006020528140000060202281100000A6F1200000A280E000006020E0428180000062A7602280500000A7225010070038C06000001280600000A176F0800000A2A7202280500000A7225010070038C06000001280600000A6F0700000A2A32020304050E0428280000062A4202030405168D1200000128280000062A000000133004003A0000000100001102042839000006020528040000060A02062839000006020605282F000006020603281300000A723B010070281400000A0206040528250000062A0000133004004400000001000011020428040000060A020628390000060206282D000006020306281500000A728901007072D9010070280600000A281400000A020403280800000602060304282B0000062A9E0202281100000A6F1200000A0304280C0000060202281100000A6F1200000A0304282C0000062A3E02032839000006020328090000062A133002001100000001000011020328040000060A02062839000006062A000000133002001700000001000011020328040000060A02062839000006020328070000062A26020304280B0000062A86020528290000060203052826000006020405282700000602030405282A0000062A00133004002800000002000011020328090000060A02030617281600000A281700000A280A00000602047E1800000A28050000062A1330040024000000020000110204032805000006020328090000060A02030617281600000A281900000A280A0000062A5602030405281E000006020304050E0428360000062A82020328070000067E1800000A281500000A2C0C02037E1800000A28080000062A00133003002800000003000011021200FE15040000021200037D080000041200047D090000041200057D0A00000406280100002B2A133003002800000004000011021200FE15050000021200037D0B0000041200047D0C0000041200057D0D00000406280200002B2A133003002800000005000011021200FE15060000021200037D0E0000041200047D0F0000041200057D1000000406280300002B2A4E020203282E00000672EB010070281400000A2AA20302281100000A6F1200000A281300000A2D13020302281100000A6F1200000A280B0000062A172A000000133003004400000000000000020203282E0000062D190204280700000602281100000A6F1200000A281300000A2B0117722F020070281400000A020204281B00000616FE017287020070281400000A2A13300300320000000600001102283200000602032810000006021200FE1508000002120002280D0000067D130000041200037D1400000406280400002B2A0000133003005C0000000700001102280F0000060A020602281100000A6F1200000A281300000A72B9020070281400000A02280D0000060B0206280E000006027E1800000A2810000006021202FE15070000021202077D110000041202067D1200000408280500002B2A8A0202281100000A6F1200000A02280D000006281300000A722B030070281400000A2A2E020304052835000006042A6A020304052835000006027E1800000A03040E042836000006042A00001330040051000000010000110228170000062C06022832000006020428040000060A02067E1800000A281300000A725F030070281400000A0203283900000602030428270000060204052816000006027E1800000A0304282A0000062A00000013300800660000000800001102280500000A046F1B00000A2C570204166A728B0300701A8D10000001251602281100000A6F1200000A8C07000001A22517038C07000001A22518058C06000001A225190E04A2166A281C00000A0A02066F1D00000AA51300000172C1030070281400000A2A0000133004004900000001000011020328040000060A020602281100000A6F1200000A281300000A7211040070281400000A020328290000060206032826000006020314281600000602067E1800000A03282A0000062A22020328150000062A5E02037E1800000A281500000A725B040070281400000A2A000042534A4201000100000000000C00000076342E302E33303331390000000005006C00000020090000237E00008C090000F406000023537472696E67730000000080100000980400002355530018150000100000002347554944000000281500000C02000023426C6F620000000000000002000001571DA201090A000000FA01330016000001000000130000000800000014000000390000005F0000001D000000060000000F0000000800000001000000050000000A00000001000000020000000600000005000000000082030100000000000600B001B5050600DF01B50506009C018B050F00D50500000A004B0612060A00160012060A00020612060A00710112060A006C0612060600DE03C10306005201C1030A00D00112060A008501120606000100C10306003F02C10306005906C1030A000A0112060600FD01C1030600E303C103000000001E000000000001000100010010001104000015000100010003010000D2000000290001003A000A011000D40200002D0008003A000A011000B60200002D000B003A000A011000C20200002D000E003A000A011000810200002D0011003A000A011000980200002D0013003A00060653000C015680E1000F01568010040F01568033050F0156805B000F0156801F010F0156803B060F010600D40389000600580489000600980013010600270589000600BD00890006009800130106002705890006007C0589000600BD00170106000005890006001B05890006000E0589000600CE0489005020000000008600E2001A0101006D200000000081009E051F0102008C200000000081009F06260104009E20000000008100E6042C010500B220000000008100F30433010600C7200000000081008C0626010800D9200000000081001A032C010900ED200000000081002A0333010A000221000000008100F4003B010C001F21000000008100FF0042010D003D210000000081004D054A010F006021000000008100600552011100842100000000860895045D00140096210000000081089F045B011400A921000000008608A9045D001500BB21000000008108BA045B011500CE210000000086083B0161011600E021000000008108440165011600F32100000000860894036101170005220000000081089F03650117001822000000008100330026011800352200000000810043006A0119005322000000008108460271011B0065220000000081085B0275011B00782200000000861885057A011C00E422000000008600A800830120000223000000008600B300890121001F23000000008600C8038F0122002C23000000008600C8039B0126004023000000008600CC039B0129008823000000008600180242012C00D8230000000086006B03A5012E00002400000000860020023B01300010240000000086002A022C0131003024000000008600BA002C013200532400000000860048034A0133005D24000000008100FD029B0135008024000000008100220442013800B424000000008100EB0342013A00E424000000008100E4028F013C00FA240000000081003A03830140001C250000000081007D049B01410050250000000081000E039B0144008425000000008100590352014700B8250000000081005C015B014A00CC250000000081007305CF004B00F825000000008100890442014C004826000000008600CB045B014E008826000000008600630406004F00F026000000008100AF0606004F0013270000000086008006AC014F001F270000000086007C06B60152003C270000000081002E04C20156009C27000000008100F4038F0159001028000000008600530483015D0065280000000086004A0026015E006E28000000008100D9065B015F00000001002700000001008C0000000200120200000100CF0000000100CF0000000100CF0000000200120200000100CF0000000100CF0000000100CF00000002001202000001000A06000001000A06000002001202000001002D0500000200E405000001002D0500000200E40500000300120200000100120200000100120200000100120200000100120200000100A00000000100A00000000200E002000001001202000001009601000002004D0100000300AA0300000400700200000100A00000000100A00000000100D90300000200600400000300A00000000400700000000100D90300000200600400000300A00000000100D90300000200600400000300A00000000100C60000000200A00000000100E40500000200C600000001002D0500000100A00000000100A000000001002D0500000200E40500000100D90300000200600400000300A00000000100D90300000200A00000000100600400000200A00000000100D90300000200600400000300A00000000400700000000100A00000000100D90300000200600400000300A000000001002D0500000200C60000000300A000000001002D0500000200E40500000300C60000000100DB0400000100DB0400000100DB0400000200A00000000100240500000100600400000200A00000000300E00200000100600400000200A00000000300E00200000400700000000100600400000200A00000000300E00200000100D90300000200600400000300A00000000400700000000100A00000000100A000000001000A06090085050100110085050600190085050A006100850506002900670110007900290615006900B1031B006900B90320006900F40526006900FF052C0069000800330069001300390079002906400069003202470069003C024C00290085055200290013015800890072045D003900BF066700290085066F003900CB066700310060067A0031003804800039005B0489003100470480002900DC02920069003006CF0029007D03D50049000202E10008000800EE0008000C00F30008001000F80008001400FD0008001800020108001C0007012E000B00D8012E001300E1012E001B00000201012300EE0021012300EE0041012300EE0061012300EE0081012300EE00A1012300EE00C1012300EE00E1012300EE0021022300EE0041022300EE0061022300EE0081022300EE00620075008D009E00A800B200BC00CA000200010000002705CB010000CE04CB0100004801D0010000A303D00100005F02D40102000D00030001000E00030002000F000500010010000500020011000700010012000700020013000900010014000900020017000B00010018000B000480000000000000000000000000000000004B060000040000000000000000000000E5007500000000000200000000000000000000000000120600000000030002000400020005000200060002000700020008000200350099003500A3003500AD003500B7003500C500000000000055496E7433320047657455496E743235360053657455496E74323536003C4D6F64756C653E00696E746572666163654944004765744964546F546F6B656E555249005365744964546F546F6B656E5552490076616C75655F5F00494E6F6E46756E6769626C65546F6B656E4D657461646174610053797374656D2E507269766174652E436F72654C696200696E74657266616365496400546F6B656E496400746F6B656E4964004D61726B4173557365640049735573656400476574417070726F76656400617070726F76656400696400546F6B656E496E746572666163650049537570706F727473496E746572666163650047657442616C616E63650053657442616C616E636500494D657373616765006765745F4D65737361676500494E6F6E46756E6769626C65546F6B656E456E756D657261626C65006765745F4E616D65007365745F4E616D65006E616D650056616C7565547970650043616E4F706572617465006765745F53746174650049536D617274436F6E74726163745374617465004950657273697374656E7453746174650073746174650044656275676761626C6541747472696275746500436F6D70696C6174696F6E52656C61786174696F6E7341747472696275746500496E6465784174747269627574650052756E74696D65436F6D7061746962696C6974794174747269627574650042797465006765745F52657475726E56616C75650076616C756500417070726F76650042616C616E63654F66004F776E65724F6600476574537472696E6700536574537472696E67006765745F4F776E65724F6E6C794D696E74696E67007365745F4F776E65724F6E6C794D696E74696E67006F776E65724F6E6C794D696E74696E67004F776E6572736869705472616E7366657265644C6F67004F776E6572736869705472616E736665725265717565737465644C6F6700417070726F76616C4C6F6700417070726F76616C466F72416C6C4C6F67005472616E736665724C6F670075726900536166655472616E7366657246726F6D496E7465726E616C005472616E73666572496E7465726E616C004C6F67417070726F76616C004765744964546F417070726F76616C005365744964546F417070726F76616C00436C656172417070726F76616C004973417070726F766564466F72416C6C004C6F67417070726F76616C466F72416C6C00536574417070726F76616C466F72416C6C0043616C6C00536D617274436F6E74726163742E646C6C006765745F53796D626F6C007365745F53796D626F6C0073796D626F6C00476574426F6F6C00536574426F6F6C0053797374656D00536166655472616E7366657246726F6D0066726F6D00456E756D00426F6F6C65616E00416464546F6B656E00456E73757265436F6E74726163745265636569766564546F6B656E00494E6F6E46756E6769626C65546F6B656E0052656D6F7665546F6B656E004D696E74546F6B656E006F705F5375627472616374696F6E006F705F4164646974696F6E004275726E00546F005A65726F00746F00436C61696D4F776E657273686970006765745F53656E646572004C6F675472616E736665720043616E5472616E73666572006765745F4F776E6572007365745F4F776E6572006765745F50656E64696E674F776E6572007365745F50656E64696E674F776E65720053657450656E64696E674F776E657200746F6B656E4F776E6572004765744964546F4F776E6572005365744964546F4F776E65720050726576696F75734F776E65720043757272656E744F776E6572004E65774F776E6572006E65774F776E6572006F776E657200494E6F6E46756E6769626C65546F6B656E5265636569766572004765744F776E6572546F4F70657261746F72005365744F776E6572546F4F70657261746F720049734F776E65724F724F70657261746F72002E63746F720053797374656D2E446961676E6F737469637300536574537570706F72746564496E74657266616365730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F646573006F70657261746F724164647265737300476574416464726573730053657441646472657373006164647265737300537472617469732E536D617274436F6E74726163747300466F726D6174004973436F6E747261637400495469636B6574436F6E747261637400536D617274436F6E7472616374004F626A656374006F705F496D706C6963697400495472616E73666572526573756C7400536166654D696E7400417373657274004765744964546F417070726F76616C4B6579004765744964546F4F776E65724B657900456E737572654F776E65724F6E6C79006F705F457175616C697479006F705F496E657175616C69747900456E737572654164647265737349734E6F74456D70747900000000002D53007500700070006F00720074006500640049006E0074006500720066006100630065003A007B0030007D00001B4900640054006F004F0077006E00650072003A007B0030007D0000214900640054006F0041007000700072006F00760061006C003A007B0030007D000017420061006C0061006E00630065003A007B0030007D00002F4F0077006E006500720054006F004F00700065007200610074006F0072003A007B0030007D003A007B0031007D00000B4F0077006E00650072000019500065006E00640069006E0067004F0077006E006500720000094E0061006D006500000D530079006D0062006F006C00000F5500520049003A007B0030007D0000214F0077006E00650072004F006E006C0079004D0069006E00740069006E00670000154900730055007300650064003A007B0030007D00004D5400680065002000660072006F006D00200070006100720061006D00650074006500720020006900730020006E006F007400200074006F006B0065006E0020006F0077006E00650072002E00004F54006800650020007B0030007D0020006100640064007200650073007300200069007300200061006C0072006500610064007900200074006F006B0065006E0020006F0077006E00650072002E00001161007000700072006F007600650064000043430061006C006C006500720020006900730020006E006F00740020006F0077006E006500720020006E006F007200200061007000700072006F007600650064002E000057430061006C006C006500720020006900730020006E006F00740020006F0077006E006500720020006E006F007200200061007000700072006F00760065006400200066006F007200200074006F006B0065006E002E0000315400690063006B00650074002000770061007300200061006C0072006500610064007900200075007300650064002E00007143006C00610069006D004F0077006E0065007200730068006900700020006D007500730074002000620065002000630061006C006C0065006400200062007900200074006800650020006E00650077002800700065006E00640069006E006700290020006F0077006E00650072002E00003354006800650020006D006500740068006F00640020006900730020006F0077006E006500720020006F006E006C0079002E00002B54006F006B0065006E00200061006C007200650061006400790020006D0069006E007400650064002E0000354F006E004E006F006E00460075006E006700690062006C00650054006F006B0065006E0052006500630065006900760065006400004F4F006E004E006F006E00460075006E006700690062006C00650054006F006B0065006E00520065006300650069007600650064002000630061006C006C0020006600610069006C00650064002E0000494F006E006C007900200074006F006B0065006E0020006F0077006E00650072002000630061006E0020006200750072006E002000740068006500200074006F006B0065006E002E000039540068006500200061006400640072006500730073002000630061006E0020006E006F00740020006200650020007A00650072006F002E0000000000AF5D068910484A449B78184A777E105D0004200101080320000105200101111104200012350500020E0E1C042001020E052002010E02052001111D0E062002010E111D05200111190E062002010E11190600030E0E1C1C0420010E0E052002010E0E0520010112210420001245042000111D040701111D07000202111D111D05200201020E04070111190500011119080800021119111911190306111D040701111006300101011E00040A0111100407011114040A0111140407011118040A0111180407011120040A011120080703111D111D111C040A01111C040701122505200102111D0B20051225111D0B0E1D1C0B0320001C087CEC85D7BEA7798E0401000000040200000004030000000404000000040500000004640000000206080306110C03061119020602042001020906200201110C020520010E1119062001111D1119072002011119111D0620011119111D07200201111D111907200202111D111D08200301111D111D0205200101111D0320000E042001010E0620020111190E0320000204200101020820040112210E0E020520010111190520010211190B200401111D111D11191D0509200301111D111D111906200201111D020920031119111D11190E0B20041119111D11190E1D0508200301111D11190E042800111D0328000E032800020801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F777301080100020000000000000000000000000000000000000000100000000000000000000000000000000040000000000000000000001A4000000020000000000000000000000000000000000000000000000C400000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF25002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000C0000002C3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` From cb80fb2e8ea71f8bc52968c711b037b18600d391 Mon Sep 17 00:00:00 2001 From: "Lazar Prijovic @madrazzl3" Date: Wed, 8 Mar 2023 15:09:18 +0400 Subject: [PATCH 3/7] Add EnsureOwnerOnly and add setters/getters for IsUsed property --- .../NonFungibleToken/NonFungibleToken.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs index b01b3dc3..54146556 100644 --- a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs @@ -94,6 +94,20 @@ public bool SupportsInterface(uint interfaceID) /// The permission value. private void SetOwnerToOperator(Address owner, Address operatorAddress, bool value) => State.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); + /// + /// Gets IsUsed state for the token. + /// + /// Token ID. + /// A value indicating if the ticket token was marked as used or not. + private bool GetIsUsed(UInt256 tokenId) => State.GetBool($"IsUsed:{tokenId}"); + + /// + /// Sets IsUsed state for the token. + /// + /// Token ID. + /// New value indicating was ticket used or not. + private void SetIsUsed(UInt256 tokenId, bool value) => State.SetBool($"IsUsed:{tokenId}", value); + /// /// Owner of the contract is responsible to for minting/burning /// @@ -168,7 +182,8 @@ public NonFungibleToken(ISmartContractState state, string name, string symbol, b /// Ticket ID public void MarkAsUsed(UInt256 tokenId) { - State.SetBool($"IsUsed:{tokenId}", true); + EnsureOwnerOnly(); + this.SetIsUsed(tokenId, true); } /// @@ -178,7 +193,7 @@ public void MarkAsUsed(UInt256 tokenId) /// public bool IsUsed(UInt256 tokenId) { - return State.GetBool($"IsUsed:{tokenId}"); + return this.GetIsUsed(tokenId); } /// From 8a2d735dd53c55742b1882a86ce83d6d18e55c07 Mon Sep 17 00:00:00 2001 From: "Lazar Prijovic @madrazzl3" Date: Wed, 8 Mar 2023 15:22:08 +0400 Subject: [PATCH 4/7] Add logging to MarkAsUsed --- .../NonFungibleToken/NonFungibleToken.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs index 54146556..b2fb37f4 100644 --- a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs @@ -184,6 +184,7 @@ public void MarkAsUsed(UInt256 tokenId) { EnsureOwnerOnly(); this.SetIsUsed(tokenId, true); + this.LogMarkAsUsed(tokenId); } /// @@ -446,6 +447,14 @@ private void LogApprovalForAll(Address owner, Address operatorAddress, bool appr Log(new ApprovalForAllLog() { Owner = owner, Operator = operatorAddress, Approved = approved }); } + /// + /// This logs when the token was marked as used. + /// + /// Ticket ID. + private void LogMarkAsUsed(UInt256 tokenId) + { + Log(new MarkAsUsedLog() { TokenId = tokenId }); + } /// /// Guarantees that the is an owner or operator of the given NFT. @@ -650,4 +659,9 @@ public struct OwnershipTransferRequestedLog public Address PendingOwner; } + public struct MarkAsUsedLog + { + [Index] + public UInt256 TokenId; + } } \ No newline at end of file From d3ef89e728b9376e79493590d6d5e3834a339997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Prijovi=C4=87?= Date: Tue, 14 Mar 2023 15:10:03 +0400 Subject: [PATCH 5/7] Replace Ticket with Token Co-authored-by: YakupIpek --- .../NonFungibleToken/NonFungibleToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs index b2fb37f4..4015af9e 100644 --- a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs @@ -450,7 +450,7 @@ private void LogApprovalForAll(Address owner, Address operatorAddress, bool appr /// /// This logs when the token was marked as used. /// - /// Ticket ID. + /// Token ID. private void LogMarkAsUsed(UInt256 tokenId) { Log(new MarkAsUsedLog() { TokenId = tokenId }); From f4bd339eb872eace3cf3e245366e23e5a8b3aa28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Prijovi=C4=87?= Date: Tue, 14 Mar 2023 14:12:59 +0300 Subject: [PATCH 6/7] Remove unnecessary this. --- .../NonFungibleToken/NonFungibleToken.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs index 4015af9e..71ec7421 100644 --- a/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs +++ b/Testnet/NonFungibleToken-Ticket/NonFungibleToken/NonFungibleToken.cs @@ -183,8 +183,8 @@ public NonFungibleToken(ISmartContractState state, string name, string symbol, b public void MarkAsUsed(UInt256 tokenId) { EnsureOwnerOnly(); - this.SetIsUsed(tokenId, true); - this.LogMarkAsUsed(tokenId); + SetIsUsed(tokenId, true); + LogMarkAsUsed(tokenId); } /// @@ -194,7 +194,7 @@ public void MarkAsUsed(UInt256 tokenId) /// public bool IsUsed(UInt256 tokenId) { - return this.GetIsUsed(tokenId); + return GetIsUsed(tokenId); } /// From b9fab594dd3228a5f4f3f8bfd54568c0fa5400b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Prijovi=C4=87?= Date: Tue, 14 Mar 2023 15:05:39 +0300 Subject: [PATCH 7/7] Update sc hash & bytecode --- Testnet/NonFungibleToken-Ticket/README.MD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Testnet/NonFungibleToken-Ticket/README.MD b/Testnet/NonFungibleToken-Ticket/README.MD index 5d8db83f..373d5b50 100644 --- a/Testnet/NonFungibleToken-Ticket/README.MD +++ b/Testnet/NonFungibleToken-Ticket/README.MD @@ -6,10 +6,10 @@ v2.0.0 ``` **Contract Hash** ``` -e8d9a79928cdad35fd05a78b10d968b74e8623698b7202c96704d12ed0a5172c +0a42a2b8a47e1aaac340e141700d146043e3e0d4bcceb9160994b962f4831ca2 ``` **Contract Byte Code** ``` -4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C0102003C11F7E60000000000000000E00022200B0130000022000000020000000000002A400000002000000060000000000010002000000002000004000000000000000400000000000000008000000002000000000000030040850000100000100000000010000010000000000000100000000000000000000000D83F00004F000000000000000000000000000000000000000000000000000000006000000C000000BC3F00001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E7465787400000030200000002000000022000000020000000000000000000000000000200000602E72656C6F6300000C0000000060000000020000002400000000000000000000000000004000004200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C400000000000004800000002000500882800003417000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007202280500000A7201000070038C0E000001280600000A6F0700000A2A7A02280500000A720100007003B88C0E000001280600000A046F0800000A2A46722F000070038C06000001280600000A2A4E02280500000A020328030000066F0900000A2A5202280500000A02032803000006046F0A00000A2A46724B000070038C06000001280600000A2A4E02280500000A020328060000066F0900000A2A5202280500000A02032806000006046F0A00000A2A7202280500000A726D000070038C07000001280600000A6F0B00000A2A7602280500000A726D000070038C07000001280600000A046F0C00000A2A8A02280500000A7285000070038C07000001048C07000001280D00000A6F0700000A2A8E02280500000A7285000070038C07000001048C07000001280D00000A056F0800000A2A4602280500000A72B50000706F0900000A2A4A02280500000A72B5000070036F0A00000A2A4602280500000A72C10000706F0900000A2A4A02280500000A72C1000070036F0A00000A2A4602280500000A72DB0000706F0E00000A2A4A02280500000A72DB000070036F0F00000A2A4602280500000A72E50000706F0E00000A2A4A02280500000A72E5000070036F0F00000A2A7202280500000A72F3000070038C06000001280600000A6F0E00000A2A7602280500000A72F3000070038C06000001280600000A046F0F00000A2A4602280500000A72030100706F0700000A2A4A02280500000A7203010070036F0800000A2A1330030060000000000000000203281000000A021717280200000602181728020000060219162802000006021A172802000006021B162802000006021F6417280200000602042812000006020528140000060202281100000A6F1200000A280E000006020E0428180000062A7602280500000A7225010070038C06000001280600000A176F0800000A2A7202280500000A7225010070038C06000001280600000A6F0700000A2A32020304050E0428280000062A4202030405168D1200000128280000062A000000133004003A0000000100001102042839000006020528040000060A02062839000006020605282F000006020603281300000A723B010070281400000A0206040528250000062A0000133004004400000001000011020428040000060A020628390000060206282D000006020306281500000A728901007072D9010070280600000A281400000A020403280800000602060304282B0000062A9E0202281100000A6F1200000A0304280C0000060202281100000A6F1200000A0304282C0000062A3E02032839000006020328090000062A133002001100000001000011020328040000060A02062839000006062A000000133002001700000001000011020328040000060A02062839000006020328070000062A26020304280B0000062A86020528290000060203052826000006020405282700000602030405282A0000062A00133004002800000002000011020328090000060A02030617281600000A281700000A280A00000602047E1800000A28050000062A1330040024000000020000110204032805000006020328090000060A02030617281600000A281900000A280A0000062A5602030405281E000006020304050E0428360000062A82020328070000067E1800000A281500000A2C0C02037E1800000A28080000062A00133003002800000003000011021200FE15040000021200037D080000041200047D090000041200057D0A00000406280100002B2A133003002800000004000011021200FE15050000021200037D0B0000041200047D0C0000041200057D0D00000406280200002B2A133003002800000005000011021200FE15060000021200037D0E0000041200047D0F0000041200057D1000000406280300002B2A4E020203282E00000672EB010070281400000A2AA20302281100000A6F1200000A281300000A2D13020302281100000A6F1200000A280B0000062A172A000000133003004400000000000000020203282E0000062D190204280700000602281100000A6F1200000A281300000A2B0117722F020070281400000A020204281B00000616FE017287020070281400000A2A13300300320000000600001102283200000602032810000006021200FE1508000002120002280D0000067D130000041200037D1400000406280400002B2A0000133003005C0000000700001102280F0000060A020602281100000A6F1200000A281300000A72B9020070281400000A02280D0000060B0206280E000006027E1800000A2810000006021202FE15070000021202077D110000041202067D1200000408280500002B2A8A0202281100000A6F1200000A02280D000006281300000A722B030070281400000A2A2E020304052835000006042A6A020304052835000006027E1800000A03040E042836000006042A00001330040051000000010000110228170000062C06022832000006020428040000060A02067E1800000A281300000A725F030070281400000A0203283900000602030428270000060204052816000006027E1800000A0304282A0000062A00000013300800660000000800001102280500000A046F1B00000A2C570204166A728B0300701A8D10000001251602281100000A6F1200000A8C07000001A22517038C07000001A22518058C06000001A225190E04A2166A281C00000A0A02066F1D00000AA51300000172C1030070281400000A2A0000133004004900000001000011020328040000060A020602281100000A6F1200000A281300000A7211040070281400000A020328290000060206032826000006020314281600000602067E1800000A03282A0000062A22020328150000062A5E02037E1800000A281500000A725B040070281400000A2A000042534A4201000100000000000C00000076342E302E33303331390000000005006C00000020090000237E00008C090000F406000023537472696E67730000000080100000980400002355530018150000100000002347554944000000281500000C02000023426C6F620000000000000002000001571DA201090A000000FA01330016000001000000130000000800000014000000390000005F0000001D000000060000000F0000000800000001000000050000000A00000001000000020000000600000005000000000082030100000000000600B001B5050600DF01B50506009C018B050F00D50500000A004B0612060A00160012060A00020612060A00710112060A006C0612060600DE03C10306005201C1030A00D00112060A008501120606000100C10306003F02C10306005906C1030A000A0112060600FD01C1030600E303C103000000001E000000000001000100010010001104000015000100010003010000D2000000290001003A000A011000D40200002D0008003A000A011000B60200002D000B003A000A011000C20200002D000E003A000A011000810200002D0011003A000A011000980200002D0013003A00060653000C015680E1000F01568010040F01568033050F0156805B000F0156801F010F0156803B060F010600D40389000600580489000600980013010600270589000600BD00890006009800130106002705890006007C0589000600BD00170106000005890006001B05890006000E0589000600CE0489005020000000008600E2001A0101006D200000000081009E051F0102008C200000000081009F06260104009E20000000008100E6042C010500B220000000008100F30433010600C7200000000081008C0626010800D9200000000081001A032C010900ED200000000081002A0333010A000221000000008100F4003B010C001F21000000008100FF0042010D003D210000000081004D054A010F006021000000008100600552011100842100000000860895045D00140096210000000081089F045B011400A921000000008608A9045D001500BB21000000008108BA045B011500CE210000000086083B0161011600E021000000008108440165011600F32100000000860894036101170005220000000081089F03650117001822000000008100330026011800352200000000810043006A0119005322000000008108460271011B0065220000000081085B0275011B00782200000000861885057A011C00E422000000008600A800830120000223000000008600B300890121001F23000000008600C8038F0122002C23000000008600C8039B0126004023000000008600CC039B0129008823000000008600180242012C00D8230000000086006B03A5012E00002400000000860020023B01300010240000000086002A022C0131003024000000008600BA002C013200532400000000860048034A0133005D24000000008100FD029B0135008024000000008100220442013800B424000000008100EB0342013A00E424000000008100E4028F013C00FA240000000081003A03830140001C250000000081007D049B01410050250000000081000E039B0144008425000000008100590352014700B8250000000081005C015B014A00CC250000000081007305CF004B00F825000000008100890442014C004826000000008600CB045B014E008826000000008600630406004F00F026000000008100AF0606004F0013270000000086008006AC014F001F270000000086007C06B60152003C270000000081002E04C20156009C27000000008100F4038F0159001028000000008600530483015D0065280000000086004A0026015E006E28000000008100D9065B015F00000001002700000001008C0000000200120200000100CF0000000100CF0000000100CF0000000200120200000100CF0000000100CF0000000100CF00000002001202000001000A06000001000A06000002001202000001002D0500000200E405000001002D0500000200E40500000300120200000100120200000100120200000100120200000100120200000100A00000000100A00000000200E002000001001202000001009601000002004D0100000300AA0300000400700200000100A00000000100A00000000100D90300000200600400000300A00000000400700000000100D90300000200600400000300A00000000100D90300000200600400000300A00000000100C60000000200A00000000100E40500000200C600000001002D0500000100A00000000100A000000001002D0500000200E40500000100D90300000200600400000300A00000000100D90300000200A00000000100600400000200A00000000100D90300000200600400000300A00000000400700000000100A00000000100D90300000200600400000300A000000001002D0500000200C60000000300A000000001002D0500000200E40500000300C60000000100DB0400000100DB0400000100DB0400000200A00000000100240500000100600400000200A00000000300E00200000100600400000200A00000000300E00200000400700000000100600400000200A00000000300E00200000100D90300000200600400000300A00000000400700000000100A00000000100A000000001000A06090085050100110085050600190085050A006100850506002900670110007900290615006900B1031B006900B90320006900F40526006900FF052C0069000800330069001300390079002906400069003202470069003C024C00290085055200290013015800890072045D003900BF066700290085066F003900CB066700310060067A0031003804800039005B0489003100470480002900DC02920069003006CF0029007D03D50049000202E10008000800EE0008000C00F30008001000F80008001400FD0008001800020108001C0007012E000B00D8012E001300E1012E001B00000201012300EE0021012300EE0041012300EE0061012300EE0081012300EE00A1012300EE00C1012300EE00E1012300EE0021022300EE0041022300EE0061022300EE0081022300EE00620075008D009E00A800B200BC00CA000200010000002705CB010000CE04CB0100004801D0010000A303D00100005F02D40102000D00030001000E00030002000F000500010010000500020011000700010012000700020013000900010014000900020017000B00010018000B000480000000000000000000000000000000004B060000040000000000000000000000E5007500000000000200000000000000000000000000120600000000030002000400020005000200060002000700020008000200350099003500A3003500AD003500B7003500C500000000000055496E7433320047657455496E743235360053657455496E74323536003C4D6F64756C653E00696E746572666163654944004765744964546F546F6B656E555249005365744964546F546F6B656E5552490076616C75655F5F00494E6F6E46756E6769626C65546F6B656E4D657461646174610053797374656D2E507269766174652E436F72654C696200696E74657266616365496400546F6B656E496400746F6B656E4964004D61726B4173557365640049735573656400476574417070726F76656400617070726F76656400696400546F6B656E496E746572666163650049537570706F727473496E746572666163650047657442616C616E63650053657442616C616E636500494D657373616765006765745F4D65737361676500494E6F6E46756E6769626C65546F6B656E456E756D657261626C65006765745F4E616D65007365745F4E616D65006E616D650056616C7565547970650043616E4F706572617465006765745F53746174650049536D617274436F6E74726163745374617465004950657273697374656E7453746174650073746174650044656275676761626C6541747472696275746500436F6D70696C6174696F6E52656C61786174696F6E7341747472696275746500496E6465784174747269627574650052756E74696D65436F6D7061746962696C6974794174747269627574650042797465006765745F52657475726E56616C75650076616C756500417070726F76650042616C616E63654F66004F776E65724F6600476574537472696E6700536574537472696E67006765745F4F776E65724F6E6C794D696E74696E67007365745F4F776E65724F6E6C794D696E74696E67006F776E65724F6E6C794D696E74696E67004F776E6572736869705472616E7366657265644C6F67004F776E6572736869705472616E736665725265717565737465644C6F6700417070726F76616C4C6F6700417070726F76616C466F72416C6C4C6F67005472616E736665724C6F670075726900536166655472616E7366657246726F6D496E7465726E616C005472616E73666572496E7465726E616C004C6F67417070726F76616C004765744964546F417070726F76616C005365744964546F417070726F76616C00436C656172417070726F76616C004973417070726F766564466F72416C6C004C6F67417070726F76616C466F72416C6C00536574417070726F76616C466F72416C6C0043616C6C00536D617274436F6E74726163742E646C6C006765745F53796D626F6C007365745F53796D626F6C0073796D626F6C00476574426F6F6C00536574426F6F6C0053797374656D00536166655472616E7366657246726F6D0066726F6D00456E756D00426F6F6C65616E00416464546F6B656E00456E73757265436F6E74726163745265636569766564546F6B656E00494E6F6E46756E6769626C65546F6B656E0052656D6F7665546F6B656E004D696E74546F6B656E006F705F5375627472616374696F6E006F705F4164646974696F6E004275726E00546F005A65726F00746F00436C61696D4F776E657273686970006765745F53656E646572004C6F675472616E736665720043616E5472616E73666572006765745F4F776E6572007365745F4F776E6572006765745F50656E64696E674F776E6572007365745F50656E64696E674F776E65720053657450656E64696E674F776E657200746F6B656E4F776E6572004765744964546F4F776E6572005365744964546F4F776E65720050726576696F75734F776E65720043757272656E744F776E6572004E65774F776E6572006E65774F776E6572006F776E657200494E6F6E46756E6769626C65546F6B656E5265636569766572004765744F776E6572546F4F70657261746F72005365744F776E6572546F4F70657261746F720049734F776E65724F724F70657261746F72002E63746F720053797374656D2E446961676E6F737469637300536574537570706F72746564496E74657266616365730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F646573006F70657261746F724164647265737300476574416464726573730053657441646472657373006164647265737300537472617469732E536D617274436F6E74726163747300466F726D6174004973436F6E747261637400495469636B6574436F6E747261637400536D617274436F6E7472616374004F626A656374006F705F496D706C6963697400495472616E73666572526573756C7400536166654D696E7400417373657274004765744964546F417070726F76616C4B6579004765744964546F4F776E65724B657900456E737572654F776E65724F6E6C79006F705F457175616C697479006F705F496E657175616C69747900456E737572654164647265737349734E6F74456D70747900000000002D53007500700070006F00720074006500640049006E0074006500720066006100630065003A007B0030007D00001B4900640054006F004F0077006E00650072003A007B0030007D0000214900640054006F0041007000700072006F00760061006C003A007B0030007D000017420061006C0061006E00630065003A007B0030007D00002F4F0077006E006500720054006F004F00700065007200610074006F0072003A007B0030007D003A007B0031007D00000B4F0077006E00650072000019500065006E00640069006E0067004F0077006E006500720000094E0061006D006500000D530079006D0062006F006C00000F5500520049003A007B0030007D0000214F0077006E00650072004F006E006C0079004D0069006E00740069006E00670000154900730055007300650064003A007B0030007D00004D5400680065002000660072006F006D00200070006100720061006D00650074006500720020006900730020006E006F007400200074006F006B0065006E0020006F0077006E00650072002E00004F54006800650020007B0030007D0020006100640064007200650073007300200069007300200061006C0072006500610064007900200074006F006B0065006E0020006F0077006E00650072002E00001161007000700072006F007600650064000043430061006C006C006500720020006900730020006E006F00740020006F0077006E006500720020006E006F007200200061007000700072006F007600650064002E000057430061006C006C006500720020006900730020006E006F00740020006F0077006E006500720020006E006F007200200061007000700072006F00760065006400200066006F007200200074006F006B0065006E002E0000315400690063006B00650074002000770061007300200061006C0072006500610064007900200075007300650064002E00007143006C00610069006D004F0077006E0065007200730068006900700020006D007500730074002000620065002000630061006C006C0065006400200062007900200074006800650020006E00650077002800700065006E00640069006E006700290020006F0077006E00650072002E00003354006800650020006D006500740068006F00640020006900730020006F0077006E006500720020006F006E006C0079002E00002B54006F006B0065006E00200061006C007200650061006400790020006D0069006E007400650064002E0000354F006E004E006F006E00460075006E006700690062006C00650054006F006B0065006E0052006500630065006900760065006400004F4F006E004E006F006E00460075006E006700690062006C00650054006F006B0065006E00520065006300650069007600650064002000630061006C006C0020006600610069006C00650064002E0000494F006E006C007900200074006F006B0065006E0020006F0077006E00650072002000630061006E0020006200750072006E002000740068006500200074006F006B0065006E002E000039540068006500200061006400640072006500730073002000630061006E0020006E006F00740020006200650020007A00650072006F002E0000000000AF5D068910484A449B78184A777E105D0004200101080320000105200101111104200012350500020E0E1C042001020E052002010E02052001111D0E062002010E111D05200111190E062002010E11190600030E0E1C1C0420010E0E052002010E0E0520010112210420001245042000111D040701111D07000202111D111D05200201020E04070111190500011119080800021119111911190306111D040701111006300101011E00040A0111100407011114040A0111140407011118040A0111180407011120040A011120080703111D111D111C040A01111C040701122505200102111D0B20051225111D0B0E1D1C0B0320001C087CEC85D7BEA7798E0401000000040200000004030000000404000000040500000004640000000206080306110C03061119020602042001020906200201110C020520010E1119062001111D1119072002011119111D0620011119111D07200201111D111907200202111D111D08200301111D111D0205200101111D0320000E042001010E0620020111190E0320000204200101020820040112210E0E020520010111190520010211190B200401111D111D11191D0509200301111D111D111906200201111D020920031119111D11190E0B20041119111D11190E1D0508200301111D11190E042800111D0328000E032800020801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F777301080100020000000000000000000000000000000000000000100000000000000000000000000000000040000000000000000000001A4000000020000000000000000000000000000000000000000000000C400000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF25002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000C0000002C3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C0102005CB4C08A0000000000000000E00022200B013000002200000002000000000000FE400000002000000060000000000010002000000002000004000000000000000400000000000000008000000002000000000000030040850000100000100000000010000010000000000000100000000000000000000000AC4000004F000000000000000000000000000000000000000000000000000000006000000C000000904000001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E7465787400000004210000002000000022000000020000000000000000000000000000200000602E72656C6F6300000C000000006000000002000000240000000000000000000000000000400000420000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E0400000000000004800000002000500CC280000C417000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007202280500000A7201000070038C0E000001280600000A6F0700000A2A7A02280500000A720100007003B88C0E000001280600000A046F0800000A2A46722F000070038C06000001280600000A2A4E02280500000A020328030000066F0900000A2A5202280500000A02032803000006046F0A00000A2A46724B000070038C06000001280600000A2A4E02280500000A020328060000066F0900000A2A5202280500000A02032806000006046F0A00000A2A7202280500000A726D000070038C07000001280600000A6F0B00000A2A7602280500000A726D000070038C07000001280600000A046F0C00000A2A8A02280500000A7285000070038C07000001048C07000001280D00000A6F0700000A2A8E02280500000A7285000070038C07000001048C07000001280D00000A056F0800000A2A7202280500000A72B5000070038C06000001280600000A6F0700000A2A7602280500000A72B5000070038C06000001280600000A046F0800000A2A4602280500000A72CB0000706F0900000A2A4A02280500000A72CB000070036F0A00000A2A4602280500000A72D70000706F0900000A2A4A02280500000A72D7000070036F0A00000A2A4602280500000A72F10000706F0E00000A2A4A02280500000A72F1000070036F0F00000A2A4602280500000A72FB0000706F0E00000A2A4A02280500000A72FB000070036F0F00000A2A7202280500000A7209010070038C06000001280600000A6F0E00000A2A7602280500000A7209010070038C06000001280600000A046F0F00000A2A4602280500000A72190100706F0700000A2A4A02280500000A7219010070036F0800000A2A001330030060000000000000000203281000000A021717280200000602181728020000060219162802000006021A172802000006021B162802000006021F6417280200000602042814000006020528160000060202281100000A6F1200000A2810000006020E04281A0000062A5A022835000006020317280E0000060203282F0000062A220203280D0000062A32020304050E04282A0000062A4202030405168D12000001282A0000062A0000133004003A000000010000110204283C000006020528040000060A0206283C0000060206052832000006020603281300000A723B010070281400000A0206040528270000062A0000133004004400000001000011020428040000060A0206283C00000602062830000006020306281500000A728901007072D9010070280600000A281400000A020403280800000602060304282D0000062A9E0202281100000A6F1200000A0304280C0000060202281100000A6F1200000A0304282E0000062A3E0203283C000006020328090000062A133002001100000001000011020328040000060A0206283C000006062A000000133002001700000001000011020328040000060A0206283C000006020328070000062A26020304280B0000062A860205282B0000060203052828000006020405282900000602030405282C0000062A00133004002800000002000011020328090000060A02030617281600000A281700000A280A00000602047E1800000A28050000062A1330040024000000020000110204032805000006020328090000060A02030617281600000A281900000A280A0000062A56020304052820000006020304050E0428390000062A82020328070000067E1800000A281500000A2C0C02037E1800000A28080000062A00133003002800000003000011021200FE15040000021200037D080000041200047D090000041200057D0A00000406280100002B2A133003002800000004000011021200FE15050000021200037D0B0000041200047D0C0000041200057D0D00000406280200002B2A133003002800000005000011021200FE15060000021200037D0E0000041200047D0F0000041200057D1000000406280300002B2A133003001800000006000011021200FE15090000021200037D1500000406280400002B2A4E020203283100000672EB010070281400000A2AA20302281100000A6F1200000A281300000A2D13020302281100000A6F1200000A280B0000062A172A00000013300300440000000000000002020328310000062D190204280700000602281100000A6F1200000A281300000A2B0117722F020070281400000A020204281D00000616FE017287020070281400000A2A13300300320000000700001102283500000602032812000006021200FE1508000002120002280F0000067D130000041200037D1400000406280500002B2A0000133003005C000000080000110228110000060A020602281100000A6F1200000A281300000A72B9020070281400000A02280F0000060B02062810000006027E1800000A2812000006021202FE15070000021202077D110000041202067D1200000408280600002B2A8A0202281100000A6F1200000A02280F000006281300000A722B030070281400000A2A2E020304052838000006042A6A020304052838000006027E1800000A03040E042839000006042A00001330040051000000010000110228190000062C06022835000006020428040000060A02067E1800000A281300000A725F030070281400000A0203283C00000602030428290000060204052818000006027E1800000A0304282C0000062A00000013300800660000000900001102280500000A046F1B00000A2C570204166A728B0300701A8D10000001251602281100000A6F1200000A8C07000001A22517038C07000001A22518058C06000001A225190E04A2166A281C00000A0A02066F1D00000AA51300000172C1030070281400000A2A0000133004004900000001000011020328040000060A020602281100000A6F1200000A281300000A7211040070281400000A0203282B0000060206032828000006020314281800000602067E1800000A03282C0000062A22020328170000062A5E02037E1800000A281500000A725B040070281400000A2A000042534A4201000100000000000C00000076342E302E33303331390000000005006C00000084090000237E0000F00900001007000023537472696E67730000000000110000980400002355530098150000100000002347554944000000A81500001C02000023426C6F620000000000000002000001571DA201090A000000FA013300160000010000001300000009000000150000003C000000630000001D00000006000000100000000900000001000000050000000A000000010000000200000007000000060000000000A0030100000000000600C001D3050600EF01D3050600AC01A9050F00F30500000A00690630060A00160030060A00200630060A00810130060A008A0630060600FC03DF0306006201DF030A00E00130060A009501300606000100DF0306004F02DF0306007706DF030A001A01300606000D02DF0306000104DF03000000001E000000000001000100010010002F04000015000100010003010000E2000000290001003D000A011000F20200002D0008003D000A011000D40200002D000B003D000A011000E00200002D000E003D000A011000910200002D0011003D000A011000B60200002D0013003D000A011000A80200002D0015003D000606530016015680F100190156802E04190156805105190156805B00190156802F0119015680590619010600F2038900060076048900060098001D010600450589000600CD008900060098001D0106004505890006009A0589000600CD00210106001E05890006003905890006002C0589000600EC048900060098001D015020000000008600F200240101006D20000000008100BC05290102008C20000000008100BD06300104009E20000000008100040536010500B22000000000810011053D010600C720000000008100AA0630010800D920000000008100380336010900ED2000000000810048033D010A000221000000008100040145010C001F210000000081000F014C010D003D210000000081006B0554010F0060210000000081007E055C0111008421000000008100B60065011400A121000000008100C0006B011500BF21000000008608B3045D001700D121000000008108BD0472011700E421000000008608C7045D001800F621000000008108D8047201180009220000000086084B01780119001B2200000000810854017C0119002E22000000008608B20378011A004022000000008108BD037C011A005322000000008100330030011B007022000000008100430081011C008E22000000008108560288011E00A0220000000081086B028C011E00B422000000008618A30591011F002023000000008600AB009A0123003723000000008600C300650124004023000000008600E603A00125004D23000000008600E603AC0129006023000000008600EA03AC012C00A82300000000860028024C012F00F8230000000086008903B6013100202400000000860030024501330030240000000086003A02360134005024000000008600CA003601350073240000000086006603540136007D240000000081001B03AC013800A02400000000810040044C013B00D42400000000810009044C013D0004250000000081000203A0013F001A2500000000810058039A0143003C250000000081009B04AC01440070250000000081002C03AC014700A42500000000810077035C014A00D825000000008100A8009A014D00FC250000000081006C0172014E0010260000000081009105D9004F003C26000000008100A7044C0150008C26000000008600E90472015200CC260000000086008104060053003427000000008100CD060600530057270000000086009E06BD01530063270000000086009A06C701560080270000000081004C04D3015A00E0270000000081001204A0015D00542800000000860071049A016100A9280000000086004A0030016200B228000000008100F70672016300000001002700000001008C0000000200220200000100DF0000000100DF0000000100DF0000000200220200000100DF0000000100DF0000000100DF00000002002202000001002806000001002806000002002202000001004B05000002000206000001004B0500000200020600000300220200000100A00000000100A00000000200220200000100220200000100220200000100220200000100220200000100A00000000100A00000000200FE0200000100220200000100A601000002005D0100000300C80300000400800200000100A00000000100A00000000100F703000002007E0400000300A00000000400700000000100F703000002007E0400000300A00000000100F703000002007E0400000300A00000000100D60000000200A00000000100020600000200D600000001004B0500000100A00000000100A000000001004B0500000200020600000100F703000002007E0400000300A00000000100F70300000200A000000001007E0400000200A00000000100F703000002007E0400000300A00000000400700000000100A00000000100F703000002007E0400000300A000000001004B0500000200D60000000300A000000001004B0500000200020600000300D60000000100A00000000100F90400000100F90400000100F90400000200A000000001004205000001007E0400000200A00000000300FE02000001007E0400000200A00000000300FE02000004007000000001007E0400000200A00000000300FE0200000100F703000002007E0400000300A00000000400700000000100A00000000100A0000000010028060900A30501001100A30506001900A3050A006100A30506002900770110007900470615006900CF031B006900D703200069001206260069001D062C0069000800330069001300390079004706400069004202470069004C024C002900A3055200290023015800890090045D003900DD0667002900A3066F003900E906670031007E067A003100560480003900790489003100650480002900FA02920069004E06D90029009B03DF0049001202EB0008000800F80008000C00FD00080010000201080014000701080018000C0108001C0011012E000B00E9012E001300F2012E001B00110201012300F80021012300F80041012300F80061012300F80081012300F800A1012300F800C1012300F800E1012300F80021022300F80041022300F80061022300F80081022300F800A1022300F800620075008D009E00A800B200BC00C600D4000200010000004505DC010000EC04DC0100005801E1010000C103E10100006F02E50102000F000300010010000300020011000500010012000500020013000700010014000700020015000900010016000900020019000B0001001A000B0004800000000000000000000000000000000069060000040000000000000000000000EF00750000000000020000000000000000000000000030060000000003000200040002000500020006000200070002000800020009000200350099003500A3003500AD003500B7003500C1003500CF0000000055496E7433320047657455496E743235360053657455496E74323536003C4D6F64756C653E00696E746572666163654944004765744964546F546F6B656E555249005365744964546F546F6B656E5552490076616C75655F5F00494E6F6E46756E6769626C65546F6B656E4D657461646174610053797374656D2E507269766174652E436F72654C696200696E74657266616365496400546F6B656E496400746F6B656E4964004C6F674D61726B417355736564004765744973557365640053657449735573656400476574417070726F76656400617070726F76656400696400546F6B656E496E746572666163650049537570706F727473496E746572666163650047657442616C616E63650053657442616C616E636500494D657373616765006765745F4D65737361676500494E6F6E46756E6769626C65546F6B656E456E756D657261626C65006765745F4E616D65007365745F4E616D65006E616D650056616C7565547970650043616E4F706572617465006765745F53746174650049536D617274436F6E74726163745374617465004950657273697374656E7453746174650073746174650044656275676761626C6541747472696275746500436F6D70696C6174696F6E52656C61786174696F6E7341747472696275746500496E6465784174747269627574650052756E74696D65436F6D7061746962696C6974794174747269627574650042797465006765745F52657475726E56616C75650076616C756500417070726F76650042616C616E63654F66004F776E65724F6600476574537472696E6700536574537472696E67006765745F4F776E65724F6E6C794D696E74696E67007365745F4F776E65724F6E6C794D696E74696E67006F776E65724F6E6C794D696E74696E67004F776E6572736869705472616E7366657265644C6F67004D61726B4173557365644C6F67004F776E6572736869705472616E736665725265717565737465644C6F6700417070726F76616C4C6F6700417070726F76616C466F72416C6C4C6F67005472616E736665724C6F670075726900536166655472616E7366657246726F6D496E7465726E616C005472616E73666572496E7465726E616C004C6F67417070726F76616C004765744964546F417070726F76616C005365744964546F417070726F76616C00436C656172417070726F76616C004973417070726F766564466F72416C6C004C6F67417070726F76616C466F72416C6C00536574417070726F76616C466F72416C6C0043616C6C00536D617274436F6E74726163742E646C6C006765745F53796D626F6C007365745F53796D626F6C0073796D626F6C00476574426F6F6C00536574426F6F6C0053797374656D00536166655472616E7366657246726F6D0066726F6D00456E756D00426F6F6C65616E00416464546F6B656E00456E73757265436F6E74726163745265636569766564546F6B656E00494E6F6E46756E6769626C65546F6B656E0052656D6F7665546F6B656E004D696E74546F6B656E006F705F5375627472616374696F6E006F705F4164646974696F6E004275726E00546F005A65726F00746F00436C61696D4F776E657273686970006765745F53656E646572004C6F675472616E736665720043616E5472616E73666572006765745F4F776E6572007365745F4F776E6572006765745F50656E64696E674F776E6572007365745F50656E64696E674F776E65720053657450656E64696E674F776E657200746F6B656E4F776E6572004765744964546F4F776E6572005365744964546F4F776E65720050726576696F75734F776E65720043757272656E744F776E6572004E65774F776E6572006E65774F776E6572006F776E657200494E6F6E46756E6769626C65546F6B656E5265636569766572004765744F776E6572546F4F70657261746F72005365744F776E6572546F4F70657261746F720049734F776E65724F724F70657261746F72002E63746F720053797374656D2E446961676E6F737469637300536574537570706F72746564496E74657266616365730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F646573006F70657261746F724164647265737300476574416464726573730053657441646472657373006164647265737300537472617469732E536D617274436F6E74726163747300466F726D6174004973436F6E747261637400495469636B6574436F6E747261637400536D617274436F6E7472616374004F626A656374006F705F496D706C6963697400495472616E73666572526573756C7400536166654D696E7400417373657274004765744964546F417070726F76616C4B6579004765744964546F4F776E65724B657900456E737572654F776E65724F6E6C79006F705F457175616C697479006F705F496E657175616C69747900456E737572654164647265737349734E6F74456D7074790000002D53007500700070006F00720074006500640049006E0074006500720066006100630065003A007B0030007D00001B4900640054006F004F0077006E00650072003A007B0030007D0000214900640054006F0041007000700072006F00760061006C003A007B0030007D000017420061006C0061006E00630065003A007B0030007D00002F4F0077006E006500720054006F004F00700065007200610074006F0072003A007B0030007D003A007B0031007D0000154900730055007300650064003A007B0030007D00000B4F0077006E00650072000019500065006E00640069006E0067004F0077006E006500720000094E0061006D006500000D530079006D0062006F006C00000F5500520049003A007B0030007D0000214F0077006E00650072004F006E006C0079004D0069006E00740069006E006700004D5400680065002000660072006F006D00200070006100720061006D00650074006500720020006900730020006E006F007400200074006F006B0065006E0020006F0077006E00650072002E00004F54006800650020007B0030007D0020006100640064007200650073007300200069007300200061006C0072006500610064007900200074006F006B0065006E0020006F0077006E00650072002E00001161007000700072006F007600650064000043430061006C006C006500720020006900730020006E006F00740020006F0077006E006500720020006E006F007200200061007000700072006F007600650064002E000057430061006C006C006500720020006900730020006E006F00740020006F0077006E006500720020006E006F007200200061007000700072006F00760065006400200066006F007200200074006F006B0065006E002E0000315400690063006B00650074002000770061007300200061006C0072006500610064007900200075007300650064002E00007143006C00610069006D004F0077006E0065007200730068006900700020006D007500730074002000620065002000630061006C006C0065006400200062007900200074006800650020006E00650077002800700065006E00640069006E006700290020006F0077006E00650072002E00003354006800650020006D006500740068006F00640020006900730020006F0077006E006500720020006F006E006C0079002E00002B54006F006B0065006E00200061006C007200650061006400790020006D0069006E007400650064002E0000354F006E004E006F006E00460075006E006700690062006C00650054006F006B0065006E0052006500630065006900760065006400004F4F006E004E006F006E00460075006E006700690062006C00650054006F006B0065006E00520065006300650069007600650064002000630061006C006C0020006600610069006C00650064002E0000494F006E006C007900200074006F006B0065006E0020006F0077006E00650072002000630061006E0020006200750072006E002000740068006500200074006F006B0065006E002E000039540068006500200061006400640072006500730073002000630061006E0020006E006F00740020006200650020007A00650072006F002E00000000008C629931B0A25947A574F3320067B7020004200101080320000105200101111104200012350500020E0E1C042001020E052002010E02052001111D0E062002010E111D05200111190E062002010E11190600030E0E1C1C0420010E0E052002010E0E0520010112210420001245042000111D040701111D07000202111D111D05200201020E04070111190500011119080800021119111911190306111D040701111006300101011E00040A0111100407011114040A0111140407011118040A0111180407011124040A0111240407011120040A011120080703111D111D111C040A01111C040701122505200102111D0B20051225111D0B0E1D1C0B0320001C087CEC85D7BEA7798E0401000000040200000004030000000404000000040500000004640000000206080306110C03061119020602042001020906200201110C020520010E1119062001111D1119072002011119111D0620011119111D07200201111D111907200202111D111D08200301111D111D020520010211190620020111190205200101111D0320000E042001010E0620020111190E0320000204200101020820040112210E0E020520010111190B200401111D111D11191D0509200301111D111D111906200201111D020920031119111D11190E0B20041119111D11190E1D0508200301111D11190E042800111D0328000E032800020801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F777301080100020000000000000000000000000000000000000010000000000000000000000000000000D44000000000000000000000EE400000002000000000000000000000000000000000000000000000E0400000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF2500200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000C000000003100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ```