From 4db6f1074c62931f33df0d7f39c58183a22c3856 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Sun, 1 Sep 2019 12:54:55 +0200 Subject: [PATCH 01/10] Initial conversion of non fungible token contract needs initial feedback --- src/NonFungibleToken/.gitignore | 333 +++++++++++ .../NonFungibleToken.Tests.csproj | 22 + src/NonFungibleToken/NonFungibleToken.sln | 31 + .../NonFungibleToken/IERC165.cs | 17 + .../NonFungibleToken/INonFungibleToken.cs | 159 +++++ .../INonFungibleTokenReceiver.cs | 30 + .../NonFungibleToken/NonFungibleToken.cs | 565 ++++++++++++++++++ .../NonFungibleToken/NonFungibleToken.csproj | 12 + 8 files changed, 1169 insertions(+) create mode 100644 src/NonFungibleToken/.gitignore create mode 100644 src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleToken.Tests.csproj create mode 100644 src/NonFungibleToken/NonFungibleToken.sln create mode 100644 src/NonFungibleToken/NonFungibleToken/IERC165.cs create mode 100644 src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs create mode 100644 src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs create mode 100644 src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs create mode 100644 src/NonFungibleToken/NonFungibleToken/NonFungibleToken.csproj diff --git a/src/NonFungibleToken/.gitignore b/src/NonFungibleToken/.gitignore new file mode 100644 index 0000000..959502c --- /dev/null +++ b/src/NonFungibleToken/.gitignore @@ -0,0 +1,333 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +.vs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +*.sol \ No newline at end of file diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleToken.Tests.csproj b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleToken.Tests.csproj new file mode 100644 index 0000000..db7c469 --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleToken.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + + + + diff --git a/src/NonFungibleToken/NonFungibleToken.sln b/src/NonFungibleToken/NonFungibleToken.sln new file mode 100644 index 0000000..343e621 --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken.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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NonFungibleToken", "NonFungibleToken\NonFungibleToken.csproj", "{D64B8959-5CC0-43D4-99B7-E07481222B5D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NonFungibleToken.Tests", "NonFungibleToken.Tests\NonFungibleToken.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/src/NonFungibleToken/NonFungibleToken/IERC165.cs b/src/NonFungibleToken/NonFungibleToken/IERC165.cs new file mode 100644 index 0000000..b591725 --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken/IERC165.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NonFungibleToken +{ + public interface IERC165 + { + /** + * @dev Checks if the smart contract includes a specific interface. + * @notice This function uses less than 30,000 gas. + * @param interfaceID The interface identifier, as specified in ERC-165. + * @return True if interfaceID is supported, false otherwise. + */ + bool SupportsInterface(uint interfaceID); + } +} diff --git a/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs new file mode 100644 index 0000000..849a22d --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs @@ -0,0 +1,159 @@ +namespace NonFungibleToken +{ + using Stratis.SmartContracts; + + public interface INonFungibleToken : IERC165 , INonFungibleTokenReceiver + { + /** + * @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are + * created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any + * number of NFTs may be created and assigned without emitting Transfer. At the time of any + * transfer, the approved Address for that NFT (if any) is reset to none. + */ + /*LogTransfer( + Address from, + Address to, + ulong tokenId + );*/ + + /** + * @dev This emits when the approved Address for an NFT is changed or reaffirmed. The zero + * Address indicates there is no approved Address. When a Transfer event emits, this also + * indicates that the approved Address for that NFT (if any) is reset to none. + */ + /*LogApproval( + Address owner, + Address approved, + ulong tokenId + );*/ + + /** + * @dev This emits when an operator is enabled or disabled for an owner. The operator can manage + * all NFTs of the owner. + */ + /*LogApprovalForAll( + Address owner, + Address operatorAddress, + bool approved + );*/ + + /** + * @dev Transfers the ownership of an NFT from one Address to another Address. + * @notice Throws unless `msg.sender` 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 (code size > 0). If so, it calls + * `onERC721Received` on `to` and throws if the return value is not + * `bytes4(keccak256("onERC721Received(Address,ulong,bytes)"))`. + * @param from The current owner of the NFT. + * @param to The new owner. + * @param tokenId The NFT to transfer. + * @param data Additional data with no specified format, sent in call to `to`. + */ + void SafeTransferFrom( + Address from, + Address to, + ulong tokenId, + byte[] data); + + + /** + * @dev Transfers the ownership of an NFT from one Address to another Address. + * @notice This works identically to the other function with an extra data parameter, except this + * function just sets data to "" + * @param from The current owner of the NFT. + * @param to The new owner. + * @param tokenId The NFT to transfer. + */ + void SafeTransferFrom( + Address from, + Address to, + ulong tokenId + ); + + + /** + * @dev Throws unless `msg.sender` 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. + * @notice The caller is responsible to confirm that `to` is capable of receiving NFTs or else + * they mayb be permanently lost. + * @param from The current owner of the NFT. + * @param to The new owner. + * @param tokenId The NFT to transfer. + */ + void TransferFrom( + Address from, + Address to, + ulong tokenId + ); + + + /** + * @dev Set or reaffirm the approved Address for an NFT. + * @notice The zero Address indicates there is no approved Address. Throws unless `msg.sender` is + * the current NFT owner, or an authorized operator of the current owner. + * @param approved The new approved NFT controller. + * @param tokenId The NFT to approve. + */ + void Approve( + Address approved, + ulong tokenId + ); + + + /** + * @dev Enables or disables approval for a third party ("operator") to manage all of + * `msg.sender`'s assets. It also emits the ApprovalForAll event. + * @notice The contract MUST allow multiple operators per owner. + * @param operator Address to add to the set of authorized operators. + * @param approved True if the operators is approved, false to revoke approval. + */ + void SetApprovalForAll( + Address operatorAddress, + bool approved + ); + + + /** + * @dev 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. + * @param owner Address for whom to query the balance. + * @return Balance of owner. + */ + ulong BalanceOf( + Address owner + ); + + /** + * @dev Returns the Address of the owner of the NFT. NFTs assigned to zero Address are considered + * invalid, and queries about them do throw. + * @param tokenId The identifier for an NFT. + * @return Address of tokenId owner. + */ + Address OwnerOf( + ulong tokenId + ); + + /** + * @dev Get the approved Address for a single NFT. + * @notice Throws if `tokenId` is not a valid NFT. + * @param tokenId The NFT to find the approved Address for. + * @return Address that tokenId is approved for. + */ + Address GetApproved( + ulong tokenId + ); + + /** + * @dev Returns true if `operator` is an approved operator for `owner`, false otherwise. + * @param owner The Address that owns the NFTs. + * @param operator The Address that acts on behalf of the owner. + * @return True if approved for all, false otherwise. + */ + bool IsApprovedForAll( + Address owner, + Address operatorAddress + ); + } +} diff --git a/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs b/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs new file mode 100644 index 0000000..e58b52f --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs @@ -0,0 +1,30 @@ +namespace NonFungibleToken +{ + using Stratis.SmartContracts; + using System; + using System.Collections.Generic; + using System.Text; + + public interface INonFungibleTokenReceiver + { + /** + * @dev Handle the receipt of a NFT. The ERC721 smart contract calls this function on the + * recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return + * of other than the magic value MUST result in the transaction being reverted. + * Returns `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` unless throwing. + * @notice The contract address is always the message sender. A wallet/broker/auction application + * MUST implement the wallet interface if it will accept safe transfers. + * @param operator The address which called `safeTransferFrom` function. + * @param from The address which previously owned the token. + * @param tokenId The NFT identifier which is being transferred. + * @param data Additional data with no specified format. + * @return Returns `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. + */ + byte[] onERC721Received( + Address operatorAddress, + Address fromAddress, + ulong tokenId, + byte[] data + ); + } +} diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs new file mode 100644 index 0000000..890b747 --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs @@ -0,0 +1,565 @@ +namespace NonFungibleToken +{ + using Stratis.SmartContracts; + using System; + + public class NonFungibleToken : SmartContract, INonFungibleToken + { + /** + * @dev Magic value of a smart contract that can recieve NFT. + * Equal to: bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")). + */ + // todo: how do we calculate if this is still correct after we've implemented the NonFungibleTokenReceiver? + internal static readonly byte[] MAGIC_ON_ERC721_RECEIVED = new byte[] { 0x15, 0x0b, 0x7a, 0x02 }; + + private string GetIdToOwnerKey(ulong id) + { + return $"IdToOwner:{id}"; + } + + /** + * @dev A mapping from NFT ID to the address that owns it. + */ + private Address GetIdToOwner(ulong id) + { + return this.PersistentState.GetAddress(GetIdToOwnerKey(id)); + } + + private void SetIdToOwner(ulong id, Address value) + { + this.PersistentState.SetAddress(GetIdToOwnerKey(id), value); + } + + /** + * @dev Mapping from NFT ID to approved address. + */ + private string GetIdToApprovalKey(ulong id) + { + return $"IdToApproval:{id}"; + } + + private Address GetIdToApproval(ulong id) + { + return this.PersistentState.GetAddress(GetIdToApprovalKey(id)); + } + + private void SetIdToApproval(ulong id, Address value) + { + this.PersistentState.SetAddress(GetIdToApprovalKey(id), value); + } + + /** + * @dev Mapping from owner address to count of his tokens. + */ + private ulong GetOwnerToNFTokenCount(Address address) + { + return this.PersistentState.GetUInt64($"OwnerToNFTokenCount:{address}"); + } + + private void SetOwnerToNFTokenCount(Address address, ulong value) + { + this.PersistentState.SetUInt64($"OwnerToNFTokenCount:{address}", value); + } + + /** + * @dev Mapping from owner address to mapping of operator addresses. + */ + // todo: review if this is the best way to solve it. + private bool GetOwnerToOperators(Address owner, Address operatorAddress) + { + return this.PersistentState.GetBool($"OwnerToOperator:{owner}:{operatorAddress}"); + } + + private void SetOwnerToOperators(Address owner, Address operatorAddress, bool value) + { + this.PersistentState.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); + } + + public struct TransferLog + { + [Index] + public Address From; + [Index] + public Address To; + [Index] + public ulong TokenId; + } + + public struct ApprovalLog + { + [Index] + public Address Owner; + [Index] + public Address Approved; + [Index] + public ulong TokenId; + } + + public struct ApprovalForAllLog + { + [Index] + public Address Owner; + [Index] + public Address Operator; + + public bool Approved; + } + + /** + * @dev Guarantees that the msg.sender is an owner or operator of the given NFT. + * @param _tokenId ID of the NFT to validate. + */ + private void CanOperate(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == this.Message.Sender || GetOwnerToOperators(tokenOwner, this.Message.Sender)); + } + + /** + * @dev Guarantees that the msg.sender is allowed to transfer NFT. + * @param _tokenId ID of the NFT to transfer. + */ + private void CanTransfer(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + Assert( + tokenOwner == this.Message.Sender + || GetIdToApproval(tokenId) == Message.Sender + || GetOwnerToOperators(tokenOwner, Message.Sender) + ); + } + + /** + * @dev Guarantees that _tokenId is a valid Token. + * @param _tokenId ID of the NFT to validate. + */ + private void ValidNFToken(ulong tokenId) + { + Assert(GetIdToOwner(tokenId) != Address.Zero); + } + + /** + * @dev Contract constructor. + */ + public NonFungibleToken(ISmartContractState state) : base(state) + { + this.SetSupportedInterfaces(0x01ffc9a7, true); // ERC165 + this.SetSupportedInterfaces(0x80ac58cd, true); // ERC721 + } + + /** + * @dev Transfers the ownership of an NFT from one address to another address. This function can + * be changed to payable. + * @notice Throws unless `msg.sender` 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 (code size > 0). If so, it calls + * `onERC721Received` on `_to` and throws if the return value is not + * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`. + * @param _from The current owner of the NFT. + * @param _to The new owner. + * @param _tokenId The NFT to transfer. + * @param _data Additional data with no specified format, sent in call to `_to`. + */ + public void SafeTransferFrom( + Address from, + Address to, + ulong tokenId, + byte[] data) + { + SafeTransferFromInternal(from, to, tokenId, data); + } + + /** + * @dev Transfers the ownership of an NFT from one address to another address. This function can + * be changed to payable. + * @notice This works identically to the other function with an extra data parameter, except this + * function just sets data to "" + * @param _from The current owner of the NFT. + * @param _to The new owner. + * @param _tokenId The NFT to transfer. + */ + public void SafeTransferFrom( + Address from, + Address to, + ulong tokenId + ) + { + SafeTransferFromInternal(from, to, tokenId, new byte[0]); + } + + /** + * @dev Throws unless `msg.sender` 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. This function can be changed to payable. + * @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else + * they maybe be permanently lost. + * @param _from The current owner of the NFT. + * @param _to The new owner. + * @param _tokenId The NFT to transfer. + */ + public void TransferFrom( + Address from, + Address to, + ulong tokenId + ) + { + CanTransfer(tokenId); + ValidNFToken(tokenId); + + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == from); + Assert(to != Address.Zero); + + TransferInternal(to, tokenId); + } + + /** + * @dev Set or reaffirm the approved address for an NFT. This function can be changed to payable. + * @notice The zero address indicates there is no approved address. Throws unless `msg.sender` is + * the current NFT owner, or an authorized operator of the current owner. + * @param _approved Address to be approved for the given NFT ID. + * @param _tokenId ID of the token to be approved. + */ + public void Approve( + Address approved, + ulong tokenId + ) + { + CanOperate(tokenId); + ValidNFToken(tokenId); + + Address tokenOwner = GetIdToOwner(tokenId); + Assert(approved != tokenOwner); + + SetIdToApproval(tokenId, approved); + LogApproval(tokenOwner, approved, tokenId); + } + + /** + * @dev Enables or disables approval for a third party ("operator") to manage all of + * `msg.sender`'s assets. It also emits the ApprovalForAll event. + * @notice This works even if sender doesn't own any tokens at the time. + * @param _operator Address to add to the set of authorized operators. + * @param _approved True if the operators is approved, false to revoke approval. + */ + public void SetApprovalForAll( + Address operatorAddress, + bool approved + ) + { + SetOwnerToOperators(this.Message.Sender, operatorAddress, approved); + LogApprovalForAll(this.Message.Sender, operatorAddress, approved); + } + + /** + * @dev 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. + * @param _owner Address for whom to query the balance. + * @return Balance of _owner. + */ + public ulong BalanceOf( + Address owner + ) + { + Assert(owner != Address.Zero); + return GetOwnerToNFTokenCount(owner); + } + + /** + * @dev Returns the address of the owner of the NFT. NFTs assigned to zero address are considered + * invalid, and queries about them do throw. + * @param _tokenId The identifier for an NFT. + * @return Address of _tokenId owner. + */ + public Address OwnerOf( + ulong tokenId + ) + { + Address owner = GetIdToOwner(tokenId); + Assert(owner != Address.Zero); + return owner; + } + + /** + * @dev Get the approved address for a single NFT. + * @notice Throws if `_tokenId` is not a valid NFT. + * @param _tokenId ID of the NFT to query the approval of. + * @return Address that _tokenId is approved for. + */ + public Address GetApproved( + ulong tokenId + ) + { + ValidNFToken(tokenId); + + return GetIdToApproval(tokenId); + } + + /** + * @dev Checks if `_operator` is an approved operator for `_owner`. + * @param _owner The address that owns the NFTs. + * @param _operator The address that acts on behalf of the owner. + * @return True if approved for all, false otherwise. + */ + public bool IsApprovedForAll( + Address owner, + Address operatorAddress + ) + { + return GetOwnerToOperators(owner, operatorAddress); + } + + /** + * @dev Actually preforms the transfer. + * @notice Does NO checks. + * @param _to Address of a new owner. + * @param _tokenId The NFT that is being transferred. + */ + private void TransferInternal( + Address to, + ulong tokenId + ) + { + Address from = GetIdToOwner(tokenId); + ClearApproval(tokenId); + + RemoveNFToken(from, tokenId); + AddNFToken(to, tokenId); + + LogTransfer(from, to, tokenId); + } + + /** + * @dev Mints a new NFT. + * @notice This is an internal function which should be called from user-implemented external + * mint function. Its purpose is to show and properly initialize data structures when using this + * implementation. + * @param _to The address that will own the minted NFT. + * @param _tokenId of the NFT to be minted by the msg.sender. + */ + private void Mint( + Address to, + ulong tokenId + ) + { + // todo: no references and not on interface? what to do? + Assert(to != Address.Zero); + Assert(GetIdToOwner(tokenId) == Address.Zero); + + AddNFToken(to, tokenId); + + LogTransfer(Address.Zero, to, tokenId); + } + + /** + * @dev Burns a NFT. + * @notice This is an internal function which should be called from user-implemented external burn + * function. Its purpose is to show and properly initialize data structures when using this + * implementation. Also, note that this burn implementation allows the minter to re-mint a burned + * NFT. + * @param _tokenId ID of the NFT to be burned. + */ + private void Burn( + ulong tokenId + ) + { + // todo: no references and not on interface? what to do? + ValidNFToken(tokenId); + + Address tokenOwner = GetIdToOwner(tokenId); + ClearApproval(tokenId); + RemoveNFToken(tokenOwner, tokenId); + LogTransfer(tokenOwner, Address.Zero, tokenId); + } + + /** + * @dev Removes a NFT from owner. + * @notice Use and override this function with caution. Wrong usage can have serious consequences. + * @param _from Address from wich we want to remove the NFT. + * @param _tokenId Which NFT we want to remove. + */ + private void RemoveNFToken( + Address from, + ulong tokenId + ) + { + Assert(GetIdToOwner(tokenId) == from); + SetOwnerToNFTokenCount(from, GetOwnerToNFTokenCount(from) - 1); + this.PersistentState.Clear(GetIdToOwnerKey(tokenId)); + } + + /** + * @dev Assignes a new NFT to owner. + * @notice Use and override this function with caution. Wrong usage can have serious consequences. + * @param _to Address to wich we want to add the NFT. + * @param _tokenId Which NFT we want to add. + */ + public void AddNFToken( + Address to, + ulong tokenId + ) + { + Assert(GetIdToOwner(tokenId) == Address.Zero); + + SetIdToOwner(tokenId, to); + ulong currentTokenAmount = GetOwnerToNFTokenCount(to); + SetOwnerToNFTokenCount(to, checked(currentTokenAmount + 1)); + } + + /** + * @dev Helper function that gets NFT count of owner. This is needed for overriding in enumerable + * extension to remove double storage (gas optimization) of owner nft count. + * @param _owner Address for whom to query the count. + * @return Number of _owner NFTs. + */ + /*private ulong GetOwnerNFTCount( + Address owner + ) + { + return GetOwnerToNFTokenCount(owner); + }*/ + + /** + * @dev Actually perform the safeTransferFrom. + * @param _from The current owner of the NFT. + * @param _to The new owner. + * @param _tokenId The NFT to transfer. + * @param _data Additional data with no specified format, sent in call to `_to`. + */ + private void SafeTransferFromInternal( + Address from, + Address to, + ulong tokenId, + byte[] data + ) + { + CanTransfer(tokenId); + ValidNFToken(tokenId); + + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == from); + Assert(to != Address.Zero); + + TransferInternal(to, tokenId); + + if (this.PersistentState.IsContract(to)) + { + ITransferResult result = this.Call(to, 0, "onERC721Received", new object[] { this.Message.Sender, from, tokenId, data }, 0); + var retval = result.ReturnValue as byte[]; + Assert(MagicBytesEqual(retval, MAGIC_ON_ERC721_RECEIVED)); + } + } + + /** + * @dev Clears the current approval of a given NFT ID. + * @param _tokenId ID of the NFT to be transferred. + */ + private void ClearApproval( + ulong tokenId + ) + { + if (GetIdToApproval(tokenId) != Address.Zero) + { + this.PersistentState.Clear(GetIdToApprovalKey(tokenId)); + } + } + + /** + * @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are + * created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any + * number of NFTs may be created and assigned without emitting Transfer. At the time of any + * transfer, the approved Address for that NFT (if any) is reset to none. + */ + private void LogTransfer( + Address from, + Address to, + ulong tokenId + ) + { + Log(new TransferLog() { From = from, To = to, TokenId = tokenId }); + } + + /** + * @dev This emits when the approved Address for an NFT is changed or reaffirmed. The zero + * Address indicates there is no approved Address. When a Transfer event emits, this also + * indicates that the approved Address for that NFT (if any) is reset to none. + */ + private void LogApproval( + Address owner, + Address approved, + ulong tokenId + ) + { + Log(new ApprovalLog() { Owner = owner, Approved = approved, TokenId = tokenId }); + } + + /** + * @dev This emits when an operator is enabled or disabled for an owner. The operator can manage + * all NFTs of the owner. + */ + private void LogApprovalForAll( + Address owner, + Address operatorAddress, + bool approved + ) + { + Log(new ApprovalForAllLog() { Owner = owner, Operator = operatorAddress, Approved = approved }); + } + + private bool GetSupportedInterfaces(uint interfaceId) + { + return this.PersistentState.GetBool($"SupportedInterface:{interfaceId}"); + } + + private void SetSupportedInterfaces(uint interfaceId, bool value) + { + this.PersistentState.SetBool($"SupportedInterface:{interfaceId}", value); + } + + /** + * @dev Function to check which interfaces are suported by this contract. + * @param _interfaceID Id of the interface. + * @return True if _interfaceID is supported, false otherwise. + */ + public bool SupportsInterface(uint interfaceID) + { + // todo: go back to byte4? + return GetSupportedInterfaces(interfaceID); + } + + // todo: rename to onNonFungibleTokenReceived? + public byte[] onERC721Received(Address operatorAddress, Address fromAddress, ulong tokenId, byte[] data) + { + // todo: update documentation. + string stringToHash = $"onERC721Received{operatorAddress},{fromAddress},{tokenId},{BitConverter.ToString(data)}"; + byte[] bytesToHash = Array.ConvertAll(stringToHash.ToCharArray(), s => Convert.ToByte(s)); + var keccakHash = this.Keccak256(bytesToHash); + var magicBytes = new byte[] { keccakHash[0], keccakHash[1], keccakHash[2], keccakHash[3] }; + return magicBytes; + } + + private bool MagicBytesEqual(byte[] b1, byte[] b2) + { + if (b1 == null || b2 == null) + { + return false; + } + + if (b1.Length != 4 || b2.Length != 4) + { + return false; + } + + for (var i = 0; i < 4; i++) + { + if (!b1[i].Equals(b2[i])) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.csproj b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.csproj new file mode 100644 index 0000000..f511226 --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.2 + + + + + + + + From b0409dc7e1b247f2ef7cb75dec50db8321ffb042 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Mon, 2 Sep 2019 21:35:13 +0200 Subject: [PATCH 02/10] Rework. --- .../NonFungibleToken/IERC165.cs | 17 - .../NonFungibleToken/INonFungibleToken.cs | 244 +++---- .../INonFungibleTokenReceiver.cs | 36 +- .../NonFungibleToken/ISupportsInterface.cs | 15 + .../NonFungibleToken/NonFungibleToken.cs | 682 ++++++++---------- 5 files changed, 425 insertions(+), 569 deletions(-) delete mode 100644 src/NonFungibleToken/NonFungibleToken/IERC165.cs create mode 100644 src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs diff --git a/src/NonFungibleToken/NonFungibleToken/IERC165.cs b/src/NonFungibleToken/NonFungibleToken/IERC165.cs deleted file mode 100644 index b591725..0000000 --- a/src/NonFungibleToken/NonFungibleToken/IERC165.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NonFungibleToken -{ - public interface IERC165 - { - /** - * @dev Checks if the smart contract includes a specific interface. - * @notice This function uses less than 30,000 gas. - * @param interfaceID The interface identifier, as specified in ERC-165. - * @return True if interfaceID is supported, false otherwise. - */ - bool SupportsInterface(uint interfaceID); - } -} diff --git a/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs index 849a22d..11c6ede 100644 --- a/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs @@ -2,158 +2,98 @@ { using Stratis.SmartContracts; - public interface INonFungibleToken : IERC165 , INonFungibleTokenReceiver + /// + /// Interface for a non-fungible token. + /// + public interface INonFungibleToken { - /** - * @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are - * created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any - * number of NFTs may be created and assigned without emitting Transfer. At the time of any - * transfer, the approved Address for that NFT (if any) is reset to none. - */ - /*LogTransfer( - Address from, - Address to, - ulong tokenId - );*/ - - /** - * @dev This emits when the approved Address for an NFT is changed or reaffirmed. The zero - * Address indicates there is no approved Address. When a Transfer event emits, this also - * indicates that the approved Address for that NFT (if any) is reset to none. - */ - /*LogApproval( - Address owner, - Address approved, - ulong tokenId - );*/ - - /** - * @dev This emits when an operator is enabled or disabled for an owner. The operator can manage - * all NFTs of the owner. - */ - /*LogApprovalForAll( - Address owner, - Address operatorAddress, - bool approved - );*/ - - /** - * @dev Transfers the ownership of an NFT from one Address to another Address. - * @notice Throws unless `msg.sender` 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 (code size > 0). If so, it calls - * `onERC721Received` on `to` and throws if the return value is not - * `bytes4(keccak256("onERC721Received(Address,ulong,bytes)"))`. - * @param from The current owner of the NFT. - * @param to The new owner. - * @param tokenId The NFT to transfer. - * @param data Additional data with no specified format, sent in call to `to`. - */ - void SafeTransferFrom( - Address from, - Address to, - ulong tokenId, - byte[] data); - - - /** - * @dev Transfers the ownership of an NFT from one Address to another Address. - * @notice This works identically to the other function with an extra data parameter, except this - * function just sets data to "" - * @param from The current owner of the NFT. - * @param to The new owner. - * @param tokenId The NFT to transfer. - */ - void SafeTransferFrom( - Address from, - Address to, - ulong tokenId - ); - - - /** - * @dev Throws unless `msg.sender` 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. - * @notice The caller is responsible to confirm that `to` is capable of receiving NFTs or else - * they mayb be permanently lost. - * @param from The current owner of the NFT. - * @param to The new owner. - * @param tokenId The NFT to transfer. - */ - void TransferFrom( - Address from, - Address to, - ulong tokenId - ); - - - /** - * @dev Set or reaffirm the approved Address for an NFT. - * @notice The zero Address indicates there is no approved Address. Throws unless `msg.sender` is - * the current NFT owner, or an authorized operator of the current owner. - * @param approved The new approved NFT controller. - * @param tokenId The NFT to approve. - */ - void Approve( - Address approved, - ulong tokenId - ); - - - /** - * @dev Enables or disables approval for a third party ("operator") to manage all of - * `msg.sender`'s assets. It also emits the ApprovalForAll event. - * @notice The contract MUST allow multiple operators per owner. - * @param operator Address to add to the set of authorized operators. - * @param approved True if the operators is approved, false to revoke approval. - */ - void SetApprovalForAll( - Address operatorAddress, - bool approved - ); - - - /** - * @dev 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. - * @param owner Address for whom to query the balance. - * @return Balance of owner. - */ - ulong BalanceOf( - Address owner - ); - - /** - * @dev Returns the Address of the owner of the NFT. NFTs assigned to zero Address are considered - * invalid, and queries about them do throw. - * @param tokenId The identifier for an NFT. - * @return Address of tokenId owner. - */ - Address OwnerOf( - ulong tokenId - ); - - /** - * @dev Get the approved Address for a single NFT. - * @notice Throws if `tokenId` is not a valid NFT. - * @param tokenId The NFT to find the approved Address for. - * @return Address that tokenId is approved for. - */ - Address GetApproved( - ulong tokenId - ); - - /** - * @dev Returns true if `operator` is an approved operator for `owner`, false otherwise. - * @param owner The Address that owns the NFTs. - * @param operator The Address that acts on behalf of the owner. - * @return True if approved for all, false otherwise. - */ - bool IsApprovedForAll( - Address owner, - Address operatorAddress - ); + /// + /// 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, ulong 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, ulong 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, ulong 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, ulong 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. + ulong 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(ulong 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(ulong 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/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs b/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs index e58b52f..3e9e5b7 100644 --- a/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs +++ b/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs @@ -1,30 +1,22 @@ namespace NonFungibleToken { using Stratis.SmartContracts; - using System; - using System.Collections.Generic; - using System.Text; + /// + /// Interface for a non-fungible token receiver. + /// public interface INonFungibleTokenReceiver { - /** - * @dev Handle the receipt of a NFT. The ERC721 smart contract calls this function on the - * recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return - * of other than the magic value MUST result in the transaction being reverted. - * Returns `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` unless throwing. - * @notice The contract address is always the message sender. A wallet/broker/auction application - * MUST implement the wallet interface if it will accept safe transfers. - * @param operator The address which called `safeTransferFrom` function. - * @param from The address which previously owned the token. - * @param tokenId The NFT identifier which is being transferred. - * @param data Additional data with no specified format. - * @return Returns `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. - */ - byte[] onERC721Received( - Address operatorAddress, - Address fromAddress, - ulong tokenId, - byte[] data - ); + /// + /// 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, ulong tokenId, byte[] data); } } diff --git a/src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs b/src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs new file mode 100644 index 0000000..43340b5 --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs @@ -0,0 +1,15 @@ +namespace NonFungibleToken +{ + /// + /// 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/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs index 890b747..2624981 100644 --- a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs @@ -3,206 +3,244 @@ using Stratis.SmartContracts; using System; - public class NonFungibleToken : SmartContract, INonFungibleToken + /// + /// A non fungible token contract. + /// + public class NonFungibleToken : SmartContract, INonFungibleToken, ISupportsInterface, INonFungibleTokenReceiver { - /** - * @dev Magic value of a smart contract that can recieve NFT. - * Equal to: bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")). - */ - // todo: how do we calculate if this is still correct after we've implemented the NonFungibleTokenReceiver? - internal static readonly byte[] MAGIC_ON_ERC721_RECEIVED = new byte[] { 0x15, 0x0b, 0x7a, 0x02 }; + public struct TransferLog + { + [Index] + public Address From; + [Index] + public Address To; + [Index] + public ulong TokenId; + } + + public struct ApprovalLog + { + [Index] + public Address Owner; + [Index] + public Address Approved; + [Index] + public ulong TokenId; + } + + public struct ApprovalForAllLog + { + [Index] + public Address Owner; + [Index] + public Address Operator; + + public bool Approved; + } + + /// + /// Get a value indicacting if the interface is supported. + /// + /// The id of the interface to support. + /// A value indicating if the interface is supported. + private bool GetSupportedInterfaces(uint interfaceId) + { + return this.PersistentState.GetBool($"SupportedInterface:{interfaceId}"); + } + + /// + /// Sets the supported interface value. + /// + /// The interface id. + /// A value indicating if the interface id is supported. + private void SetSupportedInterfaces(uint interfaceId, bool value) + { + this.PersistentState.SetBool($"SupportedInterface:{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(ulong id) { return $"IdToOwner:{id}"; } - /** - * @dev A mapping from NFT ID to the address that owns it. - */ + /// + /// Gets the address of the owner of the NFT ID. + /// + /// The ID of the NFT + ///The owner address. private Address GetIdToOwner(ulong id) { return this.PersistentState.GetAddress(GetIdToOwnerKey(id)); } + /// + /// Sets the owner to the NFT ID. + /// + /// The ID of the NFT + /// The address of the owner. private void SetIdToOwner(ulong id, Address value) { this.PersistentState.SetAddress(GetIdToOwnerKey(id), value); } - /** - * @dev Mapping from NFT ID to approved address. - */ + /// + /// 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(ulong id) { return $"IdToApproval:{id}"; } + /// + /// Getting from NFT ID the approval address. + /// + /// The ID of the NFT + /// Address of the approval. private Address GetIdToApproval(ulong id) { return this.PersistentState.GetAddress(GetIdToApprovalKey(id)); } + /// + /// Setting to NFT ID to approval address. + /// + /// The ID of the NFT + /// The address of the approval. private void SetIdToApproval(ulong id, Address value) { this.PersistentState.SetAddress(GetIdToApprovalKey(id), value); } - - /** - * @dev Mapping from owner address to count of his tokens. - */ + + /// + /// Gets the amount of non fungible tokens the owner has. + /// + /// The address of the owner. + /// The amount of non fungible tokens. private ulong GetOwnerToNFTokenCount(Address address) { return this.PersistentState.GetUInt64($"OwnerToNFTokenCount:{address}"); } + /// + /// Sets the owner count of this non fungible tokens. + /// + /// The address of the owner. + /// The amount of tokens. private void SetOwnerToNFTokenCount(Address address, ulong value) { this.PersistentState.SetUInt64($"OwnerToNFTokenCount:{address}", value); } - - /** - * @dev Mapping from owner address to mapping of operator addresses. - */ - // todo: review if this is the best way to solve it. + + /// + /// 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 GetOwnerToOperators(Address owner, Address operatorAddress) { return this.PersistentState.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 SetOwnerToOperators(Address owner, Address operatorAddress, bool value) { this.PersistentState.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); } - public struct TransferLog - { - [Index] - public Address From; - [Index] - public Address To; - [Index] - public ulong TokenId; - } - - public struct ApprovalLog - { - [Index] - public Address Owner; - [Index] - public Address Approved; - [Index] - public ulong TokenId; - } - - public struct ApprovalForAllLog - { - [Index] - public Address Owner; - [Index] - public Address Operator; - - public bool Approved; - } - - /** - * @dev Guarantees that the msg.sender is an owner or operator of the given NFT. - * @param _tokenId ID of the NFT to validate. - */ - private void CanOperate(ulong tokenId) + /// + /// Constructor. Initializes the supported interfaces. + /// + /// The smart contract state. + public NonFungibleToken(ISmartContractState state) : base(state) { - Address tokenOwner = GetIdToOwner(tokenId); - Assert(tokenOwner == this.Message.Sender || GetOwnerToOperators(tokenOwner, this.Message.Sender)); + // todo: discuss callback handling and supported interface numbering with community. + this.SetSupportedInterfaces((uint)0x00000001, true); // (ERC165) - ISupportsInterface + this.SetSupportedInterfaces((uint)0x00000002, true); // (ERC721) - INonFungibleToken + this.SetSupportedInterfaces((uint)0x00000003, true); // (-) - INonFungibleTokenReceiver } - /** - * @dev Guarantees that the msg.sender is allowed to transfer NFT. - * @param _tokenId ID of the NFT to transfer. - */ - private void CanTransfer(ulong tokenId) + /// + /// 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) { - Address tokenOwner = GetIdToOwner(tokenId); - Assert( - tokenOwner == this.Message.Sender - || GetIdToApproval(tokenId) == Message.Sender - || GetOwnerToOperators(tokenOwner, Message.Sender) - ); + return GetSupportedInterfaces(interfaceID); } - /** - * @dev Guarantees that _tokenId is a valid Token. - * @param _tokenId ID of the NFT to validate. - */ - private void ValidNFToken(ulong tokenId) - { - Assert(GetIdToOwner(tokenId) != Address.Zero); + /// + /// 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. + /// + /// Up to the contract to implement. This contract just returns true. + /// 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. + public bool OnNonFungibleTokenReceived(Address operatorAddress, Address fromAddress, ulong tokenId, byte[] data) + { + return true; } - /** - * @dev Contract constructor. - */ - public NonFungibleToken(ISmartContractState state) : base(state) - { - this.SetSupportedInterfaces(0x01ffc9a7, true); // ERC165 - this.SetSupportedInterfaces(0x80ac58cd, true); // ERC721 - } - - /** - * @dev Transfers the ownership of an NFT from one address to another address. This function can - * be changed to payable. - * @notice Throws unless `msg.sender` 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 (code size > 0). If so, it calls - * `onERC721Received` on `_to` and throws if the return value is not - * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`. - * @param _from The current owner of the NFT. - * @param _to The new owner. - * @param _tokenId The NFT to transfer. - * @param _data Additional data with no specified format, sent in call to `_to`. - */ - public void SafeTransferFrom( - Address from, - Address to, - ulong tokenId, - byte[] data) + /// + /// 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, ulong tokenId, byte[] data) { SafeTransferFromInternal(from, to, tokenId, data); } - /** - * @dev Transfers the ownership of an NFT from one address to another address. This function can - * be changed to payable. - * @notice This works identically to the other function with an extra data parameter, except this - * function just sets data to "" - * @param _from The current owner of the NFT. - * @param _to The new owner. - * @param _tokenId The NFT to transfer. - */ - public void SafeTransferFrom( - Address from, - Address to, - ulong tokenId - ) + /// + /// 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, ulong tokenId) { SafeTransferFromInternal(from, to, tokenId, new byte[0]); } - /** - * @dev Throws unless `msg.sender` 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. This function can be changed to payable. - * @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else - * they maybe be permanently lost. - * @param _from The current owner of the NFT. - * @param _to The new owner. - * @param _tokenId The NFT to transfer. - */ - public void TransferFrom( - Address from, - Address to, - ulong 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. + public void TransferFrom(Address from, Address to, ulong tokenId) { CanTransfer(tokenId); ValidNFToken(tokenId); @@ -214,17 +252,16 @@ ulong tokenId TransferInternal(to, tokenId); } - /** - * @dev Set or reaffirm the approved address for an NFT. This function can be changed to payable. - * @notice The zero address indicates there is no approved address. Throws unless `msg.sender` is - * the current NFT owner, or an authorized operator of the current owner. - * @param _approved Address to be approved for the given NFT ID. - * @param _tokenId ID of the token to be approved. - */ - public void Approve( - Address approved, - ulong 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, ulong tokenId) { CanOperate(tokenId); ValidNFToken(tokenId); @@ -236,90 +273,74 @@ ulong tokenId LogApproval(tokenOwner, approved, tokenId); } - /** - * @dev Enables or disables approval for a third party ("operator") to manage all of - * `msg.sender`'s assets. It also emits the ApprovalForAll event. - * @notice This works even if sender doesn't own any tokens at the time. - * @param _operator Address to add to the set of authorized operators. - * @param _approved True if the operators is approved, false to revoke approval. - */ - public void SetApprovalForAll( - Address operatorAddress, - bool approved - ) + /// + /// 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) { SetOwnerToOperators(this.Message.Sender, operatorAddress, approved); LogApprovalForAll(this.Message.Sender, operatorAddress, approved); } - /** - * @dev 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. - * @param _owner Address for whom to query the balance. - * @return Balance of _owner. - */ - public ulong BalanceOf( - Address owner - ) + /// + /// 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 ulong BalanceOf(Address owner) { Assert(owner != Address.Zero); return GetOwnerToNFTokenCount(owner); } - /** - * @dev Returns the address of the owner of the NFT. NFTs assigned to zero address are considered - * invalid, and queries about them do throw. - * @param _tokenId The identifier for an NFT. - * @return Address of _tokenId owner. - */ - public Address OwnerOf( - ulong tokenId - ) + /// + /// 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(ulong tokenId) { Address owner = GetIdToOwner(tokenId); Assert(owner != Address.Zero); return owner; } - /** - * @dev Get the approved address for a single NFT. - * @notice Throws if `_tokenId` is not a valid NFT. - * @param _tokenId ID of the NFT to query the approval of. - * @return Address that _tokenId is approved for. - */ - public Address GetApproved( - ulong 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. + public Address GetApproved(ulong tokenId) { ValidNFToken(tokenId); return GetIdToApproval(tokenId); } - /** - * @dev Checks if `_operator` is an approved operator for `_owner`. - * @param _owner The address that owns the NFTs. - * @param _operator The address that acts on behalf of the owner. - * @return True if approved for all, false otherwise. - */ - public bool IsApprovedForAll( - Address owner, - Address operatorAddress - ) + /// + /// 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 GetOwnerToOperators(owner, operatorAddress); } - /** - * @dev Actually preforms the transfer. - * @notice Does NO checks. - * @param _to Address of a new owner. - * @param _tokenId The NFT that is being transferred. - */ - private void TransferInternal( - Address to, - ulong tokenId - ) + /// + /// Actually preforms the transfer. + /// + /// Does NO checks. + /// Address of a new owner. + /// The NFT that is being transferred. + private void TransferInternal(Address to, ulong tokenId) { Address from = GetIdToOwner(tokenId); ClearApproval(tokenId); @@ -330,75 +351,26 @@ ulong tokenId LogTransfer(from, to, tokenId); } - /** - * @dev Mints a new NFT. - * @notice This is an internal function which should be called from user-implemented external - * mint function. Its purpose is to show and properly initialize data structures when using this - * implementation. - * @param _to The address that will own the minted NFT. - * @param _tokenId of the NFT to be minted by the msg.sender. - */ - private void Mint( - Address to, - ulong tokenId - ) - { - // todo: no references and not on interface? what to do? - Assert(to != Address.Zero); - Assert(GetIdToOwner(tokenId) == Address.Zero); - - AddNFToken(to, tokenId); - - LogTransfer(Address.Zero, to, tokenId); - } - - /** - * @dev Burns a NFT. - * @notice This is an internal function which should be called from user-implemented external burn - * function. Its purpose is to show and properly initialize data structures when using this - * implementation. Also, note that this burn implementation allows the minter to re-mint a burned - * NFT. - * @param _tokenId ID of the NFT to be burned. - */ - private void Burn( - ulong tokenId - ) - { - // todo: no references and not on interface? what to do? - ValidNFToken(tokenId); - - Address tokenOwner = GetIdToOwner(tokenId); - ClearApproval(tokenId); - RemoveNFToken(tokenOwner, tokenId); - LogTransfer(tokenOwner, Address.Zero, tokenId); - } - - /** - * @dev Removes a NFT from owner. - * @notice Use and override this function with caution. Wrong usage can have serious consequences. - * @param _from Address from wich we want to remove the NFT. - * @param _tokenId Which NFT we want to remove. - */ - private void RemoveNFToken( - Address from, - ulong 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 RemoveNFToken(Address from, ulong tokenId) { Assert(GetIdToOwner(tokenId) == from); - SetOwnerToNFTokenCount(from, GetOwnerToNFTokenCount(from) - 1); + SetOwnerToNFTokenCount(from, checked(GetOwnerToNFTokenCount(from) - 1)); this.PersistentState.Clear(GetIdToOwnerKey(tokenId)); } - /** - * @dev Assignes a new NFT to owner. - * @notice Use and override this function with caution. Wrong usage can have serious consequences. - * @param _to Address to wich we want to add the NFT. - * @param _tokenId Which NFT we want to add. - */ - public void AddNFToken( - Address to, - ulong tokenId - ) + /// + /// 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 AddNFToken(Address to, ulong tokenId) { Assert(GetIdToOwner(tokenId) == Address.Zero); @@ -407,32 +379,14 @@ ulong tokenId SetOwnerToNFTokenCount(to, checked(currentTokenAmount + 1)); } - /** - * @dev Helper function that gets NFT count of owner. This is needed for overriding in enumerable - * extension to remove double storage (gas optimization) of owner nft count. - * @param _owner Address for whom to query the count. - * @return Number of _owner NFTs. - */ - /*private ulong GetOwnerNFTCount( - Address owner - ) - { - return GetOwnerToNFTokenCount(owner); - }*/ - - /** - * @dev Actually perform the safeTransferFrom. - * @param _from The current owner of the NFT. - * @param _to The new owner. - * @param _tokenId The NFT to transfer. - * @param _data Additional data with no specified format, sent in call to `_to`. - */ - private void SafeTransferFromInternal( - Address from, - Address to, - ulong tokenId, - byte[] data - ) + /// + /// 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, ulong tokenId, byte[] data) { CanTransfer(tokenId); ValidNFToken(tokenId); @@ -445,19 +399,16 @@ byte[] data if (this.PersistentState.IsContract(to)) { - ITransferResult result = this.Call(to, 0, "onERC721Received", new object[] { this.Message.Sender, from, tokenId, data }, 0); - var retval = result.ReturnValue as byte[]; - Assert(MagicBytesEqual(retval, MAGIC_ON_ERC721_RECEIVED)); + ITransferResult result = this.Call(to, 0, "OnNonFungibleTokenReceived", new object[] { this.Message.Sender, from, tokenId, data }, 0); + Assert(Convert.ToBoolean(result.ReturnValue)); } } - /** - * @dev Clears the current approval of a given NFT ID. - * @param _tokenId ID of the NFT to be transferred. - */ - private void ClearApproval( - ulong tokenId - ) + /// + /// Clears the current approval of a given NFT ID. + /// + /// ID of the NFT to be transferred + private void ClearApproval(ulong tokenId) { if (GetIdToApproval(tokenId) != Address.Zero) { @@ -465,101 +416,76 @@ ulong tokenId } } - /** - * @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are - * created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any - * number of NFTs may be created and assigned without emitting Transfer. At the time of any - * transfer, the approved Address for that NFT (if any) is reset to none. - */ - private void LogTransfer( - Address from, - Address to, - ulong tokenId - ) + /// + /// 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, ulong tokenId) { Log(new TransferLog() { From = from, To = to, TokenId = tokenId }); } - /** - * @dev This emits when the approved Address for an NFT is changed or reaffirmed. The zero - * Address indicates there is no approved Address. When a Transfer event emits, this also - * indicates that the approved Address for that NFT (if any) is reset to none. - */ - private void LogApproval( - Address owner, - Address approved, - ulong 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, ulong tokenId) { Log(new ApprovalLog() { Owner = owner, Approved = approved, TokenId = tokenId }); } - /** - * @dev This emits when an operator is enabled or disabled for an owner. The operator can manage - * all NFTs of the owner. - */ - private void LogApprovalForAll( - Address owner, - Address operatorAddress, - bool approved - ) + /// + /// 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 }); } - private bool GetSupportedInterfaces(uint interfaceId) - { - return this.PersistentState.GetBool($"SupportedInterface:{interfaceId}"); - } - - private void SetSupportedInterfaces(uint interfaceId, bool value) - { - this.PersistentState.SetBool($"SupportedInterface:{interfaceId}", value); - } - /** - * @dev Function to check which interfaces are suported by this contract. - * @param _interfaceID Id of the interface. - * @return True if _interfaceID is supported, false otherwise. - */ - public bool SupportsInterface(uint interfaceID) + /// + /// Guarantees that the is an owner or operator of the given NFT. + /// + /// ID of the NFT to validate. + private void CanOperate(ulong tokenId) { - // todo: go back to byte4? - return GetSupportedInterfaces(interfaceID); + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == this.Message.Sender || GetOwnerToOperators(tokenOwner, this.Message.Sender)); } - // todo: rename to onNonFungibleTokenReceived? - public byte[] onERC721Received(Address operatorAddress, Address fromAddress, ulong tokenId, byte[] data) + /// + /// Guarantees that the msg.sender is allowed to transfer NFT. + /// + /// ID of the NFT to transfer. + private void CanTransfer(ulong tokenId) { - // todo: update documentation. - string stringToHash = $"onERC721Received{operatorAddress},{fromAddress},{tokenId},{BitConverter.ToString(data)}"; - byte[] bytesToHash = Array.ConvertAll(stringToHash.ToCharArray(), s => Convert.ToByte(s)); - var keccakHash = this.Keccak256(bytesToHash); - var magicBytes = new byte[] { keccakHash[0], keccakHash[1], keccakHash[2], keccakHash[3] }; - return magicBytes; + Address tokenOwner = GetIdToOwner(tokenId); + Assert( + tokenOwner == this.Message.Sender + || GetIdToApproval(tokenId) == Message.Sender + || GetOwnerToOperators(tokenOwner, Message.Sender) + ); } - private bool MagicBytesEqual(byte[] b1, byte[] b2) + /// + /// Guarantees that tokenId is a valid Token. + /// + /// ID of the NFT to validate. + private void ValidNFToken(ulong tokenId) { - if (b1 == null || b2 == null) - { - return false; - } - - if (b1.Length != 4 || b2.Length != 4) - { - return false; - } - - for (var i = 0; i < 4; i++) - { - if (!b1[i].Equals(b2[i])) - { - return false; - } - } - - return true; + Assert(GetIdToOwner(tokenId) != Address.Zero); } } } From 09d32619d83eab1cf649817c917c7043887e21c1 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Mon, 2 Sep 2019 21:44:19 +0200 Subject: [PATCH 03/10] Format document of interface, label receiver interface. --- src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs | 6 +++--- src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs index 11c6ede..ffa438a 100644 --- a/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs @@ -20,7 +20,7 @@ public interface INonFungibleToken /// The new owner. /// The NFT to transfer. /// Additional data with no specified format, sent in call to 'to'. - void SafeTransferFrom( Address from, Address to, ulong tokenId, byte[] data); + void SafeTransferFrom(Address from, Address to, ulong tokenId, byte[] data); /// /// Transfers the ownership of an NFT from one address to another address. This function can @@ -31,7 +31,7 @@ public interface INonFungibleToken /// The current owner of the NFT. /// The new owner. /// The NFT to transfer. - void SafeTransferFrom( Address from, Address to, ulong tokenId); + void SafeTransferFrom(Address from, Address to, ulong tokenId); /// /// Throws unless is the current owner, an authorized operator, or the approved @@ -94,6 +94,6 @@ public interface INonFungibleToken /// 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); + bool IsApprovedForAll(Address owner, Address operatorAddress); } } diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs index 2624981..9046a9b 100644 --- a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs @@ -168,8 +168,8 @@ public NonFungibleToken(ISmartContractState state) : base(state) { // todo: discuss callback handling and supported interface numbering with community. this.SetSupportedInterfaces((uint)0x00000001, true); // (ERC165) - ISupportsInterface - this.SetSupportedInterfaces((uint)0x00000002, true); // (ERC721) - INonFungibleToken - this.SetSupportedInterfaces((uint)0x00000003, true); // (-) - INonFungibleTokenReceiver + this.SetSupportedInterfaces((uint)0x00000002, true); // (ERC721) - INonFungibleToken, + this.SetSupportedInterfaces((uint)0x00000003, true); // (ERC721) - INonFungibleTokenReceiver } /// From 46c0290335806cc3bb7358a4a78e35ba2124b28f Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Tue, 3 Sep 2019 20:50:02 +0200 Subject: [PATCH 04/10] Tests for NonFungibleToken --- .../NonFungibleTokenTests.cs | 371 ++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs new file mode 100644 index 0000000..f87c6fc --- /dev/null +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs @@ -0,0 +1,371 @@ +namespace NonFungibleToken.Tests +{ + using System; + using System.Collections.Generic; + using Moq; + using Stratis.SmartContracts; + using Stratis.SmartContracts.CLR; + using Xunit; + + public class NonFungibleTokenTests + { + private Mock smartContractStateMock; + private Mock contractLoggerMock; + private Mock persistentStateMock; + private Dictionary supportedInterfaces; + private Dictionary idToOwner; + private Dictionary idToApproval; + private Dictionary ownerToOperator; + private Dictionary ownerToNFTokenCount; + + public NonFungibleTokenTests() + { + this.contractLoggerMock = new Mock(); + this.persistentStateMock = new Mock(); + this.smartContractStateMock = new Mock(); + this.smartContractStateMock.Setup(s => s.PersistentState).Returns(this.persistentStateMock.Object); + this.smartContractStateMock.Setup(s => s.ContractLogger).Returns(this.contractLoggerMock.Object); + + this.supportedInterfaces = new Dictionary(); + this.idToOwner = new Dictionary(); + this.idToApproval = new Dictionary(); + this.ownerToOperator = new Dictionary(); + this.ownerToNFTokenCount = new Dictionary(); + + this.SetupPersistentState(); + } + + [Fact] + public void Constructor_Sets_SupportedInterfaces() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Equal(3, this.supportedInterfaces.Count); + Assert.True(this.supportedInterfaces["SupportedInterface:1"]); + Assert.True(this.supportedInterfaces["SupportedInterface:2"]); + Assert.True(this.supportedInterfaces["SupportedInterface:3"]); + } + + [Fact] + public void SupportsInterface_InterfaceSupported_ReturnsTrue() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.SupportsInterface(3); + + Assert.True(result); + } + + [Fact] + public void SupportsInterface_InterfaceSetToFalseSupported_ReturnsFalse() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + this.supportedInterfaces["SupportedInterface:3"] = false; + + var result = nonFungibleToken.SupportsInterface(3); + + Assert.False(result); + } + + [Fact] + public void SupportsInterface_InterfaceNotSupported_ReturnsFalse() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.SupportsInterface(4); + + Assert.False(result); + } + + [Fact] + public void OnNonFungibleTokenReceived_ReturnsTrue() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.OnNonFungibleTokenReceived(Address.Zero, Address.Zero, 1, new byte[0]); + + Assert.True(result); + } + + [Fact] + public void GetApproved_NotValidNFToken_OwnerAddressZero_ThrowsException() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.GetApproved(1)); + } + + [Fact] + public void GetApproved_ApprovalNotInStorage_ReturnsZeroAddress() + { + this.idToOwner.Add("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); + this.idToApproval.Clear(); + + var nonFungibleToken = this.CreateNonFungibleToken(); + var result = nonFungibleToken.GetApproved(1); + + Assert.Equal(Address.Zero, result); + } + + [Fact] + public void GetApproved_ApprovalInStorage_ReturnsAddress() + { + var approvalAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + var result = nonFungibleToken.GetApproved(1); + + Assert.Equal(approvalAddress, result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorInStateAsTrue_ReturnsTrue() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", true); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.True(result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorInStateAsFalse_ReturnsFalse() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", false); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.False(result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorNotInState_ReturnsFalse() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.ownerToOperator.Clear(); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.False(result); + } + + [Fact] + public void OwnerOf_IdToOwnerNotInStorage_ThrowsException() + { + this.idToOwner.Clear(); + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.OwnerOf(1)); + } + + [Fact] + public void OwnerOf_NFTokenMappedToAddressZero_ThrowsException() + { + this.idToOwner.Add("IdToOwner:1", Address.Zero); + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.OwnerOf(1)); + } + + [Fact] + public void OwnerOf_NFTokenExistsWithOwner_ReturnsOwnerAddress() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.OwnerOf(1); + + Assert.Equal(ownerAddress, result); + } + + [Fact] + public void BalanceOf_OwnerZero_ThrowsException() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => { nonFungibleToken.BalanceOf(Address.Zero); }); + } + + [Fact] + public void BalanceOf_NftTokenCountNotInStorage_ReturnsZero() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.ownerToNFTokenCount.Clear(); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.BalanceOf(ownerAddress); + + Assert.Equal((ulong)0, result); + } + + [Fact] + public void BalanceOf_OwnerNftTokenCountInStorage_ReturnsTokenCount() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 15); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.BalanceOf(ownerAddress); + + Assert.Equal((ulong)15, result); + } + + private NonFungibleToken CreateNonFungibleToken() + { + return new NonFungibleToken(this.smartContractStateMock.Object); + } + + private void SetupPersistentState() + { + this.SetupSupportedInterfaces(); + this.SetupIdToOwner(); + this.SetupIdToApproval(); + this.SetupOwnerToOperators(); + this.SetupOwnerToNFTokenCount(); + } + + private void SetupOwnerToNFTokenCount() + { + this.persistentStateMock.Setup(p => p.SetUInt64(It.Is(s => s.StartsWith("OwnerToNFTokenCount:", StringComparison.Ordinal)), It.IsAny())) + .Callback((key, value) => + { + if (this.ownerToNFTokenCount.ContainsKey(key)) + { + this.ownerToNFTokenCount[key] = value; + } + else + { + this.ownerToNFTokenCount.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetUInt64(It.Is(s => s.StartsWith("OwnerToNFTokenCount:")))) + .Returns((key) => + { + if (this.ownerToNFTokenCount.ContainsKey(key)) + { + return this.ownerToNFTokenCount[key]; + } + + return default(ulong); + }); + } + + private void SetupOwnerToOperators() + { + this.persistentStateMock.Setup(p => p.SetBool(It.Is(s => s.StartsWith("OwnerToOperator:", StringComparison.Ordinal)), It.IsAny())) + .Callback((key, value) => + { + if (this.ownerToOperator.ContainsKey(key)) + { + this.ownerToOperator[key] = value; + } + else + { + this.ownerToOperator.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetBool(It.Is(s => s.StartsWith("OwnerToOperator:")))) + .Returns((key) => + { + if (this.ownerToOperator.ContainsKey(key)) + { + return this.ownerToOperator[key]; + } + + return default(bool); + }); + } + + private void SetupIdToApproval() + { + this.persistentStateMock.Setup(p => p.SetAddress(It.Is(s => s.StartsWith("IdToApproval:", StringComparison.Ordinal)), It.IsAny
())) + .Callback((key, value) => + { + if (this.idToApproval.ContainsKey(key)) + { + this.idToApproval[key] = value; + } + else + { + this.idToApproval.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetAddress(It.Is(s => s.StartsWith("IdToApproval:")))) + .Returns((key) => + { + if (this.idToApproval.ContainsKey(key)) + { + return this.idToApproval[key]; + } + + return Address.Zero; + }); + } + + private void SetupIdToOwner() + { + this.persistentStateMock.Setup(p => p.SetAddress(It.Is(s => s.StartsWith("IdToOwner:", StringComparison.Ordinal)), It.IsAny
())) + .Callback((key, value) => + { + if (this.idToOwner.ContainsKey(key)) + { + this.idToOwner[key] = value; + } + else + { + this.idToOwner.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetAddress(It.Is(s => s.StartsWith("IdToOwner:")))) + .Returns((key) => + { + if (this.idToOwner.ContainsKey(key)) + { + return this.idToOwner[key]; + } + + return Address.Zero; + }); + } + + private void SetupSupportedInterfaces() + { + this.persistentStateMock.Setup(p => p.SetBool(It.Is(s => s.StartsWith("SupportedInterface:", StringComparison.Ordinal)), It.IsAny())) + .Callback((key, value) => + { + if (this.supportedInterfaces.ContainsKey(key)) + { + this.supportedInterfaces[key] = value; + } + else + { + this.supportedInterfaces.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetBool(It.Is(s => s.StartsWith("SupportedInterface:")))) + .Returns((key) => + { + if (this.supportedInterfaces.ContainsKey(key)) + { + return this.supportedInterfaces[key]; + } + + return default(bool); + }); + } + } +} From 6b0ca086bd9877fa5837c02c1bc052fac81102b0 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Wed, 4 Sep 2019 20:54:33 +0200 Subject: [PATCH 05/10] Tests for nonfungibletoken approve and setapprovalforall methods --- .../NonFungibleTokenTests.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs index f87c6fc..9b30983 100644 --- a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs @@ -224,6 +224,104 @@ public void BalanceOf_OwnerNftTokenCountInStorage_ReturnsTokenCount() Assert.Equal((ulong)15, result); } + [Fact] + public void SetApprovalForAll_SetsMessageSender_ToOperatorApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var nonFungibleToken = this.CreateNonFungibleToken(); + + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + nonFungibleToken.SetApprovalForAll(operatorAddress, true); + + Assert.NotEmpty(this.ownerToOperator); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalForAllLog { Owner = ownerAddress, Operator = operatorAddress, Approved = true })); + } + + [Fact] + public void Approve_TokenOwnerNotMessageSenderOrOperator_ThrowsException() + { + this.idToOwner.Clear(); + this.ownerToOperator.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); + } + + [Fact] + public void Approve_ValidApproval_SwitchesOwnerToApprovedForNFToken() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.Approve(someAddress, 1); + + Assert.NotEmpty(this.idToApproval); + Assert.Equal(this.idToApproval["IdToApproval:1"], someAddress); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); + } + + [Fact] + public void Approve_NTFokenOwnerSameAsMessageSender_ThrowsException() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(ownerAddress, 1)); + } + + [Fact] + public void Approve_ValidApproval_ByApprovedOperator_SwitchesOwnerToApprovedForNFToken() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.Approve(someAddress, 1); + + Assert.NotEmpty(this.idToApproval); + Assert.Equal(this.idToApproval["IdToApproval:1"], someAddress); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); + } + + [Fact] + public void Approve_InvalidNFToken_ThrowsException() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = Address.Zero; + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); + } + private NonFungibleToken CreateNonFungibleToken() { return new NonFungibleToken(this.smartContractStateMock.Object); From f87ae4149fe856f0edd4f038b7d15388341e6c83 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Thu, 5 Sep 2019 20:34:04 +0200 Subject: [PATCH 06/10] Add remaining tests for nonfungibletoken --- .../NonFungibleTokenTests.cs | 796 ++++++++++++++++++ .../NonFungibleToken/NonFungibleToken.cs | 12 +- 2 files changed, 802 insertions(+), 6 deletions(-) diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs index 9b30983..138f340 100644 --- a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs @@ -17,14 +17,17 @@ public class NonFungibleTokenTests private Dictionary idToApproval; private Dictionary ownerToOperator; private Dictionary ownerToNFTokenCount; + private Mock internalTransactionExecutorMock; public NonFungibleTokenTests() { this.contractLoggerMock = new Mock(); this.persistentStateMock = new Mock(); this.smartContractStateMock = new Mock(); + this.internalTransactionExecutorMock = new Mock(); this.smartContractStateMock.Setup(s => s.PersistentState).Returns(this.persistentStateMock.Object); this.smartContractStateMock.Setup(s => s.ContractLogger).Returns(this.contractLoggerMock.Object); + this.smartContractStateMock.Setup(x => x.InternalTransactionExecutor).Returns(this.internalTransactionExecutorMock.Object); this.supportedInterfaces = new Dictionary(); this.idToOwner = new Dictionary(); @@ -322,6 +325,787 @@ public void Approve_InvalidNFToken_ThrowsException() Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); } + [Fact] + public void TransferFrom_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void TransferFrom_ValidTokenTransfer_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_NFTokenOwnerZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(Address.Zero, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_TokenDoesNotBelongToFrom_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var notOwningAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(notOwningAddress, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { approvalAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { operatorAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_NFTokenOwnerZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(notOwningAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ValidTokenTransfer_ToContractReturnsFalse_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(false)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTruthyObject_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(1)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { approvalAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { operatorAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(false)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTruthyObject_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(1)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, Address.Zero, 1, new byte[1] { 0xff })); + } + private NonFungibleToken CreateNonFungibleToken() { return new NonFungibleToken(this.smartContractStateMock.Object); @@ -412,6 +1196,12 @@ private void SetupIdToApproval() return Address.Zero; }); + + this.persistentStateMock.Setup(p => p.Clear(It.Is(s => s.StartsWith("IdToApproval:")))) + .Callback((key) => + { + this.idToApproval.Remove(key); + }); } private void SetupIdToOwner() @@ -438,6 +1228,12 @@ private void SetupIdToOwner() return Address.Zero; }); + + this.persistentStateMock.Setup(p => p.Clear(It.Is(s => s.StartsWith("IdToOwner:")))) + .Callback((key) => + { + this.idToOwner.Remove(key); + }); } private void SetupSupportedInterfaces() diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs index 9046a9b..bd2a6ff 100644 --- a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs @@ -144,7 +144,7 @@ private void SetOwnerToNFTokenCount(Address address, ulong value) /// 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 GetOwnerToOperators(Address owner, Address operatorAddress) + private bool GetOwnerToOperator(Address owner, Address operatorAddress) { return this.PersistentState.GetBool($"OwnerToOperator:{owner}:{operatorAddress}"); } @@ -155,7 +155,7 @@ private bool GetOwnerToOperators(Address owner, Address operatorAddress) /// The owner address of the NFT. /// >Address to add to the set of authorized operators. /// The permission value. - private void SetOwnerToOperators(Address owner, Address operatorAddress, bool value) + private void SetOwnerToOperator(Address owner, Address operatorAddress, bool value) { this.PersistentState.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); } @@ -282,7 +282,7 @@ public void Approve(Address approved, ulong tokenId) /// True if the operators is approved, false to revoke approval. public void SetApprovalForAll(Address operatorAddress, bool approved) { - SetOwnerToOperators(this.Message.Sender, operatorAddress, approved); + SetOwnerToOperator(this.Message.Sender, operatorAddress, approved); LogApprovalForAll(this.Message.Sender, operatorAddress, approved); } @@ -331,7 +331,7 @@ public Address GetApproved(ulong tokenId) /// True if approved for all, false otherwise. public bool IsApprovedForAll(Address owner, Address operatorAddress) { - return GetOwnerToOperators(owner, operatorAddress); + return GetOwnerToOperator(owner, operatorAddress); } /// @@ -462,7 +462,7 @@ private void LogApprovalForAll(Address owner, Address operatorAddress, bool appr private void CanOperate(ulong tokenId) { Address tokenOwner = GetIdToOwner(tokenId); - Assert(tokenOwner == this.Message.Sender || GetOwnerToOperators(tokenOwner, this.Message.Sender)); + Assert(tokenOwner == this.Message.Sender || GetOwnerToOperator(tokenOwner, this.Message.Sender)); } /// @@ -475,7 +475,7 @@ private void CanTransfer(ulong tokenId) Assert( tokenOwner == this.Message.Sender || GetIdToApproval(tokenId) == Message.Sender - || GetOwnerToOperators(tokenOwner, Message.Sender) + || GetOwnerToOperator(tokenOwner, Message.Sender) ); } From 627db08ed890c73cf7e5be8ae46c697f0f94d349 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Mon, 16 Sep 2019 19:32:26 +0200 Subject: [PATCH 07/10] Remove INonfungibleToken interface as method implementation is not useful. --- .../NonFungibleTokenTests.cs | 16 +++------------ .../NonFungibleToken/NonFungibleToken.cs | 20 ++----------------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs index 138f340..1966bda 100644 --- a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs @@ -46,7 +46,7 @@ public void Constructor_Sets_SupportedInterfaces() Assert.Equal(3, this.supportedInterfaces.Count); Assert.True(this.supportedInterfaces["SupportedInterface:1"]); Assert.True(this.supportedInterfaces["SupportedInterface:2"]); - Assert.True(this.supportedInterfaces["SupportedInterface:3"]); + Assert.False(this.supportedInterfaces["SupportedInterface:3"]); } [Fact] @@ -54,7 +54,7 @@ public void SupportsInterface_InterfaceSupported_ReturnsTrue() { var nonFungibleToken = this.CreateNonFungibleToken(); - var result = nonFungibleToken.SupportsInterface(3); + var result = nonFungibleToken.SupportsInterface(2); Assert.True(result); } @@ -63,7 +63,7 @@ public void SupportsInterface_InterfaceSupported_ReturnsTrue() public void SupportsInterface_InterfaceSetToFalseSupported_ReturnsFalse() { var nonFungibleToken = this.CreateNonFungibleToken(); - this.supportedInterfaces["SupportedInterface:3"] = false; + this.supportedInterfaces["SupportedInterface:2"] = false; var result = nonFungibleToken.SupportsInterface(3); @@ -80,16 +80,6 @@ public void SupportsInterface_InterfaceNotSupported_ReturnsFalse() Assert.False(result); } - [Fact] - public void OnNonFungibleTokenReceived_ReturnsTrue() - { - var nonFungibleToken = this.CreateNonFungibleToken(); - - var result = nonFungibleToken.OnNonFungibleTokenReceived(Address.Zero, Address.Zero, 1, new byte[0]); - - Assert.True(result); - } - [Fact] public void GetApproved_NotValidNFToken_OwnerAddressZero_ThrowsException() { diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs index bd2a6ff..e50feb7 100644 --- a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs @@ -6,7 +6,7 @@ /// /// A non fungible token contract. /// - public class NonFungibleToken : SmartContract, INonFungibleToken, ISupportsInterface, INonFungibleTokenReceiver + public class NonFungibleToken : SmartContract, INonFungibleToken, ISupportsInterface { public struct TransferLog { @@ -169,7 +169,7 @@ public NonFungibleToken(ISmartContractState state) : base(state) // todo: discuss callback handling and supported interface numbering with community. this.SetSupportedInterfaces((uint)0x00000001, true); // (ERC165) - ISupportsInterface this.SetSupportedInterfaces((uint)0x00000002, true); // (ERC721) - INonFungibleToken, - this.SetSupportedInterfaces((uint)0x00000003, true); // (ERC721) - INonFungibleTokenReceiver + this.SetSupportedInterfaces((uint)0x00000003, false); // (ERC721) - INonFungibleTokenReceiver } /// @@ -182,22 +182,6 @@ public bool SupportsInterface(uint interfaceID) return GetSupportedInterfaces(interfaceID); } - /// - /// 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. - /// - /// Up to the contract to implement. This contract just returns true. - /// 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. - public bool OnNonFungibleTokenReceived(Address operatorAddress, Address fromAddress, ulong tokenId, byte[] data) - { - return true; - } - /// /// Transfers the ownership of an NFT from one address to another address. This function can /// be changed to payable. From f5d2b2deab43b1b0c07b2e61b94fa66c4abf61ac Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Tue, 17 Sep 2019 17:28:22 +0200 Subject: [PATCH 08/10] Handle casting to boolean without system.convert. --- .../NonFungibleTokenTests.cs | 18 ++++-------------- .../NonFungibleToken/NonFungibleToken.cs | 14 +++++++++++++- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs index 1966bda..05cfa05 100644 --- a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs @@ -713,7 +713,7 @@ public void SafeTransferFrom_NoDataProvided_ValidTokenTransfer_ToContractReturns } [Fact] - public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTruthyObject_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() { var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); @@ -743,12 +743,7 @@ public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTr }) .Returns(TransferResult.Transferred(1)); - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); } [Fact] @@ -1044,7 +1039,7 @@ public void SafeTransferFrom_DataProvided_ValidTokenTransfer_ToContractReturnsFa } [Fact] - public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTruthyObject_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() { var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); @@ -1075,12 +1070,7 @@ public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTrut }) .Returns(TransferResult.Transferred(1)); - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff })); } [Fact] diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs index e50feb7..885f0cb 100644 --- a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs @@ -384,7 +384,19 @@ private void SafeTransferFromInternal(Address from, Address to, ulong tokenId, b if (this.PersistentState.IsContract(to)) { ITransferResult result = this.Call(to, 0, "OnNonFungibleTokenReceived", new object[] { this.Message.Sender, from, tokenId, data }, 0); - Assert(Convert.ToBoolean(result.ReturnValue)); + try + { + Assert((bool)result.ReturnValue); + } + catch (Exception ex) + { + if (ex is InvalidCastException || ex is NullReferenceException) + { + Assert(false); + } + + throw; + } } } From 1515d8602ab17f95cdb89de9bc03ebf5feb749c1 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Tue, 17 Sep 2019 18:02:25 +0200 Subject: [PATCH 09/10] Resolve sct validation issues. --- ... => NonFungibleTokenContract.Tests.csproj} | 2 +- .../NonFungibleToken/INonFungibleToken.cs | 2 +- .../INonFungibleTokenReceiver.cs | 2 +- .../NonFungibleToken/ISupportsInterface.cs | 2 +- .../NonFungibleToken/NonFungibleToken.cs | 851 +++++++++--------- ...csproj => NonFungibleTokenContract.csproj} | 0 ...Token.sln => NonFungibleTokenContract.sln} | 4 +- 7 files changed, 424 insertions(+), 439 deletions(-) rename src/NonFungibleToken/NonFungibleToken.Tests/{NonFungibleToken.Tests.csproj => NonFungibleTokenContract.Tests.csproj} (97%) rename src/NonFungibleToken/NonFungibleToken/{NonFungibleToken.csproj => NonFungibleTokenContract.csproj} (100%) rename src/NonFungibleToken/{NonFungibleToken.sln => NonFungibleTokenContract.sln} (78%) diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleToken.Tests.csproj b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenContract.Tests.csproj similarity index 97% rename from src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleToken.Tests.csproj rename to src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenContract.Tests.csproj index db7c469..411bf1f 100644 --- a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleToken.Tests.csproj +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenContract.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs index ffa438a..dcdb99b 100644 --- a/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/INonFungibleToken.cs @@ -1,4 +1,4 @@ -namespace NonFungibleToken +namespace NonFungibleTokenContract { using Stratis.SmartContracts; diff --git a/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs b/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs index 3e9e5b7..78290e7 100644 --- a/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs +++ b/src/NonFungibleToken/NonFungibleToken/INonFungibleTokenReceiver.cs @@ -1,4 +1,4 @@ -namespace NonFungibleToken +namespace NonFungibleTokenContract { using Stratis.SmartContracts; diff --git a/src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs b/src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs index 43340b5..9ea804f 100644 --- a/src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs +++ b/src/NonFungibleToken/NonFungibleToken/ISupportsInterface.cs @@ -1,4 +1,4 @@ -namespace NonFungibleToken +namespace NonFungibleTokenContract { /// /// Interface for a class with interface indication support. diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs index 885f0cb..e48ab20 100644 --- a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs +++ b/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.cs @@ -1,487 +1,472 @@ -namespace NonFungibleToken +using Stratis.SmartContracts; +using System; + +/// +/// A non fungible token contract. +/// +public class NonFungibleToken : SmartContract { - using Stratis.SmartContracts; - using System; + public struct TransferLog + { + [Index] + public Address From; + [Index] + public Address To; + [Index] + public ulong TokenId; + } + + public struct ApprovalLog + { + [Index] + public Address Owner; + [Index] + public Address Approved; + [Index] + public ulong TokenId; + } + + public struct ApprovalForAllLog + { + [Index] + public Address Owner; + [Index] + public Address Operator; + + public bool Approved; + } /// - /// A non fungible token contract. + /// Get a value indicacting if the interface is supported. /// - public class NonFungibleToken : SmartContract, INonFungibleToken, ISupportsInterface + /// The id of the interface to support. + /// A value indicating if the interface is supported. + private bool GetSupportedInterfaces(uint interfaceId) { - public struct TransferLog - { - [Index] - public Address From; - [Index] - public Address To; - [Index] - public ulong TokenId; - } + return this.PersistentState.GetBool($"SupportedInterface:{interfaceId}"); + } - public struct ApprovalLog - { - [Index] - public Address Owner; - [Index] - public Address Approved; - [Index] - public ulong TokenId; - } + /// + /// Sets the supported interface value. + /// + /// The interface id. + /// A value indicating if the interface id is supported. + private void SetSupportedInterfaces(uint interfaceId, bool value) + { + this.PersistentState.SetBool($"SupportedInterface:{interfaceId}", value); + } - public struct ApprovalForAllLog - { - [Index] - public Address Owner; - [Index] - public Address Operator; + /// + /// 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(ulong id) + { + return $"IdToOwner:{id}"; + } - public bool Approved; - } + /// + /// Gets the address of the owner of the NFT ID. + /// + /// The ID of the NFT + ///The owner address. + private Address GetIdToOwner(ulong id) + { + return this.PersistentState.GetAddress(GetIdToOwnerKey(id)); + } - /// - /// Get a value indicacting if the interface is supported. - /// - /// The id of the interface to support. - /// A value indicating if the interface is supported. - private bool GetSupportedInterfaces(uint interfaceId) - { - return this.PersistentState.GetBool($"SupportedInterface:{interfaceId}"); - } + /// + /// Sets the owner to the NFT ID. + /// + /// The ID of the NFT + /// The address of the owner. + private void SetIdToOwner(ulong id, Address value) + { + this.PersistentState.SetAddress(GetIdToOwnerKey(id), value); + } - /// - /// Sets the supported interface value. - /// - /// The interface id. - /// A value indicating if the interface id is supported. - private void SetSupportedInterfaces(uint interfaceId, bool value) - { - this.PersistentState.SetBool($"SupportedInterface:{interfaceId}", 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(ulong id) + { + return $"IdToApproval:{id}"; + } - /// - /// 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(ulong id) - { - return $"IdToOwner:{id}"; - } + /// + /// Getting from NFT ID the approval address. + /// + /// The ID of the NFT + /// Address of the approval. + private Address GetIdToApproval(ulong id) + { + return this.PersistentState.GetAddress(GetIdToApprovalKey(id)); + } - /// - /// Gets the address of the owner of the NFT ID. - /// - /// The ID of the NFT - ///The owner address. - private Address GetIdToOwner(ulong id) - { - return this.PersistentState.GetAddress(GetIdToOwnerKey(id)); - } + /// + /// Setting to NFT ID to approval address. + /// + /// The ID of the NFT + /// The address of the approval. + private void SetIdToApproval(ulong id, Address value) + { + this.PersistentState.SetAddress(GetIdToApprovalKey(id), value); + } - /// - /// Sets the owner to the NFT ID. - /// - /// The ID of the NFT - /// The address of the owner. - private void SetIdToOwner(ulong id, Address value) - { - this.PersistentState.SetAddress(GetIdToOwnerKey(id), value); - } + /// + /// Gets the amount of non fungible tokens the owner has. + /// + /// The address of the owner. + /// The amount of non fungible tokens. + private ulong GetOwnerToNFTokenCount(Address address) + { + return this.PersistentState.GetUInt64($"OwnerToNFTokenCount:{address}"); + } - /// - /// 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(ulong id) - { - return $"IdToApproval:{id}"; - } + /// + /// Sets the owner count of this non fungible tokens. + /// + /// The address of the owner. + /// The amount of tokens. + private void SetOwnerToNFTokenCount(Address address, ulong value) + { + this.PersistentState.SetUInt64($"OwnerToNFTokenCount:{address}", value); + } - /// - /// Getting from NFT ID the approval address. - /// - /// The ID of the NFT - /// Address of the approval. - private Address GetIdToApproval(ulong id) - { - return this.PersistentState.GetAddress(GetIdToApprovalKey(id)); - } + /// + /// 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) + { + return this.PersistentState.GetBool($"OwnerToOperator:{owner}:{operatorAddress}"); + } - /// - /// Setting to NFT ID to approval address. - /// - /// The ID of the NFT - /// The address of the approval. - private void SetIdToApproval(ulong id, Address value) - { - this.PersistentState.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 ulong GetOwnerToNFTokenCount(Address address) - { - return this.PersistentState.GetUInt64($"OwnerToNFTokenCount:{address}"); - } + /// + /// 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) + { + this.PersistentState.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); + } - /// - /// Sets the owner count of this non fungible tokens. - /// - /// The address of the owner. - /// The amount of tokens. - private void SetOwnerToNFTokenCount(Address address, ulong value) - { - this.PersistentState.SetUInt64($"OwnerToNFTokenCount:{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) - { - return this.PersistentState.GetBool($"OwnerToOperator:{owner}:{operatorAddress}"); - } + /// + /// Constructor. Initializes the supported interfaces. + /// + /// The smart contract state. + public NonFungibleToken(ISmartContractState state) : base(state) + { + // todo: discuss callback handling and supported interface numbering with community. + this.SetSupportedInterfaces((uint)0x00000001, true); // (ERC165) - ISupportsInterface + this.SetSupportedInterfaces((uint)0x00000002, true); // (ERC721) - INonFungibleToken, + this.SetSupportedInterfaces((uint)0x00000003, false); // (ERC721) - INonFungibleTokenReceiver + } - /// - /// 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) - { - this.PersistentState.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); - } + /// + /// 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 GetSupportedInterfaces(interfaceID); + } - /// - /// Constructor. Initializes the supported interfaces. - /// - /// The smart contract state. - public NonFungibleToken(ISmartContractState state) : base(state) - { - // todo: discuss callback handling and supported interface numbering with community. - this.SetSupportedInterfaces((uint)0x00000001, true); // (ERC165) - ISupportsInterface - this.SetSupportedInterfaces((uint)0x00000002, true); // (ERC721) - INonFungibleToken, - this.SetSupportedInterfaces((uint)0x00000003, false); // (ERC721) - INonFungibleTokenReceiver - } + /// + /// 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, ulong tokenId, byte[] data) + { + SafeTransferFromInternal(from, to, tokenId, data); + } - /// - /// 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 GetSupportedInterfaces(interfaceID); - } + /// + /// 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, ulong tokenId) + { + SafeTransferFromInternal(from, to, tokenId, new byte[0]); + } - /// - /// 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, ulong tokenId, byte[] data) - { - SafeTransferFromInternal(from, to, tokenId, data); - } + /// + /// 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. + public void TransferFrom(Address from, Address to, ulong tokenId) + { + CanTransfer(tokenId); + ValidNFToken(tokenId); - /// - /// 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, ulong tokenId) - { - SafeTransferFromInternal(from, to, tokenId, new byte[0]); - } + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == from); + Assert(to != Address.Zero); - /// - /// 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. - public void TransferFrom(Address from, Address to, ulong tokenId) - { - CanTransfer(tokenId); - ValidNFToken(tokenId); + TransferInternal(to, tokenId); + } - Address tokenOwner = GetIdToOwner(tokenId); - Assert(tokenOwner == from); - Assert(to != Address.Zero); + /// + /// 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, ulong tokenId) + { + CanOperate(tokenId); + ValidNFToken(tokenId); - TransferInternal(to, tokenId); - } + Address tokenOwner = GetIdToOwner(tokenId); + Assert(approved != tokenOwner); - /// - /// 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, ulong tokenId) - { - CanOperate(tokenId); - ValidNFToken(tokenId); + SetIdToApproval(tokenId, approved); + LogApproval(tokenOwner, approved, tokenId); + } - Address tokenOwner = GetIdToOwner(tokenId); - Assert(approved != tokenOwner); + /// + /// 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(this.Message.Sender, operatorAddress, approved); + LogApprovalForAll(this.Message.Sender, operatorAddress, approved); + } - SetIdToApproval(tokenId, approved); - LogApproval(tokenOwner, approved, tokenId); - } + /// + /// 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 ulong BalanceOf(Address owner) + { + Assert(owner != Address.Zero); + return GetOwnerToNFTokenCount(owner); + } - /// - /// 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(this.Message.Sender, operatorAddress, approved); - LogApprovalForAll(this.Message.Sender, operatorAddress, approved); - } + /// + /// 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(ulong tokenId) + { + Address owner = GetIdToOwner(tokenId); + Assert(owner != Address.Zero); + return owner; + } - /// - /// 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 ulong BalanceOf(Address owner) - { - Assert(owner != Address.Zero); - return GetOwnerToNFTokenCount(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(ulong tokenId) + { + ValidNFToken(tokenId); - /// - /// 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(ulong tokenId) - { - Address owner = GetIdToOwner(tokenId); - Assert(owner != Address.Zero); - return owner; - } + return GetIdToApproval(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. - public Address GetApproved(ulong tokenId) - { - ValidNFToken(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); + } - return GetIdToApproval(tokenId); - } + /// + /// Actually preforms the transfer. + /// + /// Does NO checks. + /// Address of a new owner. + /// The NFT that is being transferred. + private void TransferInternal(Address to, ulong tokenId) + { + Address from = GetIdToOwner(tokenId); + ClearApproval(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); - } + RemoveNFToken(from, tokenId); + AddNFToken(to, tokenId); - /// - /// Actually preforms the transfer. - /// - /// Does NO checks. - /// Address of a new owner. - /// The NFT that is being transferred. - private void TransferInternal(Address to, ulong tokenId) - { - Address from = GetIdToOwner(tokenId); - ClearApproval(tokenId); + LogTransfer(from, to, tokenId); + } - RemoveNFToken(from, tokenId); - AddNFToken(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 RemoveNFToken(Address from, ulong tokenId) + { + Assert(GetIdToOwner(tokenId) == from); + SetOwnerToNFTokenCount(from, checked(GetOwnerToNFTokenCount(from) - 1)); + this.PersistentState.Clear(GetIdToOwnerKey(tokenId)); + } - LogTransfer(from, to, tokenId); - } + /// + /// 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 AddNFToken(Address to, ulong tokenId) + { + Assert(GetIdToOwner(tokenId) == Address.Zero); - /// - /// 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 RemoveNFToken(Address from, ulong tokenId) - { - Assert(GetIdToOwner(tokenId) == from); - SetOwnerToNFTokenCount(from, checked(GetOwnerToNFTokenCount(from) - 1)); - this.PersistentState.Clear(GetIdToOwnerKey(tokenId)); - } + SetIdToOwner(tokenId, to); + ulong currentTokenAmount = GetOwnerToNFTokenCount(to); + SetOwnerToNFTokenCount(to, checked(currentTokenAmount + 1)); + } - /// - /// 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 AddNFToken(Address to, ulong tokenId) - { - Assert(GetIdToOwner(tokenId) == Address.Zero); + /// + /// 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, ulong tokenId, byte[] data) + { + CanTransfer(tokenId); + ValidNFToken(tokenId); - SetIdToOwner(tokenId, to); - ulong currentTokenAmount = GetOwnerToNFTokenCount(to); - SetOwnerToNFTokenCount(to, checked(currentTokenAmount + 1)); - } + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == from); + Assert(to != Address.Zero); - /// - /// 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, ulong tokenId, byte[] data) - { - CanTransfer(tokenId); - ValidNFToken(tokenId); - - Address tokenOwner = GetIdToOwner(tokenId); - Assert(tokenOwner == from); - Assert(to != Address.Zero); - - TransferInternal(to, tokenId); - - if (this.PersistentState.IsContract(to)) - { - ITransferResult result = this.Call(to, 0, "OnNonFungibleTokenReceived", new object[] { this.Message.Sender, from, tokenId, data }, 0); - try - { - Assert((bool)result.ReturnValue); - } - catch (Exception ex) - { - if (ex is InvalidCastException || ex is NullReferenceException) - { - Assert(false); - } - - throw; - } - } - } + TransferInternal(to, tokenId); - /// - /// Clears the current approval of a given NFT ID. - /// - /// ID of the NFT to be transferred - private void ClearApproval(ulong tokenId) + if (this.PersistentState.IsContract(to)) { - if (GetIdToApproval(tokenId) != Address.Zero) - { - this.PersistentState.Clear(GetIdToApprovalKey(tokenId)); - } + ITransferResult result = this.Call(to, 0, "OnNonFungibleTokenReceived", new object[] { this.Message.Sender, from, tokenId, data }, 0); + Assert((bool)result.ReturnValue); } + } - /// - /// 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, ulong tokenId) + /// + /// Clears the current approval of a given NFT ID. + /// + /// ID of the NFT to be transferred + private void ClearApproval(ulong tokenId) + { + if (GetIdToApproval(tokenId) != Address.Zero) { - Log(new TransferLog() { From = from, To = to, TokenId = tokenId }); + this.PersistentState.Clear(GetIdToApprovalKey(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, ulong tokenId) - { - Log(new ApprovalLog() { Owner = owner, Approved = approved, TokenId = tokenId }); - } + /// + /// 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, ulong tokenId) + { + Log(new TransferLog() { From = from, To = to, 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 }); - } + /// + /// 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, ulong 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(ulong tokenId) - { - Address tokenOwner = GetIdToOwner(tokenId); - Assert(tokenOwner == this.Message.Sender || GetOwnerToOperator(tokenOwner, this.Message.Sender)); - } - /// - /// Guarantees that the msg.sender is allowed to transfer NFT. - /// - /// ID of the NFT to transfer. - private void CanTransfer(ulong tokenId) - { - Address tokenOwner = GetIdToOwner(tokenId); - Assert( - tokenOwner == this.Message.Sender - || GetIdToApproval(tokenId) == Message.Sender - || GetOwnerToOperator(tokenOwner, Message.Sender) - ); - } + /// + /// Guarantees that the is an owner or operator of the given NFT. + /// + /// ID of the NFT to validate. + private void CanOperate(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == this.Message.Sender || GetOwnerToOperator(tokenOwner, this.Message.Sender)); + } - /// - /// Guarantees that tokenId is a valid Token. - /// - /// ID of the NFT to validate. - private void ValidNFToken(ulong tokenId) - { - Assert(GetIdToOwner(tokenId) != Address.Zero); - } + /// + /// Guarantees that the msg.sender is allowed to transfer NFT. + /// + /// ID of the NFT to transfer. + private void CanTransfer(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + Assert( + tokenOwner == this.Message.Sender + || GetIdToApproval(tokenId) == Message.Sender + || GetOwnerToOperator(tokenOwner, Message.Sender) + ); + } + + /// + /// Guarantees that tokenId is a valid Token. + /// + /// ID of the NFT to validate. + private void ValidNFToken(ulong tokenId) + { + Assert(GetIdToOwner(tokenId) != Address.Zero); } -} +} \ No newline at end of file diff --git a/src/NonFungibleToken/NonFungibleToken/NonFungibleToken.csproj b/src/NonFungibleToken/NonFungibleToken/NonFungibleTokenContract.csproj similarity index 100% rename from src/NonFungibleToken/NonFungibleToken/NonFungibleToken.csproj rename to src/NonFungibleToken/NonFungibleToken/NonFungibleTokenContract.csproj diff --git a/src/NonFungibleToken/NonFungibleToken.sln b/src/NonFungibleToken/NonFungibleTokenContract.sln similarity index 78% rename from src/NonFungibleToken/NonFungibleToken.sln rename to src/NonFungibleToken/NonFungibleTokenContract.sln index 343e621..e069dd0 100644 --- a/src/NonFungibleToken/NonFungibleToken.sln +++ b/src/NonFungibleToken/NonFungibleTokenContract.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29201.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NonFungibleToken", "NonFungibleToken\NonFungibleToken.csproj", "{D64B8959-5CC0-43D4-99B7-E07481222B5D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonFungibleTokenContract", "NonFungibleToken\NonFungibleTokenContract.csproj", "{D64B8959-5CC0-43D4-99B7-E07481222B5D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NonFungibleToken.Tests", "NonFungibleToken.Tests\NonFungibleToken.Tests.csproj", "{855863D4-4F60-47D0-AD2A-164749950614}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonFungibleTokenContract.Tests", "NonFungibleToken.Tests\NonFungibleTokenContract.Tests.csproj", "{855863D4-4F60-47D0-AD2A-164749950614}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 5673bd6e6de2a7541c27ae177eba82d59e4657f6 Mon Sep 17 00:00:00 2001 From: Neurosploit Date: Tue, 17 Sep 2019 18:07:08 +0200 Subject: [PATCH 10/10] Make tests run again. --- .../NonFungibleTokenTests.cs | 2417 ++++++++--------- 1 file changed, 1207 insertions(+), 1210 deletions(-) diff --git a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs index 05cfa05..234aece 100644 --- a/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs +++ b/src/NonFungibleToken/NonFungibleToken.Tests/NonFungibleTokenTests.cs @@ -1,1245 +1,1242 @@ -namespace NonFungibleToken.Tests +using System; +using System.Collections.Generic; +using Moq; +using Stratis.SmartContracts; +using Stratis.SmartContracts.CLR; +using Xunit; + +public class NonFungibleTokenTests { - using System; - using System.Collections.Generic; - using Moq; - using Stratis.SmartContracts; - using Stratis.SmartContracts.CLR; - using Xunit; - - public class NonFungibleTokenTests + private Mock smartContractStateMock; + private Mock contractLoggerMock; + private Mock persistentStateMock; + private Dictionary supportedInterfaces; + private Dictionary idToOwner; + private Dictionary idToApproval; + private Dictionary ownerToOperator; + private Dictionary ownerToNFTokenCount; + private Mock internalTransactionExecutorMock; + + public NonFungibleTokenTests() { - private Mock smartContractStateMock; - private Mock contractLoggerMock; - private Mock persistentStateMock; - private Dictionary supportedInterfaces; - private Dictionary idToOwner; - private Dictionary idToApproval; - private Dictionary ownerToOperator; - private Dictionary ownerToNFTokenCount; - private Mock internalTransactionExecutorMock; - - public NonFungibleTokenTests() - { - this.contractLoggerMock = new Mock(); - this.persistentStateMock = new Mock(); - this.smartContractStateMock = new Mock(); - this.internalTransactionExecutorMock = new Mock(); - this.smartContractStateMock.Setup(s => s.PersistentState).Returns(this.persistentStateMock.Object); - this.smartContractStateMock.Setup(s => s.ContractLogger).Returns(this.contractLoggerMock.Object); - this.smartContractStateMock.Setup(x => x.InternalTransactionExecutor).Returns(this.internalTransactionExecutorMock.Object); - - this.supportedInterfaces = new Dictionary(); - this.idToOwner = new Dictionary(); - this.idToApproval = new Dictionary(); - this.ownerToOperator = new Dictionary(); - this.ownerToNFTokenCount = new Dictionary(); - - this.SetupPersistentState(); - } - - [Fact] - public void Constructor_Sets_SupportedInterfaces() - { - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Equal(3, this.supportedInterfaces.Count); - Assert.True(this.supportedInterfaces["SupportedInterface:1"]); - Assert.True(this.supportedInterfaces["SupportedInterface:2"]); - Assert.False(this.supportedInterfaces["SupportedInterface:3"]); - } - - [Fact] - public void SupportsInterface_InterfaceSupported_ReturnsTrue() - { - var nonFungibleToken = this.CreateNonFungibleToken(); - - var result = nonFungibleToken.SupportsInterface(2); - - Assert.True(result); - } - - [Fact] - public void SupportsInterface_InterfaceSetToFalseSupported_ReturnsFalse() - { - var nonFungibleToken = this.CreateNonFungibleToken(); - this.supportedInterfaces["SupportedInterface:2"] = false; - - var result = nonFungibleToken.SupportsInterface(3); - - Assert.False(result); - } - - [Fact] - public void SupportsInterface_InterfaceNotSupported_ReturnsFalse() - { - var nonFungibleToken = this.CreateNonFungibleToken(); - - var result = nonFungibleToken.SupportsInterface(4); - - Assert.False(result); - } - - [Fact] - public void GetApproved_NotValidNFToken_OwnerAddressZero_ThrowsException() - { - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.GetApproved(1)); - } - - [Fact] - public void GetApproved_ApprovalNotInStorage_ReturnsZeroAddress() - { - this.idToOwner.Add("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); - this.idToApproval.Clear(); - - var nonFungibleToken = this.CreateNonFungibleToken(); - var result = nonFungibleToken.GetApproved(1); - - Assert.Equal(Address.Zero, result); - } - - [Fact] - public void GetApproved_ApprovalInStorage_ReturnsAddress() - { - var approvalAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); - this.idToApproval.Add("IdToApproval:1", approvalAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - var result = nonFungibleToken.GetApproved(1); - - Assert.Equal(approvalAddress, result); - } - - [Fact] - public void IsApprovedForAll_OwnerToOperatorInStateAsTrue_ReturnsTrue() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", true); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + this.contractLoggerMock = new Mock(); + this.persistentStateMock = new Mock(); + this.smartContractStateMock = new Mock(); + this.internalTransactionExecutorMock = new Mock(); + this.smartContractStateMock.Setup(s => s.PersistentState).Returns(this.persistentStateMock.Object); + this.smartContractStateMock.Setup(s => s.ContractLogger).Returns(this.contractLoggerMock.Object); + this.smartContractStateMock.Setup(x => x.InternalTransactionExecutor).Returns(this.internalTransactionExecutorMock.Object); + + this.supportedInterfaces = new Dictionary(); + this.idToOwner = new Dictionary(); + this.idToApproval = new Dictionary(); + this.ownerToOperator = new Dictionary(); + this.ownerToNFTokenCount = new Dictionary(); + + this.SetupPersistentState(); + } - Assert.True(result); - } + [Fact] + public void Constructor_Sets_SupportedInterfaces() + { + var nonFungibleToken = this.CreateNonFungibleToken(); - [Fact] - public void IsApprovedForAll_OwnerToOperatorInStateAsFalse_ReturnsFalse() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", false); + Assert.Equal(3, this.supportedInterfaces.Count); + Assert.True(this.supportedInterfaces["SupportedInterface:1"]); + Assert.True(this.supportedInterfaces["SupportedInterface:2"]); + Assert.False(this.supportedInterfaces["SupportedInterface:3"]); + } - var nonFungibleToken = this.CreateNonFungibleToken(); + [Fact] + public void SupportsInterface_InterfaceSupported_ReturnsTrue() + { + var nonFungibleToken = this.CreateNonFungibleToken(); - var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + var result = nonFungibleToken.SupportsInterface(2); - Assert.False(result); - } + Assert.True(result); + } - [Fact] - public void IsApprovedForAll_OwnerToOperatorNotInState_ReturnsFalse() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.ownerToOperator.Clear(); + [Fact] + public void SupportsInterface_InterfaceSetToFalseSupported_ReturnsFalse() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + this.supportedInterfaces["SupportedInterface:2"] = false; - var nonFungibleToken = this.CreateNonFungibleToken(); + var result = nonFungibleToken.SupportsInterface(3); - var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + Assert.False(result); + } - Assert.False(result); - } + [Fact] + public void SupportsInterface_InterfaceNotSupported_ReturnsFalse() + { + var nonFungibleToken = this.CreateNonFungibleToken(); - [Fact] - public void OwnerOf_IdToOwnerNotInStorage_ThrowsException() - { - this.idToOwner.Clear(); - var nonFungibleToken = this.CreateNonFungibleToken(); + var result = nonFungibleToken.SupportsInterface(4); - Assert.Throws(() => nonFungibleToken.OwnerOf(1)); - } + Assert.False(result); + } - [Fact] - public void OwnerOf_NFTokenMappedToAddressZero_ThrowsException() - { - this.idToOwner.Add("IdToOwner:1", Address.Zero); - var nonFungibleToken = this.CreateNonFungibleToken(); + [Fact] + public void GetApproved_NotValidNFToken_OwnerAddressZero_ThrowsException() + { + var nonFungibleToken = this.CreateNonFungibleToken(); - Assert.Throws(() => nonFungibleToken.OwnerOf(1)); - } - - [Fact] - public void OwnerOf_NFTokenExistsWithOwner_ReturnsOwnerAddress() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - var nonFungibleToken = this.CreateNonFungibleToken(); + Assert.Throws(() => nonFungibleToken.GetApproved(1)); + } - var result = nonFungibleToken.OwnerOf(1); - - Assert.Equal(ownerAddress, result); - } - - [Fact] - public void BalanceOf_OwnerZero_ThrowsException() - { - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => { nonFungibleToken.BalanceOf(Address.Zero); }); - } - - [Fact] - public void BalanceOf_NftTokenCountNotInStorage_ReturnsZero() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - this.ownerToNFTokenCount.Clear(); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var result = nonFungibleToken.BalanceOf(ownerAddress); - - Assert.Equal((ulong)0, result); - } - - [Fact] - public void BalanceOf_OwnerNftTokenCountInStorage_ReturnsTokenCount() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 15); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var result = nonFungibleToken.BalanceOf(ownerAddress); - - Assert.Equal((ulong)15, result); - } - - [Fact] - public void SetApprovalForAll_SetsMessageSender_ToOperatorApproval() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - var nonFungibleToken = this.CreateNonFungibleToken(); - - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - nonFungibleToken.SetApprovalForAll(operatorAddress, true); - - Assert.NotEmpty(this.ownerToOperator); - Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalForAllLog { Owner = ownerAddress, Operator = operatorAddress, Approved = true })); - } - - [Fact] - public void Approve_TokenOwnerNotMessageSenderOrOperator_ThrowsException() - { - this.idToOwner.Clear(); - this.ownerToOperator.Clear(); - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); - } - - [Fact] - public void Approve_ValidApproval_SwitchesOwnerToApprovedForNFToken() - { - this.idToApproval.Clear(); - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.Approve(someAddress, 1); - - Assert.NotEmpty(this.idToApproval); - Assert.Equal(this.idToApproval["IdToApproval:1"], someAddress); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); - } - - [Fact] - public void Approve_NTFokenOwnerSameAsMessageSender_ThrowsException() - { - this.idToApproval.Clear(); - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.Approve(ownerAddress, 1)); - } - - [Fact] - public void Approve_ValidApproval_ByApprovedOperator_SwitchesOwnerToApprovedForNFToken() - { - this.idToApproval.Clear(); - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.Approve(someAddress, 1); - - Assert.NotEmpty(this.idToApproval); - Assert.Equal(this.idToApproval["IdToApproval:1"], someAddress); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); - } - - [Fact] - public void Approve_InvalidNFToken_ThrowsException() - { - this.idToApproval.Clear(); - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var operatorAddress = Address.Zero; - var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", Address.Zero); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); - } - - [Fact] - public void TransferFrom_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); - } - - [Fact] - public void TransferFrom_ValidTokenTransfer_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Add("IdToApproval:1", approvalAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Clear(); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1)); - } - - [Fact] - public void TransferFrom_NFTokenOwnerZero_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", Address.Zero); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.TransferFrom(Address.Zero, targetAddress, 1)); - } - - [Fact] - public void TransferFrom_TokenDoesNotBelongToFrom_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - var notOwningAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.TransferFrom(notOwningAddress, targetAddress, 1)); - } - - [Fact] - public void TransferFrom_ToAddressZero_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(false); - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); - this.internalTransactionExecutorMock.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Add("IdToApproval:1", approvalAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(false); - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); - this.internalTransactionExecutorMock.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Clear(); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(false); - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); - - this.internalTransactionExecutorMock.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.Empty((byte[])callParams[3]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(true)); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Add("IdToApproval:1", approvalAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { approvalAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.Empty((byte[])callParams[3]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(true)); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Clear(); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { operatorAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.Empty((byte[])callParams[3]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(true)); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); - } - - [Fact] - public void SafeTransferFrom_NoDataProvided_NFTokenOwnerZero_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", Address.Zero); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); - - var nonFungibleToken = this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.SafeTransferFrom(notOwningAddress, targetAddress, 1)); - } - - [Fact] - public void SafeTransferFrom_NoDataProvided_ValidTokenTransfer_ToContractReturnsFalse_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.Empty((byte[])callParams[3]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(false)); - - Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); - } - - [Fact] - public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.Empty((byte[])callParams[3]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(1)); - - Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); - } - - [Fact] - public void SafeTransferFrom_NoDataProvided_ToAddressZero_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(false); - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); - this.internalTransactionExecutorMock.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Add("IdToApproval:1", approvalAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(false); - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); - this.internalTransactionExecutorMock.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Clear(); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(false); - var nonFungibleToken = this.CreateNonFungibleToken(); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); - - this.internalTransactionExecutorMock.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.NotEmpty((byte[])callParams[3]); - Assert.Equal(0xff, ((byte[])callParams[3])[0]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(true)); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Add("IdToApproval:1", approvalAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { approvalAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.NotEmpty((byte[])callParams[3]); - Assert.Equal(0xff, ((byte[])callParams[3])[0]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(true)); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.idToApproval.Clear(); - this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { operatorAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => - { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.NotEmpty((byte[])callParams[3]); - Assert.Equal(0xff, ((byte[])callParams[3])[0]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(true)); - - nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); - - Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); - Assert.Empty(this.idToApproval); - Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); - Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); - Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); - this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); - - var nonFungibleToken = this.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(); - this.idToOwner.Add("IdToOwner:1", Address.Zero); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); - - var nonFungibleToken = this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.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(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => + [Fact] + public void GetApproved_ApprovalNotInStorage_ReturnsZeroAddress() + { + this.idToOwner.Add("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); + this.idToApproval.Clear(); + + var nonFungibleToken = this.CreateNonFungibleToken(); + var result = nonFungibleToken.GetApproved(1); + + Assert.Equal(Address.Zero, result); + } + + [Fact] + public void GetApproved_ApprovalInStorage_ReturnsAddress() + { + var approvalAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", "0x0000000000000000000000000000000000000005".HexToAddress()); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + var result = nonFungibleToken.GetApproved(1); + + Assert.Equal(approvalAddress, result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorInStateAsTrue_ReturnsTrue() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", true); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.True(result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorInStateAsFalse_ReturnsFalse() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddresss}", false); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.False(result); + } + + [Fact] + public void IsApprovedForAll_OwnerToOperatorNotInState_ReturnsFalse() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddresss = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.ownerToOperator.Clear(); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.IsApprovedForAll(ownerAddress, operatorAddresss); + + Assert.False(result); + } + + [Fact] + public void OwnerOf_IdToOwnerNotInStorage_ThrowsException() + { + this.idToOwner.Clear(); + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.OwnerOf(1)); + } + + [Fact] + public void OwnerOf_NFTokenMappedToAddressZero_ThrowsException() + { + this.idToOwner.Add("IdToOwner:1", Address.Zero); + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.OwnerOf(1)); + } + + [Fact] + public void OwnerOf_NFTokenExistsWithOwner_ReturnsOwnerAddress() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.OwnerOf(1); + + Assert.Equal(ownerAddress, result); + } + + [Fact] + public void BalanceOf_OwnerZero_ThrowsException() + { + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => { nonFungibleToken.BalanceOf(Address.Zero); }); + } + + [Fact] + public void BalanceOf_NftTokenCountNotInStorage_ReturnsZero() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.ownerToNFTokenCount.Clear(); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.BalanceOf(ownerAddress); + + Assert.Equal((ulong)0, result); + } + + [Fact] + public void BalanceOf_OwnerNftTokenCountInStorage_ReturnsTokenCount() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 15); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var result = nonFungibleToken.BalanceOf(ownerAddress); + + Assert.Equal((ulong)15, result); + } + + [Fact] + public void SetApprovalForAll_SetsMessageSender_ToOperatorApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var nonFungibleToken = this.CreateNonFungibleToken(); + + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + nonFungibleToken.SetApprovalForAll(operatorAddress, true); + + Assert.NotEmpty(this.ownerToOperator); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalForAllLog { Owner = ownerAddress, Operator = operatorAddress, Approved = true })); + } + + [Fact] + public void Approve_TokenOwnerNotMessageSenderOrOperator_ThrowsException() + { + this.idToOwner.Clear(); + this.ownerToOperator.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); + } + + [Fact] + public void Approve_ValidApproval_SwitchesOwnerToApprovedForNFToken() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.Approve(someAddress, 1); + + Assert.NotEmpty(this.idToApproval); + Assert.Equal(this.idToApproval["IdToApproval:1"], someAddress); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); + } + + [Fact] + public void Approve_NTFokenOwnerSameAsMessageSender_ThrowsException() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(ownerAddress, 1)); + } + + [Fact] + public void Approve_ValidApproval_ByApprovedOperator_SwitchesOwnerToApprovedForNFToken() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.Approve(someAddress, 1); + + Assert.NotEmpty(this.idToApproval); + Assert.Equal(this.idToApproval["IdToApproval:1"], someAddress); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.ApprovalLog { Owner = ownerAddress, Approved = someAddress, TokenId = 1 })); + } + + [Fact] + public void Approve_InvalidNFToken_ThrowsException() + { + this.idToApproval.Clear(); + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var operatorAddress = Address.Zero; + var someAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.Approve(someAddress, 1)); + } + + [Fact] + public void TransferFrom_ValidTokenTransfer_MessageSender_TransfersTokenFrom_To() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + } + + [Fact] + public void TransferFrom_ValidTokenTransfer_MessageSenderApprovedForTokenIdByOwner_TransfersTokenFrom_To_ClearsApproval() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var approvalAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_NFTokenOwnerZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(Address.Zero, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_TokenDoesNotBelongToFrom_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + var notOwningAddress = "0x0000000000000000000000000000000000000008".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.TransferFrom(notOwningAddress, targetAddress, 1)); + } + + [Fact] + public void TransferFrom_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { approvalAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { operatorAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_NFTokenOwnerZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(notOwningAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ValidTokenTransfer_ToContractReturnsFalse_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(false)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.Empty((byte[])callParams[3]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(1)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1)); + } + + [Fact] + public void SafeTransferFrom_NoDataProvided_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(false); + var nonFungibleToken = this.CreateNonFungibleToken(); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.contractLoggerMock.Verify(l => l.Log(It.IsAny(), new NonFungibleToken.TransferLog { From = ownerAddress, To = targetAddress, TokenId = 1 })); + + this.internalTransactionExecutorMock.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Add("IdToApproval:1", approvalAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(approvalAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { approvalAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.idToApproval.Clear(); + this.ownerToOperator.Add($"OwnerToOperator:{ownerAddress}:{operatorAddress}", true); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(operatorAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { operatorAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(true)); + + nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff }); + + Assert.Equal(targetAddress, this.idToOwner["IdToOwner:1"]); + Assert.Empty(this.idToApproval); + Assert.True(this.ownerToOperator[$"OwnerToOperator:{ownerAddress}:{operatorAddress}"]); + Assert.Equal((ulong)0, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{ownerAddress}"]); + Assert.Equal((ulong)1, this.ownerToNFTokenCount[$"OwnerToNFTokenCount:{targetAddress}"]); + this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(invalidSenderAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", Address.Zero); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(Address.Zero); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.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(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(false)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) + .Returns(true); + var nonFungibleToken = this.CreateNonFungibleToken(); + + var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; + this.internalTransactionExecutorMock.Setup( + t => t.Call( + It.IsAny(), + targetAddress, + 0, + "OnNonFungibleTokenReceived", + It.IsAny(), + It.IsAny())) + .Callback((a, b, c, d, callParams, f) => + { + Assert.Equal(callParamsExpected[0], callParams[0]); + Assert.Equal(callParamsExpected[1], callParams[1]); + Assert.Equal(callParamsExpected[2], callParams[2]); + Assert.NotEmpty((byte[])callParams[3]); + Assert.Equal(0xff, ((byte[])callParams[3])[0]); + Assert.Equal(typeof(byte[]), callParams[3].GetType()); + }) + .Returns(TransferResult.Transferred(1)); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff })); + } + + [Fact] + public void SafeTransferFrom_DataProvided_ToAddressZero_ThrowsException() + { + var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); + this.idToOwner.Add("IdToOwner:1", ownerAddress); + this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); + this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); + + var nonFungibleToken = this.CreateNonFungibleToken(); + + Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, Address.Zero, 1, new byte[1] { 0xff })); + } + + private NonFungibleToken CreateNonFungibleToken() + { + return new NonFungibleToken(this.smartContractStateMock.Object); + } + + private void SetupPersistentState() + { + this.SetupSupportedInterfaces(); + this.SetupIdToOwner(); + this.SetupIdToApproval(); + this.SetupOwnerToOperators(); + this.SetupOwnerToNFTokenCount(); + } + + private void SetupOwnerToNFTokenCount() + { + this.persistentStateMock.Setup(p => p.SetUInt64(It.Is(s => s.StartsWith("OwnerToNFTokenCount:", StringComparison.Ordinal)), It.IsAny())) + .Callback((key, value) => + { + if (this.ownerToNFTokenCount.ContainsKey(key)) { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.NotEmpty((byte[])callParams[3]); - Assert.Equal(0xff, ((byte[])callParams[3])[0]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(false)); - - Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff })); - } - - [Fact] - public void SafeTransferFrom_DataProvided_ToContractTrue_ContractCallReturnsTruthyObject_CannotCastToBool_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - var targetAddress = "0x0000000000000000000000000000000000000007".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - this.persistentStateMock.Setup(p => p.IsContract(targetAddress)) - .Returns(true); - var nonFungibleToken = this.CreateNonFungibleToken(); - - var callParamsExpected = new object[] { ownerAddress, ownerAddress, (ulong)1, new byte[0] }; - this.internalTransactionExecutorMock.Setup( - t => t.Call( - It.IsAny(), - targetAddress, - 0, - "OnNonFungibleTokenReceived", - It.IsAny(), - It.IsAny())) - .Callback((a, b, c, d, callParams, f) => + this.ownerToNFTokenCount[key] = value; + } + else { - Assert.Equal(callParamsExpected[0], callParams[0]); - Assert.Equal(callParamsExpected[1], callParams[1]); - Assert.Equal(callParamsExpected[2], callParams[2]); - Assert.NotEmpty((byte[])callParams[3]); - Assert.Equal(0xff, ((byte[])callParams[3])[0]); - Assert.Equal(typeof(byte[]), callParams[3].GetType()); - }) - .Returns(TransferResult.Transferred(1)); - - Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, targetAddress, 1, new byte[1] { 0xff })); - } - - [Fact] - public void SafeTransferFrom_DataProvided_ToAddressZero_ThrowsException() - { - var ownerAddress = "0x0000000000000000000000000000000000000006".HexToAddress(); - this.idToOwner.Add("IdToOwner:1", ownerAddress); - this.ownerToNFTokenCount.Add($"OwnerToNFTokenCount:{ownerAddress}", 1); - this.smartContractStateMock.Setup(m => m.Message.Sender).Returns(ownerAddress); - - var nonFungibleToken = this.CreateNonFungibleToken(); - - Assert.Throws(() => nonFungibleToken.SafeTransferFrom(ownerAddress, Address.Zero, 1, new byte[1] { 0xff })); - } - - private NonFungibleToken CreateNonFungibleToken() - { - return new NonFungibleToken(this.smartContractStateMock.Object); - } - - private void SetupPersistentState() - { - this.SetupSupportedInterfaces(); - this.SetupIdToOwner(); - this.SetupIdToApproval(); - this.SetupOwnerToOperators(); - this.SetupOwnerToNFTokenCount(); - } - - private void SetupOwnerToNFTokenCount() - { - this.persistentStateMock.Setup(p => p.SetUInt64(It.Is(s => s.StartsWith("OwnerToNFTokenCount:", StringComparison.Ordinal)), It.IsAny())) - .Callback((key, value) => + this.ownerToNFTokenCount.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetUInt64(It.Is(s => s.StartsWith("OwnerToNFTokenCount:")))) + .Returns((key) => + { + if (this.ownerToNFTokenCount.ContainsKey(key)) { - if (this.ownerToNFTokenCount.ContainsKey(key)) - { - this.ownerToNFTokenCount[key] = value; - } - else - { - this.ownerToNFTokenCount.Add(key, value); - } - }); - this.persistentStateMock.Setup(p => p.GetUInt64(It.Is(s => s.StartsWith("OwnerToNFTokenCount:")))) - .Returns((key) => + return this.ownerToNFTokenCount[key]; + } + + return default(ulong); + }); + } + + private void SetupOwnerToOperators() + { + this.persistentStateMock.Setup(p => p.SetBool(It.Is(s => s.StartsWith("OwnerToOperator:", StringComparison.Ordinal)), It.IsAny())) + .Callback((key, value) => + { + if (this.ownerToOperator.ContainsKey(key)) { - if (this.ownerToNFTokenCount.ContainsKey(key)) - { - return this.ownerToNFTokenCount[key]; - } - - return default(ulong); - }); - } - - private void SetupOwnerToOperators() - { - this.persistentStateMock.Setup(p => p.SetBool(It.Is(s => s.StartsWith("OwnerToOperator:", StringComparison.Ordinal)), It.IsAny())) - .Callback((key, value) => + this.ownerToOperator[key] = value; + } + else { - if (this.ownerToOperator.ContainsKey(key)) - { - this.ownerToOperator[key] = value; - } - else - { - this.ownerToOperator.Add(key, value); - } - }); - this.persistentStateMock.Setup(p => p.GetBool(It.Is(s => s.StartsWith("OwnerToOperator:")))) - .Returns((key) => + this.ownerToOperator.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetBool(It.Is(s => s.StartsWith("OwnerToOperator:")))) + .Returns((key) => + { + if (this.ownerToOperator.ContainsKey(key)) { - if (this.ownerToOperator.ContainsKey(key)) - { - return this.ownerToOperator[key]; - } - - return default(bool); - }); - } - - private void SetupIdToApproval() - { - this.persistentStateMock.Setup(p => p.SetAddress(It.Is(s => s.StartsWith("IdToApproval:", StringComparison.Ordinal)), It.IsAny
())) - .Callback((key, value) => + return this.ownerToOperator[key]; + } + + return default(bool); + }); + } + + private void SetupIdToApproval() + { + this.persistentStateMock.Setup(p => p.SetAddress(It.Is(s => s.StartsWith("IdToApproval:", StringComparison.Ordinal)), It.IsAny
())) + .Callback((key, value) => + { + if (this.idToApproval.ContainsKey(key)) + { + this.idToApproval[key] = value; + } + else { - if (this.idToApproval.ContainsKey(key)) - { - this.idToApproval[key] = value; - } - else - { - this.idToApproval.Add(key, value); - } - }); - this.persistentStateMock.Setup(p => p.GetAddress(It.Is(s => s.StartsWith("IdToApproval:")))) - .Returns((key) => + this.idToApproval.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetAddress(It.Is(s => s.StartsWith("IdToApproval:")))) + .Returns((key) => + { + if (this.idToApproval.ContainsKey(key)) { - if (this.idToApproval.ContainsKey(key)) - { - return this.idToApproval[key]; - } + return this.idToApproval[key]; + } - return Address.Zero; - }); + return Address.Zero; + }); - this.persistentStateMock.Setup(p => p.Clear(It.Is(s => s.StartsWith("IdToApproval:")))) - .Callback((key) => + this.persistentStateMock.Setup(p => p.Clear(It.Is(s => s.StartsWith("IdToApproval:")))) + .Callback((key) => + { + this.idToApproval.Remove(key); + }); + } + + private void SetupIdToOwner() + { + this.persistentStateMock.Setup(p => p.SetAddress(It.Is(s => s.StartsWith("IdToOwner:", StringComparison.Ordinal)), It.IsAny
())) + .Callback((key, value) => + { + if (this.idToOwner.ContainsKey(key)) { - this.idToApproval.Remove(key); - }); - } - - private void SetupIdToOwner() - { - this.persistentStateMock.Setup(p => p.SetAddress(It.Is(s => s.StartsWith("IdToOwner:", StringComparison.Ordinal)), It.IsAny
())) - .Callback((key, value) => + this.idToOwner[key] = value; + } + else { - if (this.idToOwner.ContainsKey(key)) - { - this.idToOwner[key] = value; - } - else - { - this.idToOwner.Add(key, value); - } - }); - this.persistentStateMock.Setup(p => p.GetAddress(It.Is(s => s.StartsWith("IdToOwner:")))) - .Returns((key) => + this.idToOwner.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetAddress(It.Is(s => s.StartsWith("IdToOwner:")))) + .Returns((key) => + { + if (this.idToOwner.ContainsKey(key)) { - if (this.idToOwner.ContainsKey(key)) - { - return this.idToOwner[key]; - } + return this.idToOwner[key]; + } - return Address.Zero; - }); + return Address.Zero; + }); - this.persistentStateMock.Setup(p => p.Clear(It.Is(s => s.StartsWith("IdToOwner:")))) - .Callback((key) => + this.persistentStateMock.Setup(p => p.Clear(It.Is(s => s.StartsWith("IdToOwner:")))) + .Callback((key) => + { + this.idToOwner.Remove(key); + }); + } + + private void SetupSupportedInterfaces() + { + this.persistentStateMock.Setup(p => p.SetBool(It.Is(s => s.StartsWith("SupportedInterface:", StringComparison.Ordinal)), It.IsAny())) + .Callback((key, value) => + { + if (this.supportedInterfaces.ContainsKey(key)) { - this.idToOwner.Remove(key); - }); - } - - private void SetupSupportedInterfaces() - { - this.persistentStateMock.Setup(p => p.SetBool(It.Is(s => s.StartsWith("SupportedInterface:", StringComparison.Ordinal)), It.IsAny())) - .Callback((key, value) => + this.supportedInterfaces[key] = value; + } + else { - if (this.supportedInterfaces.ContainsKey(key)) - { - this.supportedInterfaces[key] = value; - } - else - { - this.supportedInterfaces.Add(key, value); - } - }); - this.persistentStateMock.Setup(p => p.GetBool(It.Is(s => s.StartsWith("SupportedInterface:")))) - .Returns((key) => + this.supportedInterfaces.Add(key, value); + } + }); + this.persistentStateMock.Setup(p => p.GetBool(It.Is(s => s.StartsWith("SupportedInterface:")))) + .Returns((key) => + { + if (this.supportedInterfaces.ContainsKey(key)) { - if (this.supportedInterfaces.ContainsKey(key)) - { - return this.supportedInterfaces[key]; - } - - return default(bool); - }); - } + return this.supportedInterfaces[key]; + } + + return default(bool); + }); } -} +} \ No newline at end of file