diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1017d8e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,315 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# 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/ + +# Visual Studio 2015 cache/options directory +.vs/a +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# 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 + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.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 + +# 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 + +# 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 +# TODO: 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 +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable 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 + +# 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 +node_modules/ +orleans.codegen.cs + +# 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 + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# 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 + +# Visual Studio Code +.vscode + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +*.DS_Store + +.vs/ +slnx.sqlite + +# Generated Protobuf classes +*.g.cs +*.c.cs +grpc_csharp_plugin +*Grpc.cs +**/Generated/*.cs + +launchSettings.json + +yarn.lock + +results/coverage.json +results/coverage.opencover.xml + +aelf.js + +scripts/contract_csharp_plugin +scripts/contract_csharp_plugin.exe + +**/BenchmarkDotNet.Artifacts/* + +Contracts.manifest + +# coverage result +coverage*.json +coverage*.xml + +# zip package +*.gz +*.zip + +# Contract patcher dlls +scripts/patcher/* + +tools +.dotnet +!.idea/ +.idea/.idea.AElf/.idea/contentModel.xml +.idea/.idea.AElf/.idea/encodings.xml +.idea/.idea.AElf/.idea/indexLayout.xml +.idea/.idea.AElf/.idea/misc.xml +.idea/.idea.AElf/.idea/modules.xml +.idea/.idea.AElf/.idea/projectSettingsUpdater.xml +.idea/.idea.AElf/.idea/vcs.xml +.idea/.idea.AElf/.idea/workspace.xml +.idea/.idea.AElf/riderModule.iml \ No newline at end of file diff --git a/AElf.Contract.Tools.targets b/AElf.Contract.Tools.targets new file mode 100755 index 00000000..ec65ceb1 --- /dev/null +++ b/AElf.Contract.Tools.targets @@ -0,0 +1,116 @@ + + + + ..\..\protobuf + ..\..\scripts\patcher + AElf.Boilerplate.ContractPatcher + + + + + Protobuf/Proto/aelf/* + + + + + + + ../../scripts/generate_contract_base.sh + ../../scripts/generate_contract_code.sh + ../../scripts/generate_contract_reference.sh + ../../scripts/generate_contract_stub.sh + ../../scripts/generate_event_only.sh + + + + + ..\..\scripts\generate_contract_base.bat + ..\..\scripts\generate_contract_code.bat + ..\..\scripts\generate_contract_reference.bat + ..\..\scripts\generate_contract_stub.bat + ..\..\scripts\generate_event_only.bat + + + + + + + $(ProjectDir)/Protobuf + protoc --proto_path=../../protobuf --csharp_out=internal_access:$(LocalProtobufDir)/Generated --csharp_opt=file_extension=.g.cs + + + $(ProjectDir)/Protobuf + protoc --proto_path=../../protobuf --csharp_out=$(LocalProtobufDir)/Generated --csharp_opt=file_extension=.g.cs + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + + + + + + + + -w + + -s + + -t + + + + + + + + + + ../../src/AElf.Boilerplate.ForestContract.Launcher/contracts + + + + + + + + + \ No newline at end of file diff --git a/CodeCoverage.runsettings b/CodeCoverage.runsettings new file mode 100755 index 00000000..17bce499 --- /dev/null +++ b/CodeCoverage.runsettings @@ -0,0 +1,17 @@ + + + + + + + + cobertura + [xunit.*]*,[*Tests]* + **/test/**/*.cs,**/src/**/*.cs, + Obsolete,GeneratedCodeAttribute + false + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100755 index 00000000..1d51c2db --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Forest.sln b/Forest.sln new file mode 100755 index 00000000..aebdb304 --- /dev/null +++ b/Forest.sln @@ -0,0 +1,86 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.202 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "contract", "contract", "{5C980166-C987-470D-AEE0-3858A170A903}" + ProjectSection(SolutionItems) = preProject + contract\Directory.Build.props = contract\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{21D85A91-5BB8-4912-8107-49435A461AAC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DBC47323-E6A2-42A2-A624-CE2EE6139567}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AElf.Boilerplate.MainChain", "src\AElf.Boilerplate.MainChain\AElf.Boilerplate.MainChain.csproj", "{F5B9CE98-80B4-46F0-A040-1BE75C276411}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Boilerplate.TestBase", "src\AElf.Boilerplate.TestBase\AElf.Boilerplate.TestBase.csproj", "{F72731A8-5803-4362-834F-0C066E64D4CD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Boilerplate.SystemTransactionGenerator", "src\AElf.Boilerplate.SystemTransactionGenerator\AElf.Boilerplate.SystemTransactionGenerator.csproj", "{217599C5-2CF0-411E-97B5-5657739CEE5D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Forest", "contract\Forest\Forest.csproj", "{AC06E580-EFC8-4D3F-BA4A-B2D2D1F65B9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Forest.Tests", "test\Forest.Tests\Forest.Tests.csproj", "{3631B538-C617-4FCE-9C9B-1D1B5F290A7F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Boilerplate.ContractPatcher", "src\AElf.Boilerplate.ContractPatcher\AElf.Boilerplate.ContractPatcher.csproj", "{DCC207EA-842A-4B54-858B-295CAA8947D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Contracts.Whitelist", "contract\AElf.Contracts.Whitelist\AElf.Contracts.Whitelist.csproj", "{459C75F9-019E-4403-B8B9-43D622C187BE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Contracts.NFT", "contract\AElf.Contracts.NFT\AElf.Contracts.NFT.csproj", "{57D062AC-F184-4A74-A3CD-F200E1F1600A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F5B9CE98-80B4-46F0-A040-1BE75C276411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5B9CE98-80B4-46F0-A040-1BE75C276411}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5B9CE98-80B4-46F0-A040-1BE75C276411}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5B9CE98-80B4-46F0-A040-1BE75C276411}.Release|Any CPU.Build.0 = Release|Any CPU + {3631B538-C617-4FCE-9C9B-1D1B5F290A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3631B538-C617-4FCE-9C9B-1D1B5F290A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3631B538-C617-4FCE-9C9B-1D1B5F290A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3631B538-C617-4FCE-9C9B-1D1B5F290A7F}.Release|Any CPU.Build.0 = Release|Any CPU + {9484A7A9-CA57-4FF4-9FD8-27BFEF92EDBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9484A7A9-CA57-4FF4-9FD8-27BFEF92EDBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9484A7A9-CA57-4FF4-9FD8-27BFEF92EDBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9484A7A9-CA57-4FF4-9FD8-27BFEF92EDBC}.Release|Any CPU.Build.0 = Release|Any CPU + {AC06E580-EFC8-4D3F-BA4A-B2D2D1F65B9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC06E580-EFC8-4D3F-BA4A-B2D2D1F65B9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC06E580-EFC8-4D3F-BA4A-B2D2D1F65B9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC06E580-EFC8-4D3F-BA4A-B2D2D1F65B9F}.Release|Any CPU.Build.0 = Release|Any CPU + {217599C5-2CF0-411E-97B5-5657739CEE5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {217599C5-2CF0-411E-97B5-5657739CEE5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {217599C5-2CF0-411E-97B5-5657739CEE5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {217599C5-2CF0-411E-97B5-5657739CEE5D}.Release|Any CPU.Build.0 = Release|Any CPU + {DCC207EA-842A-4B54-858B-295CAA8947D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DCC207EA-842A-4B54-858B-295CAA8947D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DCC207EA-842A-4B54-858B-295CAA8947D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DCC207EA-842A-4B54-858B-295CAA8947D5}.Release|Any CPU.Build.0 = Release|Any CPU + {459C75F9-019E-4403-B8B9-43D622C187BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {459C75F9-019E-4403-B8B9-43D622C187BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {459C75F9-019E-4403-B8B9-43D622C187BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {459C75F9-019E-4403-B8B9-43D622C187BE}.Release|Any CPU.Build.0 = Release|Any CPU + {57D062AC-F184-4A74-A3CD-F200E1F1600A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57D062AC-F184-4A74-A3CD-F200E1F1600A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57D062AC-F184-4A74-A3CD-F200E1F1600A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57D062AC-F184-4A74-A3CD-F200E1F1600A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F5B9CE98-80B4-46F0-A040-1BE75C276411} = {21D85A91-5BB8-4912-8107-49435A461AAC} + {3631B538-C617-4FCE-9C9B-1D1B5F290A7F} = {DBC47323-E6A2-42A2-A624-CE2EE6139567} + {AC06E580-EFC8-4D3F-BA4A-B2D2D1F65B9F} = {5C980166-C987-470D-AEE0-3858A170A903} + {217599C5-2CF0-411E-97B5-5657739CEE5D} = {21D85A91-5BB8-4912-8107-49435A461AAC} + {F72731A8-5803-4362-834F-0C066E64D4CD} = {21D85A91-5BB8-4912-8107-49435A461AAC} + {DCC207EA-842A-4B54-858B-295CAA8947D5} = {21D85A91-5BB8-4912-8107-49435A461AAC} + {459C75F9-019E-4403-B8B9-43D622C187BE} = {5C980166-C987-470D-AEE0-3858A170A903} + {57D062AC-F184-4A74-A3CD-F200E1F1600A} = {5C980166-C987-470D-AEE0-3858A170A903} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D7426B41-E959-40E4-9ED8-ED8D9C50F133} + EndGlobalSection +EndGlobal diff --git a/NuGet.Config b/NuGet.Config new file mode 100755 index 00000000..6991b79b --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 0a0c8186..002a31e0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ -# new branch # forest contract + +BRANCH | AZURE PIPELINES | TESTS | CODE COVERAGE +-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------- +MAIN | [![Build Status](https://dev.azure.com/eforest-finance/forest-contract/_apis/build/status/eforest-finance.forest-contract?branchName=main)](https://dev.azure.com/eforest-finance/forest-contract/_build/latest?definitionId=1&branchName=main) | [![Test Status](https://img.shields.io/azure-devops/tests/eforest-finance/forest-contract/1/main)](https://dev.azure.com/eforest-finance/forest-contract/_build/latest?definitionId=1&branchName=main) | [![codecov](https://codecov.io/gh/eforest-finance/forest-contract/branch/main/graph/badge.svg?token=O8ULTA26OV)](https://codecov.io/gh/eforest-finance/forest-contract) +DEV | [![Build Status](https://dev.azure.com/eforest-finance/forest-contract/_apis/build/status/eforest-finance.forest-contract?branchName=dev)](https://dev.azure.com/eforest-finance/forest-contract/_build/latest?definitionId=1&branchName=dev) | [![Test Status](https://img.shields.io/azure-devops/tests/eforest-finance/forest-contract/1/dev)](https://dev.azure.com/eforest-finance/forest-contract/_build/latest?definitionId=1&branchName=dev) | [![codecov](https://codecov.io/gh/eforest-finance/forest-contract/branch/dev/graph/badge.svg?token=O8ULTA26OV)](https://codecov.io/gh/eforest-finance/forest-contract) \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100755 index 00000000..8590eebb --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,101 @@ +jobs: + + # All tasks on Windows.... + - job: build_all_windows + displayName: Build all tasks (Windows) + timeoutInMinutes: 120 + pool: + vmImage: windows-latest + variables: + CI_TEST: true + steps: + - task: UseDotNet@2 + displayName: 'Install .NET Core SDK' + inputs: + version: 6.0.x + - script: choco install protoc --version=3.19.4 -y + displayName: 'Install protobuf' + - script: choco install unzip + displayName: 'Install unzip' + - task: BatchScript@1 + displayName: 'Download AElf build tools' + inputs: + filename: 'scripts/download_binary.bat' + - script: PowerShell.exe -file build.ps1 -target=Run-Unit-Tests + displayName: 'Build and Test' + - task: PublishTestResults@2 + condition: always() + inputs: + testRunner: VSTest + testResultsFiles: '**/*.trx' + # - task: reportgenerator@5 + # displayName: ReportGenerator + # inputs: + # reports: '$(Build.SourcesDirectory)/test/*/TestResults/*/coverage.cobertura.xml' + # targetdir: '$(Build.SourcesDirectory)/CodeCoverage' + # reporttypes: 'Cobertura' + # assemblyfilters: '-xunit*' + # - script: PowerShell.exe -file build.ps1 -target=Upload-Coverage-Azure + # displayName: 'Upload data to Codecov' + # All tasks on Linux + - job: build_all_linux + displayName: Build all tasks (Linux) + timeoutInMinutes: 120 + pool: + vmImage: ubuntu-latest + steps: + - task: UseDotNet@2 + displayName: 'Install .NET Core SDK' + inputs: + version: 6.0.x + - script: bash scripts/download_binary.sh + displayName: 'Download AElf build tools' + - script: bash scripts/install.sh + displayName: 'Install protobuf' + - script: bash build.sh --target=Test-with-Codecov + displayName: 'Build and Test' + - task: PublishTestResults@2 + condition: always() + inputs: + testRunner: VSTest + testResultsFiles: '**/*.trx' + - task: reportgenerator@5 + displayName: ReportGenerator + inputs: + reports: '$(Build.SourcesDirectory)/test/*/TestResults/*/coverage.cobertura.xml' + targetdir: '$(Build.SourcesDirectory)/CodeCoverage' + reporttypes: 'Cobertura' + assemblyfilters: '-xunit*' + - script: bash build.sh --target=Upload-Coverage-Azure + displayName: 'Upload data to Codecov' + # All tasks on macOS + - job: build_all_darwin + displayName: Build all tasks (macOS) + timeoutInMinutes: 120 + pool: + vmImage: macos-latest + variables: + CI_TEST: true + steps: + - task: UseDotNet@2 + displayName: 'Install .NET Core SDK' + inputs: + version: 6.0.x + - script: bash scripts/install_protobuf.sh + - script: bash scripts/download_binary.sh + - script: bash build.sh -target=Run-Unit-Tests + displayName: 'Build and Test' + - task: PublishTestResults@2 + condition: always() + inputs: + testRunner: VSTest + testResultsFiles: '**/*.trx' +# - task: reportgenerator@5 +# displayName: ReportGenerator +# inputs: +# reports: '$(Build.SourcesDirectory)/test/*/TestResults/*/coverage.cobertura.xml' +# targetdir: '$(Build.SourcesDirectory)/CodeCoverage' +# reporttypes: 'Cobertura' +# assemblyfilters: '-xunit*' +# - script: PowerShell.exe -file build.ps1 -target=Upload-Coverage-Azure +# displayName: 'Upload data to Codecov' \ No newline at end of file diff --git a/build.cake b/build.cake new file mode 100755 index 00000000..84cda515 --- /dev/null +++ b/build.cake @@ -0,0 +1,103 @@ +#tool nuget:?package=Codecov +#addin nuget:?package=Cake.Codecov&version=0.8.0 + +var target = Argument("target", "Default"); +var configuration = Argument("configuration", "Debug"); +var rootPath = "./"; +var srcPath = rootPath + "src/"; +var contractPath = rootPath + "contract/"; +var testPath = rootPath + "test/"; +var solution = rootPath + "Forest.sln"; + +Task("Clean") + .Description("clean up project cache") + .Does(() => +{ + CleanDirectories(srcPath + "**/bin"); + CleanDirectories(srcPath + "**/obj"); + CleanDirectories(contractPath + "**/bin"); + CleanDirectories(contractPath + "**/obj"); + CleanDirectories(testPath + "**/bin"); + CleanDirectories(testPath + "**/obj"); +}); + +Task("Restore") + .Description("restore project dependencies") + .Does(() => +{ + DotNetCoreRestore(solution, new DotNetCoreRestoreSettings + { + Verbosity = DotNetCoreVerbosity.Quiet, + Sources = new [] { "https://www.myget.org/F/aelf-project-dev/api/v3/index.json", "https://api.nuget.org/v3/index.json" } + }); +}); +Task("Build") + .Description("Compilation project") + .IsDependentOn("Clean") + .IsDependentOn("Restore") + .Does(() => +{ + var buildSetting = new DotNetCoreBuildSettings{ + NoRestore = true, + Configuration = configuration, + ArgumentCustomization = args => { + return args.Append("/clp:ErrorsOnly") + .Append("-v quiet");} + }; + + DotNetCoreBuild(solution, buildSetting); +}); + +Task("Test-with-Codecov") + .Description("operation tes") + .IsDependentOn("Build") + .Does(() => +{ + var testSetting = new DotNetCoreTestSettings{ + Configuration = configuration, + NoRestore = true, + NoBuild = true, + ArgumentCustomization = args => { + return args + .Append("--logger trx") + .Append("--settings CodeCoverage.runsettings") + .Append("--collect:\"XPlat Code Coverage\""); + } + }; + var testProjects = GetFiles("./test/*.Tests/*.csproj"); + var testProjectList = testProjects.OrderBy(p=>p.FullPath).ToList(); + foreach(var testProject in testProjectList) + { + DotNetCoreTest(testProject.FullPath, testSetting); + } +}); + +Task("Run-Unit-Tests") + .Description("operation test") + .IsDependentOn("Build") + .Does(() => +{ + var testSetting = new DotNetCoreTestSettings{ + Configuration = configuration, + NoRestore = true, + NoBuild = true, + ArgumentCustomization = args => { + return args.Append("--logger trx"); + } +}; + var testProjects = GetFiles("./test/*.Tests/*.csproj"); + + + foreach(var testProject in testProjects) + { + DotNetCoreTest(testProject.FullPath, testSetting); + } +}); + +Task("Upload-Coverage-Azure") + .Does(() => +{ + Codecov("./CodeCoverage/Cobertura.xml",EnvironmentVariable("CODECOV_TOKEN")); +}); + +RunTarget(target); diff --git a/build.config b/build.config new file mode 100755 index 00000000..015ef8ec --- /dev/null +++ b/build.config @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +CAKE_VERSION=0.37.0 +DOTNET_VERSION=6.0.300 diff --git a/build.ps1 b/build.ps1 new file mode 100755 index 00000000..e48d09a0 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,154 @@ +#!/usr/bin/env pwsh +$DotNetInstallerUri = 'https://dot.net/v1/dotnet-install.ps1'; +$DotNetUnixInstallerUri = 'https://dot.net/v1/dotnet-install.sh' +$DotNetChannel = 'LTS' +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent + +[string] $CakeVersion = '' +[string] $DotNetVersion= '' +foreach($line in Get-Content (Join-Path $PSScriptRoot 'build.config')) +{ + if ($line -like 'CAKE_VERSION=*') { + $CakeVersion = $line.SubString(13) + } + elseif ($line -like 'DOTNET_VERSION=*') { + $DotNetVersion =$line.SubString(15) + } +} + + +if ([string]::IsNullOrEmpty($CakeVersion) -or [string]::IsNullOrEmpty($DotNetVersion)) { + 'Failed to parse Cake / .NET Core SDK Version' + exit 1 +} + +# Make sure tools folder exists +$ToolPath = Join-Path $PSScriptRoot "tools" +if (!(Test-Path $ToolPath)) { + Write-Verbose "Creating tools directory..." + New-Item -Path $ToolPath -Type Directory -Force | out-null +} + + +if ($PSVersionTable.PSEdition -ne 'Core') { + # Attempt to set highest encryption available for SecurityProtocol. + # PowerShell will not set this by default (until maybe .NET 4.6.x). This + # will typically produce a message for PowerShell v2 (just an info + # message though) + try { + # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48) + # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't + # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is + # installed (.NET 4.5 is an in-place upgrade). + [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48 + } catch { + Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3' + } +} + +########################################################################### +# INSTALL .NET CORE CLI +########################################################################### + +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT=1 +$env:DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 + + +Function Remove-PathVariable([string]$VariableToRemove) +{ + $SplitChar = ';' + if ($IsMacOS -or $IsLinux) { + $SplitChar = ':' + } + + $path = [Environment]::GetEnvironmentVariable("PATH", "User") + if ($path -ne $null) + { + $newItems = $path.Split($SplitChar, [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } + [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join($SplitChar, $newItems), "User") + } + + $path = [Environment]::GetEnvironmentVariable("PATH", "Process") + if ($path -ne $null) + { + $newItems = $path.Split($SplitChar, [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } + [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join($SplitChar, $newItems), "Process") + } +} + +# Get .NET Core CLI path if installed. +$FoundDotNetCliVersion = $null; +if (Get-Command dotnet -ErrorAction SilentlyContinue) { + $FoundDotNetCliVersion = dotnet --version; +} + +if($FoundDotNetCliVersion -ne $DotNetVersion) { + $InstallPath = Join-Path $PSScriptRoot ".dotnet" + if (!(Test-Path $InstallPath)) { + New-Item -Path $InstallPath -ItemType Directory -Force | Out-Null; + } + + if ($IsMacOS -or $IsLinux) { + $ScriptPath = Join-Path $InstallPath 'dotnet-install.sh' + (New-Object System.Net.WebClient).DownloadFile($DotNetUnixInstallerUri, $ScriptPath); + & bash $ScriptPath --version "$DotNetVersion" --install-dir "$InstallPath" --channel "$DotNetChannel" --no-path + + Remove-PathVariable "$InstallPath" + $env:PATH = "$($InstallPath):$env:PATH" + } + else { + $ScriptPath = Join-Path $InstallPath 'dotnet-install.ps1' + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallerUri, $ScriptPath); + & $ScriptPath -Channel $DotNetChannel -Version $DotNetVersion -InstallDir $InstallPath; + + Remove-PathVariable "$InstallPath" + $env:PATH = "$InstallPath;$env:PATH" + } + $env:DOTNET_ROOT=$InstallPath +} + +########################################################################### +# INSTALL CAKE +########################################################################### + +# Make sure Cake has been installed. +[string] $CakeExePath = '' +[string] $CakeInstalledVersion = Get-Command dotnet-cake -ErrorAction SilentlyContinue | % {&$_.Source --version} + +if ($CakeInstalledVersion -eq $CakeVersion) { + # Cake found locally + $CakeExePath = (Get-Command dotnet-cake).Source +} +else { + $CakePath = [System.IO.Path]::Combine($ToolPath,'.store', 'cake.tool', $CakeVersion) # Old PowerShell versions Join-Path only supports one child path + + $CakeExePath = (Get-ChildItem -Path $ToolPath -Filter "dotnet-cake*" -File| ForEach-Object FullName | Select-Object -First 1) + + + if ((!(Test-Path -Path $CakePath -PathType Container)) -or (!(Test-Path $CakeExePath -PathType Leaf))) { + + if ((![string]::IsNullOrEmpty($CakeExePath)) -and (Test-Path $CakeExePath -PathType Leaf)) + { + & dotnet tool uninstall --tool-path $ToolPath Cake.Tool + } + + & dotnet tool install --tool-path $ToolPath --version $CakeVersion Cake.Tool + if ($LASTEXITCODE -ne 0) + { + 'Failed to install cake' + exit 1 + } + $CakeExePath = (Get-ChildItem -Path $ToolPath -Filter "dotnet-cake*" -File| ForEach-Object FullName | Select-Object -First 1) + } +} + +########################################################################### +# RUN BUILD SCRIPT +########################################################################### +& "$CakeExePath" ./build.cake --bootstrap +if ($LASTEXITCODE -eq 0) +{ + & "$CakeExePath" ./build.cake $args +} +exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..06bcee80 --- /dev/null +++ b/build.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Define varibles +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +source $SCRIPT_DIR/build.config +TOOLS_DIR=$SCRIPT_DIR/tools +CAKE_EXE=$TOOLS_DIR/dotnet-cake +CAKE_PATH=$TOOLS_DIR/.store/cake.tool/$CAKE_VERSION + +if [ "$CAKE_VERSION" = "" ] || [ "$DOTNET_VERSION" = "" ]; then + echo "An error occured while parsing Cake / .NET Core SDK version." + exit 1 +fi + +# Make sure the tools folder exist. +if [ ! -d "$TOOLS_DIR" ]; then + mkdir "$TOOLS_DIR" +fi + +########################################################################### +# INSTALL .NET CORE CLI +########################################################################### + +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 +export DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 + +DOTNET_INSTALLED_VERSION=$(dotnet --version 2>&1) + +if [ "$DOTNET_VERSION" != "$DOTNET_INSTALLED_VERSION" ]; then + echo "Installing .NET CLI..." + if [ ! -d "$SCRIPT_DIR/.dotnet" ]; then + mkdir "$SCRIPT_DIR/.dotnet" + fi + curl -Lsfo "$SCRIPT_DIR/.dotnet/dotnet-install.sh" https://dot.net/v1/dotnet-install.sh + bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --version $DOTNET_VERSION --install-dir .dotnet --no-path + export PATH="$SCRIPT_DIR/.dotnet":$PATH + export DOTNET_ROOT="$SCRIPT_DIR/.dotnet" +fi + +########################################################################### +# INSTALL CAKE +########################################################################### + +CAKE_INSTALLED_VERSION=$(dotnet-cake --version 2>&1) + +if [ "$CAKE_VERSION" != "$CAKE_INSTALLED_VERSION" ]; then + if [ ! -f "$CAKE_EXE" ] || [ ! -d "$CAKE_PATH" ]; then + if [ -f "$CAKE_EXE" ]; then + dotnet tool uninstall --tool-path $TOOLS_DIR Cake.Tool + fi + + echo "Installing Cake $CAKE_VERSION..." + dotnet tool install --tool-path $TOOLS_DIR --version $CAKE_VERSION Cake.Tool + if [ $? -ne 0 ]; then + echo "An error occured while installing Cake." + exit 1 + fi + fi + + # Make sure that Cake has been installed. + if [ ! -f "$CAKE_EXE" ]; then + echo "Could not find Cake.exe at '$CAKE_EXE'." + exit 1 + fi +else + CAKE_EXE="dotnet-cake" +fi + +########################################################################### +# RUN BUILD SCRIPT +########################################################################### + +# Start Cake +(exec "$CAKE_EXE" build.cake --bootstrap) && (exec "$CAKE_EXE" build.cake "$@") \ No newline at end of file diff --git a/codecov.sh b/codecov.sh new file mode 100755 index 00000000..6b97c5b6 --- /dev/null +++ b/codecov.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +TOKEN=$1 +rm -r ./test/*.Tests/TestResults +rm -r CodeCoverage +for name in `ls ./test/*.Tests/*.csproj | awk '{print $NF}'`; +do + echo ${name} + dotnet test ${name} --logger trx --settings CodeCoverage.runsettings --collect:"XPlat Code Coverage" +done +reportgenerator /test/*/TestResults/*/coverage.cobertura.xml -reports:./test/*/TestResults/*/coverage.cobertura.xml -targetdir:./CodeCoverage -reporttypes:Cobertura -assemblyfilters:-xunit* +codecov -f ./CodeCoverage/Cobertura.xml -t ${TOKEN} \ No newline at end of file diff --git a/codecov.yml b/codecov.yml new file mode 100755 index 00000000..23be4053 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,3 @@ +codecov: + notify: + after_n_builds: 1 \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/AElf.Contracts.NFT.csproj b/contract/AElf.Contracts.NFT/AElf.Contracts.NFT.csproj new file mode 100644 index 00000000..c14f0319 --- /dev/null +++ b/contract/AElf.Contracts.NFT/AElf.Contracts.NFT.csproj @@ -0,0 +1,66 @@ + + + net6.0 + AElf.Contracts.NFT + true + NFT Contract is a contract that implements Non-Fungible-Token functionality like creation, issuance and + transfer. + + + + + true + + + + true + + + + + Protobuf\Proto\authority_info.proto + + + + + + Protobuf\Proto\reference\acs0.proto + + + Protobuf\Proto\reference\acs3.proto + + + Protobuf\Proto\reference\acs6.proto + + + Protobuf\Proto\reference\acs7.proto + + + Protobuf\Proto\reference\acs10.proto + + + Protobuf\Proto\reference\parliament_contract.proto + + + Protobuf\Proto\reference\token_contract.proto + + + + + + Protobuf\Proto\acs1.proto + + + Protobuf\Proto\acs2.proto + + + Protobuf\Proto\transaction_fee.proto + + + + + + Protobuf\Proto\nft_contract.proto + + + \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContractConstants.cs b/contract/AElf.Contracts.NFT/NFTContractConstants.cs new file mode 100644 index 00000000..84bbf9a1 --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContractConstants.cs @@ -0,0 +1,12 @@ +namespace AElf.Contracts.NFT; + +public partial class NFTContract +{ + private const int NumberMinLength = 9; + + private const string NftTypeMetadataKey = "aelf_nft_type"; + private const string NftBaseUriMetadataKey = "aelf_nft_base_uri"; + private const string NftTokenIdReuseMetadataKey = "aelf_nft_token_id_reuse"; + private const string AssembledNftsKey = "aelf_assembled_nfts"; + private const string AssembledFtsKey = "aelf_assembled_fts"; +} \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContractReferenceState.cs b/contract/AElf.Contracts.NFT/NFTContractReferenceState.cs new file mode 100644 index 00000000..1f6dc39f --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContractReferenceState.cs @@ -0,0 +1,15 @@ +using AElf.Contracts.MultiToken; +using AElf.Contracts.Parliament; +using AElf.Standards.ACS6; + +namespace AElf.Contracts.NFT; + +public partial class NFTContractState +{ + internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; } + + internal RandomNumberProviderContractContainer.RandomNumberProviderContractReferenceState + RandomNumberProviderContract { get; set; } + + internal ParliamentContractContainer.ParliamentContractReferenceState ParliamentContract { get; set; } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContractState.cs b/contract/AElf.Contracts.NFT/NFTContractState.cs new file mode 100644 index 00000000..ad35848d --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContractState.cs @@ -0,0 +1,46 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.NFT; + +public partial class NFTContractState : ContractState +{ + public Int64State NftProtocolNumberFlag { get; set; } + public Int32State CurrentSymbolNumberLength { get; set; } + public MappedState IsCreatedMap { get; set; } + + /// + /// Symbol -> Addresses have permission to mint this token + /// + public MappedState MinterListMap { get; set; } + + public MappedState NftInfoMap { get; set; } + + /// + /// Token Hash -> Owner Address -> Balance + /// + public MappedState BalanceMap { get; set; } + + public MappedState NftProtocolMap { get; set; } + + /// + /// Token Hash -> Owner Address -> Spender Address -> Approved Amount + /// Need to record approved by whom. + /// + public MappedState AllowanceMap { get; set; } + + public MappedState AssembledNftsMap { get; set; } + public MappedState AssembledFtsMap { get; set; } + + public MappedState NFTTypeShortNameMap { get; set; } + public MappedState NFTTypeFullNameMap { get; set; } + + public SingletonState
ParliamentDefaultAddress { get; set; } + + public SingletonState NFTTypes { get; set; } + + /// + /// Symbol (Protocol) -> Owner Address -> Operator Address List + /// + public MappedState OperatorMap { get; set; } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContract_ACS1.cs b/contract/AElf.Contracts.NFT/NFTContract_ACS1.cs new file mode 100644 index 00000000..43c05b56 --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContract_ACS1.cs @@ -0,0 +1,45 @@ +using AElf.Standards.ACS1; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.NFT; + +public partial class NFTContract +{ + public override Empty SetMethodFee(MethodFees input) + { + return new Empty(); + } + + public override Empty ChangeMethodFeeController(AuthorityInfo input) + { + return new Empty(); + } + + #region Views + + public override MethodFees GetMethodFee(StringValue input) + { + if (input.Value == nameof(Create)) + return new MethodFees + { + MethodName = input.Value, + Fees = + { + new MethodFee + { + Symbol = Context.Variables.NativeSymbol, + BasicFee = 100_00000000 + } + } + }; + + return new MethodFees(); + } + + public override AuthorityInfo GetMethodFeeController(Empty input) + { + return new AuthorityInfo(); + } + + #endregion +} \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContract_Create.cs b/contract/AElf.Contracts.NFT/NFTContract_Create.cs new file mode 100644 index 00000000..b8ba7669 --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContract_Create.cs @@ -0,0 +1,202 @@ +using AElf.Contracts.MultiToken; +using AElf.Sdk.CSharp; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.NFT; + +public partial class NFTContract : NFTContractContainer.NFTContractBase +{ + /// + /// The Create method can only be executed in aelf MainChain. + /// + /// + /// + public override StringValue Create(CreateInput input) + { + Assert(Context.ChainId == ChainHelper.ConvertBase58ToChainId("AELF"), + "NFT Protocol can only be created at aelf mainchain."); + MakeSureTokenContractAddressSet(); + MakeSureRandomNumberProviderContractAddressSet(); + var symbol = GetSymbol(input.NftType); + var tokenExternalInfo = GetTokenExternalInfo(input); + var creator = input.Creator ?? Context.Sender; + var tokenCreateInput = new MultiToken.CreateInput + { + Symbol = symbol, + Decimals = 0, // Fixed + Issuer = creator, + IsBurnable = input.IsBurnable, + IssueChainId = input.IssueChainId, + TokenName = input.ProtocolName, + TotalSupply = input.TotalSupply, + ExternalInfo = tokenExternalInfo + }; + State.TokenContract.Create.Send(tokenCreateInput); + + var minterList = input.MinterList ?? new MinterList(); + if (!minterList.Value.Contains(creator)) minterList.Value.Add(creator); + State.MinterListMap[symbol] = minterList; + + var protocolInfo = new NFTProtocolInfo + { + Symbol = symbol, + BaseUri = input.BaseUri, + TotalSupply = tokenCreateInput.TotalSupply, + Creator = tokenCreateInput.Issuer, + Metadata = new Metadata { Value = { tokenExternalInfo.Value } }, + ProtocolName = tokenCreateInput.TokenName, + IsTokenIdReuse = input.IsTokenIdReuse, + IssueChainId = tokenCreateInput.IssueChainId, + IsBurnable = tokenCreateInput.IsBurnable, + NftType = input.NftType + }; + State.NftProtocolMap[symbol] = protocolInfo; + + Context.Fire(new NFTProtocolCreated + { + Symbol = tokenCreateInput.Symbol, + Creator = tokenCreateInput.Issuer, + IsBurnable = tokenCreateInput.IsBurnable, + IssueChainId = tokenCreateInput.IssueChainId, + ProtocolName = tokenCreateInput.TokenName, + TotalSupply = tokenCreateInput.TotalSupply, + Metadata = protocolInfo.Metadata, + BaseUri = protocolInfo.BaseUri, + IsTokenIdReuse = protocolInfo.IsTokenIdReuse, + NftType = protocolInfo.NftType + }); + + return new StringValue + { + Value = symbol + }; + } + + public override Empty CrossChainCreate(CrossChainCreateInput input) + { + MakeSureTokenContractAddressSet(); + InitialNFTTypeNameMap(); + Assert(State.NftProtocolMap[input.Symbol] == null, $"Protocol {input.Symbol} already created."); + var tokenInfo = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput + { + Symbol = input.Symbol + }); + if (string.IsNullOrEmpty(tokenInfo.Symbol)) + throw new AssertionException($"Token info {input.Symbol} not exists."); + + var baseUri = tokenInfo.ExternalInfo.Value[NftBaseUriMetadataKey]; + var isTokenIdReuse = bool.Parse(tokenInfo.ExternalInfo.Value[NftTokenIdReuseMetadataKey]); + var nftTypeShortName = input.Symbol.Substring(0, 2); + var nftTypeFullName = State.NFTTypeFullNameMap[nftTypeShortName]; + if (nftTypeFullName == null) + throw new AssertionException( + $"Full name of {nftTypeShortName} not found. Use AddNFTType to add this new pair."); + + var nftProtocolInfo = new NFTProtocolInfo + { + Symbol = input.Symbol, + TotalSupply = tokenInfo.TotalSupply, + BaseUri = baseUri, + Creator = tokenInfo.Issuer, + IsBurnable = tokenInfo.IsBurnable, + IssueChainId = tokenInfo.IssueChainId, + IsTokenIdReuse = isTokenIdReuse, + Metadata = new Metadata { Value = { tokenInfo.ExternalInfo.Value } }, + ProtocolName = tokenInfo.TokenName, + NftType = nftTypeFullName + }; + State.NftProtocolMap[input.Symbol] = nftProtocolInfo; + + State.MinterListMap[input.Symbol] = new MinterList + { + Value = { nftProtocolInfo.Creator } + }; + + Context.Fire(new NFTProtocolCreated + { + Symbol = input.Symbol, + Creator = nftProtocolInfo.Creator, + IsBurnable = nftProtocolInfo.IsBurnable, + IssueChainId = nftProtocolInfo.IssueChainId, + ProtocolName = nftProtocolInfo.ProtocolName, + TotalSupply = nftProtocolInfo.TotalSupply, + Metadata = nftProtocolInfo.Metadata, + BaseUri = nftProtocolInfo.BaseUri, + IsTokenIdReuse = isTokenIdReuse, + NftType = nftProtocolInfo.NftType + }); + return new Empty(); + } + + public override Empty AddNFTType(AddNFTTypeInput input) + { + AssertSenderIsParliamentDefaultAddress(); + InitialNFTTypeNameMap(); + var fullName = input.FullName; + Assert(input.ShortName.Length == 2, "Incorrect short name."); + Assert(State.NFTTypeFullNameMap[input.ShortName] == null, $"Short name {input.ShortName} already exists."); + Assert(State.NFTTypeShortNameMap[fullName] == null, $"Full name {fullName} already exists."); + State.NFTTypeFullNameMap[input.ShortName] = fullName; + State.NFTTypeShortNameMap[fullName] = input.ShortName; + var nftTypes = State.NFTTypes.Value; + nftTypes.Value.Add(input.ShortName, fullName); + State.NFTTypes.Value = nftTypes; + Context.Fire(new NFTTypeAdded + { + ShortName = input.ShortName, + FullName = input.FullName + }); + return new Empty(); + } + + public override Empty RemoveNFTType(StringValue input) + { + AssertSenderIsParliamentDefaultAddress(); + InitialNFTTypeNameMap(); + Assert(input.Value.Length == 2, "Incorrect short name."); + Assert(State.NFTTypeFullNameMap[input.Value] != null, $"Short name {input.Value} does not exist."); + var fullName = State.NFTTypeFullNameMap[input.Value]; + State.NFTTypeFullNameMap.Remove(input.Value); + State.NFTTypeShortNameMap.Remove(fullName); + var nftTypes = State.NFTTypes.Value; + nftTypes.Value.Remove(input.Value); + State.NFTTypes.Value = nftTypes; + Context.Fire(new NFTTypeRemoved + { + ShortName = input.Value + }); + return new Empty(); + } + + private void AssertSenderIsParliamentDefaultAddress() + { + if (State.ParliamentContract.Value == null) + State.ParliamentContract.Value = + Context.GetContractAddressByName(SmartContractConstants.ParliamentContractSystemName); + + if (State.ParliamentDefaultAddress.Value == null) + State.ParliamentDefaultAddress.Value = + State.ParliamentContract.GetDefaultOrganizationAddress.Call(new Empty()); + + Assert(Context.Sender == State.ParliamentDefaultAddress.Value, "No permission."); + } + + private ExternalInfo GetTokenExternalInfo(CreateInput input) + { + if (input.Metadata != null) AssertMetadataKeysAreCorrect(input.Metadata.Value.Keys); + + var tokenExternalInfo = input.Metadata == null + ? new ExternalInfo() + : new ExternalInfo + { + Value = { input.Metadata.Value } + }; + + // Add NFT Type to external info. + tokenExternalInfo.Value[NftTypeMetadataKey] = input.NftType; + // Add Uri to external info. + tokenExternalInfo.Value[NftBaseUriMetadataKey] = input.BaseUri; + tokenExternalInfo.Value[NftTokenIdReuseMetadataKey] = input.IsTokenIdReuse.ToString(); + return tokenExternalInfo; + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContract_Helpers.cs b/contract/AElf.Contracts.NFT/NFTContract_Helpers.cs new file mode 100644 index 00000000..6a197402 --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContract_Helpers.cs @@ -0,0 +1,124 @@ +using System.Collections.Generic; +using AElf.CSharp.Core; +using AElf.Sdk.CSharp; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.NFT; + +public partial class NFTContract +{ + private void MakeSureTokenContractAddressSet() + { + if (State.TokenContract.Value == null) + State.TokenContract.Value = + Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + } + + private void MakeSureRandomNumberProviderContractAddressSet() + { + if (State.RandomNumberProviderContract.Value == null) + State.RandomNumberProviderContract.Value = + Context.GetContractAddressByName(SmartContractConstants.ConsensusContractSystemName); + } + + private string GetSymbol(string nftType) + { + var randomNumber = GenerateSymbolNumber(); + State.IsCreatedMap[randomNumber] = true; + var shortName = State.NFTTypeShortNameMap[nftType]; + if (shortName == null) + { + InitialNFTTypeNameMap(); + shortName = State.NFTTypeShortNameMap[nftType]; + if (shortName == null) throw new AssertionException($"Short name of NFT Type {nftType} not found."); + } + + return $"{shortName}{randomNumber}"; + } + + private NFTTypes InitialNFTTypeNameMap() + { + if (State.NFTTypes.Value != null) return State.NFTTypes.Value; + + var nftTypes = new NFTTypes(); + nftTypes.Value.Add("XX", NFTType.Any.ToString()); + nftTypes.Value.Add("AR", NFTType.Art.ToString()); + nftTypes.Value.Add("MU", NFTType.Music.ToString()); + nftTypes.Value.Add("DN", NFTType.DomainNames.ToString()); + nftTypes.Value.Add("VW", NFTType.VirtualWorlds.ToString()); + nftTypes.Value.Add("TC", NFTType.TradingCards.ToString()); + nftTypes.Value.Add("CO", NFTType.Collectables.ToString()); + nftTypes.Value.Add("SP", NFTType.Sports.ToString()); + nftTypes.Value.Add("UT", NFTType.Utility.ToString()); + nftTypes.Value.Add("BA", NFTType.Badges.ToString()); + State.NFTTypes.Value = nftTypes; + + foreach (var pair in nftTypes.Value) + { + State.NFTTypeShortNameMap[pair.Value] = pair.Key; + State.NFTTypeFullNameMap[pair.Key] = pair.Value; + } + + return nftTypes; + } + + private long GenerateSymbolNumber() + { + var length = GetCurrentNumberLength(); + var from = 1L; + for (var i = 1; i < length; i++) from = from.Mul(10); + + var randomBytes = State.RandomNumberProviderContract.GetRandomBytes.Call(new Int64Value + { + Value = Context.CurrentHeight.Sub(1) + }.ToBytesValue()); + var randomHash = + HashHelper.ConcatAndCompute(HashHelper.ComputeFrom(Context.Sender), + HashHelper.ComputeFrom(randomBytes)); + long randomNumber; + do + { + randomNumber = Context.ConvertHashToInt64(randomHash, from, from.Mul(10)); + } while (State.IsCreatedMap[randomNumber]); + + return randomNumber; + } + + private int GetCurrentNumberLength() + { + if (State.CurrentSymbolNumberLength.Value == 0) State.CurrentSymbolNumberLength.Value = NumberMinLength; + + var flag = State.NftProtocolNumberFlag.Value; + + if (flag == 0) + { + // Initial protocol number flag. + var protocolNumber = 1; + for (var i = 1; i < State.CurrentSymbolNumberLength.Value; i++) protocolNumber = protocolNumber.Mul(10); + + State.NftProtocolNumberFlag.Value = protocolNumber; + flag = protocolNumber; + } + + var upperNumberFlag = flag.Mul(2); + if (upperNumberFlag.ToString().Length > State.CurrentSymbolNumberLength.Value) + { + var newSymbolNumberLength = State.CurrentSymbolNumberLength.Value.Add(1); + State.CurrentSymbolNumberLength.Value = newSymbolNumberLength; + var protocolNumber = 1; + for (var i = 1; i < newSymbolNumberLength; i++) protocolNumber = protocolNumber.Mul(10); + + State.NftProtocolNumberFlag.Value = protocolNumber; + return newSymbolNumberLength; + } + + return State.CurrentSymbolNumberLength.Value; + } + + private void AssertMetadataKeysAreCorrect(IEnumerable metadataKeys) + { + var reservedMetadataKey = GetNftMetadataReservedKeys(); + foreach (var metadataKey in metadataKeys) + Assert(!reservedMetadataKey.Contains(metadataKey), $"Metadata key {metadataKey} is reserved."); + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContract_UseChain.cs b/contract/AElf.Contracts.NFT/NFTContract_UseChain.cs new file mode 100644 index 00000000..a0b0ab48 --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContract_UseChain.cs @@ -0,0 +1,464 @@ +using System.Linq; +using AElf.Contracts.MultiToken; +using AElf.CSharp.Core; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.NFT; + +public partial class NFTContract +{ + public override Hash Mint(MintInput input) + { + if (input.Metadata != null && input.Metadata.Value.Any()) + AssertMetadataKeysAreCorrect(input.Metadata.Value.Keys); + + var nftMinted = PerformMint(input); + return nftMinted.TokenHash; + } + + public override Empty Transfer(TransferInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + DoTransfer(tokenHash, Context.Sender, input.To, input.Amount); + Context.Fire(new Transferred + { + From = Context.Sender, + To = input.To, + Amount = input.Amount, + Symbol = input.Symbol, + TokenId = input.TokenId, + Memo = input.Memo + }); + return new Empty(); + } + + /// + /// Throw Assertion Exception if amount is negative or balance insufficient. + /// Do nothing if amount is 0 or balance of from address is 0. + /// + /// + /// + /// + /// + /// + private void DoTransfer(Hash tokenHash, Address from, Address to, long amount) + { + if (amount < 0) throw new AssertionException("Invalid transfer amount."); + + if (amount == 0) return; + + Assert(State.BalanceMap[tokenHash][from] >= amount, "Insufficient balance."); + State.BalanceMap[tokenHash][from] = State.BalanceMap[tokenHash][from].Sub(amount); + State.BalanceMap[tokenHash][to] = State.BalanceMap[tokenHash][to].Add(amount); + } + + public override Empty TransferFrom(TransferFromInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + var operatorList = State.OperatorMap[input.Symbol][input.From]; + var isOperator = operatorList?.Value.Contains(Context.Sender) ?? false; + if (!isOperator) + { + var allowance = State.AllowanceMap[tokenHash][input.From][Context.Sender]; + Assert(allowance >= input.Amount, "Not approved."); + State.AllowanceMap[tokenHash][input.From][Context.Sender] = allowance.Sub(input.Amount); + } + + DoTransfer(tokenHash, input.From, input.To, input.Amount); + Context.Fire(new Transferred + { + From = input.From, + To = input.To, + Amount = input.Amount, + Symbol = input.Symbol, + TokenId = input.TokenId, + Memo = input.Memo + }); + return new Empty(); + } + + public override Empty Burn(BurnInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + var nftInfo = GetNFTInfoByTokenHash(tokenHash); + var nftProtocolInfo = State.NftProtocolMap[input.Symbol]; + Assert(nftProtocolInfo.IsBurnable, + $"NFT Protocol {nftProtocolInfo.ProtocolName} of symbol {nftProtocolInfo.Symbol} is not burnable."); + var minterList = State.MinterListMap[input.Symbol] ?? new MinterList(); + Assert( + State.BalanceMap[tokenHash][Context.Sender] >= input.Amount && + minterList.Value.Contains(Context.Sender), + "No permission."); + State.BalanceMap[tokenHash][Context.Sender] = State.BalanceMap[tokenHash][Context.Sender].Sub(input.Amount); + nftProtocolInfo.Supply = nftProtocolInfo.Supply.Sub(input.Amount); + nftInfo.Quantity = nftInfo.Quantity.Sub(input.Amount); + + State.NftProtocolMap[input.Symbol] = nftProtocolInfo; + if (nftInfo.Quantity == 0 && !nftProtocolInfo.IsTokenIdReuse) nftInfo.IsBurned = true; + + State.NftInfoMap[tokenHash] = nftInfo; + + Context.Fire(new Burned + { + Burner = Context.Sender, + Symbol = input.Symbol, + Amount = input.Amount, + TokenId = input.TokenId + }); + return new Empty(); + } + + public override Hash Assemble(AssembleInput input) + { + if (input.Metadata != null && input.Metadata.Value.Any()) + AssertMetadataKeysAreCorrect(input.Metadata.Value.Keys); + + var metadata = input.Metadata ?? new Metadata(); + + if (input.AssembledNfts.Value.Any()) + { + metadata.Value[AssembledNftsKey] = input.AssembledNfts.ToString(); + // Check owner. + foreach (var pair in input.AssembledNfts.Value) + { + var nftHash = Hash.LoadFromHex(pair.Key); + var nftInfo = GetNFTInfoByTokenHash(nftHash); + Assert(State.BalanceMap[nftHash][Context.Sender] >= pair.Value, + $"Insufficient balance of {nftInfo.Symbol}{nftInfo.TokenId}."); + DoTransfer(nftHash, Context.Sender, Context.Self, pair.Value); + } + } + + if (input.AssembledFts.Value.Any()) + { + metadata.Value[AssembledFtsKey] = input.AssembledFts.ToString(); + // Check balance and allowance. + foreach (var pair in input.AssembledFts.Value) + { + var symbol = pair.Key; + var amount = pair.Value; + var balance = State.TokenContract.GetBalance.Call(new MultiToken.GetBalanceInput + { + Owner = Context.Sender, + Symbol = symbol + }).Balance; + Assert(balance >= amount, $"Insufficient balance of {symbol}"); + var allowance = State.TokenContract.GetAllowance.Call(new MultiToken.GetAllowanceInput + { + Owner = Context.Sender, + Spender = Context.Self, + Symbol = symbol + }).Allowance; + Assert(allowance >= amount, $"Insufficient allowance of {symbol}"); + State.TokenContract.TransferFrom.Send(new MultiToken.TransferFromInput + { + From = Context.Sender, + To = Context.Self, + Symbol = symbol, + Amount = amount + }); + } + } + + var mingInput = new MintInput + { + Symbol = input.Symbol, + Alias = input.Alias, + Owner = input.Owner, + Uri = input.Uri, + Metadata = metadata, + TokenId = input.TokenId + }; + + var nftMinted = PerformMint(mingInput, true); + if (input.AssembledNfts.Value.Any()) State.AssembledNftsMap[nftMinted.TokenHash] = input.AssembledNfts; + + if (input.AssembledFts.Value.Any()) State.AssembledFtsMap[nftMinted.TokenHash] = input.AssembledFts; + + Context.Fire(new Assembled + { + Symbol = input.Symbol, + TokenId = nftMinted.TokenId, + AssembledNfts = input.AssembledNfts, + AssembledFts = input.AssembledFts + }); + + return nftMinted.TokenHash; + } + + public override Empty Disassemble(DisassembleInput input) + { + Burn(new BurnInput + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Amount = 1 + }); + + var receiver = input.Owner ?? Context.Sender; + + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + var assembledNfts = State.AssembledNftsMap[tokenHash].Clone(); + if (assembledNfts != null) + { + var nfts = assembledNfts; + foreach (var pair in nfts.Value) DoTransfer(Hash.LoadFromHex(pair.Key), Context.Self, receiver, pair.Value); + + State.AssembledNftsMap.Remove(tokenHash); + } + + var assembledFts = State.AssembledFtsMap[tokenHash].Clone(); + if (assembledFts != null) + { + var fts = assembledFts; + foreach (var pair in fts.Value) + State.TokenContract.Transfer.Send(new MultiToken.TransferInput + { + Symbol = pair.Key, + Amount = pair.Value, + To = receiver + }); + + State.AssembledFtsMap.Remove(tokenHash); + } + + Context.Fire(new Disassembled + { + Symbol = input.Symbol, + TokenId = input.TokenId, + DisassembledNfts = assembledNfts ?? new AssembledNfts(), + DisassembledFts = assembledFts ?? new AssembledFts() + }); + + return new Empty(); + } + + public override Empty ApproveProtocol(ApproveProtocolInput input) + { + Assert(State.NftProtocolMap[input.Symbol] != null, $"Protocol {input.Symbol} not exists."); + var operatorList = State.OperatorMap[input.Symbol][Context.Sender] ?? new AddressList(); + switch (input.Approved) + { + case true when !operatorList.Value.Contains(input.Operator): + operatorList.Value.Add(input.Operator); + break; + case false when operatorList.Value.Contains(input.Operator): + operatorList.Value.Remove(input.Operator); + break; + } + + State.OperatorMap[input.Symbol][Context.Sender] = operatorList; + return new Empty(); + } + + public override Empty Recast(RecastInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + var minterList = State.MinterListMap[input.Symbol] ?? new MinterList(); + Assert(minterList.Value.Contains(Context.Sender), "No permission."); + var nftInfo = GetNFTInfoByTokenHash(tokenHash); + Assert(nftInfo.Quantity != 0 && nftInfo.Quantity == State.BalanceMap[tokenHash][Context.Sender], + "Do not support recast."); + if (input.Alias != null) nftInfo.Alias = input.Alias; + + if (input.Uri != null) nftInfo.Uri = input.Uri; + + var oldMetadata = nftInfo.Metadata.Clone(); + var metadata = new Metadata(); + // Need to keep reserved metadata key. + foreach (var reservedKey in GetNftMetadataReservedKeys()) + { + if (oldMetadata.Value.ContainsKey(reservedKey)) + metadata.Value[reservedKey] = oldMetadata.Value[reservedKey]; + + if (input.Metadata.Value.ContainsKey(reservedKey)) input.Metadata.Value.Remove(reservedKey); + } + + metadata.Value.Add(input.Metadata.Value); + nftInfo.Metadata = metadata; + + State.NftInfoMap[tokenHash] = nftInfo; + Context.Fire(new Recasted + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OldMetadata = oldMetadata, + NewMetadata = nftInfo.Metadata, + Alias = nftInfo.Alias, + Uri = nftInfo.Uri + }); + return new Empty(); + } + + public override Empty Approve(ApproveInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + State.AllowanceMap[tokenHash][Context.Sender][input.Spender] = input.Amount; + Context.Fire(new Approved + { + Owner = Context.Sender, + Spender = input.Spender, + Symbol = input.Symbol, + Amount = input.Amount, + TokenId = input.TokenId + }); + return new Empty(); + } + + public override Empty UnApprove(UnApproveInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + var oldAllowance = State.AllowanceMap[tokenHash][Context.Sender][input.Spender]; + var currentAllowance = oldAllowance.Sub(input.Amount); + if (currentAllowance <= 0) currentAllowance = 0; + + State.AllowanceMap[tokenHash][Context.Sender][input.Spender] = currentAllowance; + + Context.Fire(new UnApproved + { + Owner = Context.Sender, + Spender = input.Spender, + Symbol = input.Symbol, + CurrentAllowance = currentAllowance, + TokenId = input.TokenId + }); + return new Empty(); + } + + private Hash CalculateTokenHash(string symbol, long tokenId) + { + return HashHelper.ComputeFrom($"{symbol}{tokenId}"); + } + + public override Empty AddMinters(AddMintersInput input) + { + var protocolInfo = State.NftProtocolMap[input.Symbol]; + Assert(Context.Sender == protocolInfo.Creator, "No permission."); + var minterList = State.MinterListMap[protocolInfo.Symbol] ?? new MinterList(); + + foreach (var minter in input.MinterList.Value) + if (!minterList.Value.Contains(minter)) + minterList.Value.Add(minter); + + State.MinterListMap[input.Symbol] = minterList; + + Context.Fire(new MinterListAdded + { + Symbol = input.Symbol, + MinterList = input.MinterList + }); + return new Empty(); + } + + public override Empty RemoveMinters(RemoveMintersInput input) + { + var protocolInfo = State.NftProtocolMap[input.Symbol]; + Assert(Context.Sender == protocolInfo.Creator, "No permission."); + var minterList = State.MinterListMap[protocolInfo.Symbol]; + + foreach (var minter in input.MinterList.Value) + if (minterList.Value.Contains(minter)) + minterList.Value.Remove(minter); + + State.MinterListMap[input.Symbol] = minterList; + + Context.Fire(new MinterListRemoved + { + Symbol = input.Symbol, + MinterList = input.MinterList + }); + return new Empty(); + } + + private MinterList GetMinterList(TokenInfo tokenInfo) + { + var minterList = State.MinterListMap[tokenInfo.Symbol] ?? new MinterList(); + if (!minterList.Value.Contains(tokenInfo.Issuer)) minterList.Value.Add(tokenInfo.Issuer); + + return minterList; + } + + private NFTMinted PerformMint(MintInput input, bool isTokenIdMustBeUnique = false) + { + var tokenInfo = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput + { + Symbol = input.Symbol + }); + var protocolInfo = State.NftProtocolMap[input.Symbol]; + if (protocolInfo == null) throw new AssertionException($"Invalid NFT Token symbol: {input.Symbol}"); + + var tokenId = input.TokenId == 0 ? protocolInfo.Issued.Add(1) : input.TokenId; + var tokenHash = CalculateTokenHash(input.Symbol, tokenId); + var nftInfo = State.NftInfoMap[tokenHash]; + if (!protocolInfo.IsTokenIdReuse || isTokenIdMustBeUnique) + Assert(nftInfo == null, $"Token id {tokenId} already exists. Please assign a different token id."); + + var minterList = GetMinterList(tokenInfo); + Assert(minterList.Value.Contains(Context.Sender), "No permission to mint."); + Assert(tokenInfo.IssueChainId == Context.ChainId, "Incorrect chain."); + + var quantity = input.Quantity > 0 ? input.Quantity : 1; + protocolInfo.Supply = protocolInfo.Supply.Add(quantity); + protocolInfo.Issued = protocolInfo.Issued.Add(quantity); + Assert(protocolInfo.Issued <= protocolInfo.TotalSupply, "Total supply exceeded."); + State.NftProtocolMap[input.Symbol] = protocolInfo; + + // Inherit from protocol info. + var nftMetadata = protocolInfo.Metadata.Clone(); + if (input.Metadata != null) + foreach (var pair in input.Metadata.Value) + if (!nftMetadata.Value.ContainsKey(pair.Key)) + nftMetadata.Value[pair.Key] = pair.Value; + + if (nftInfo == null) + { + nftInfo = new NFTInfo + { + Symbol = input.Symbol, + Uri = input.Uri ?? string.Empty, + TokenId = tokenId, + Metadata = nftMetadata, + Minters = { Context.Sender }, + Quantity = quantity, + Alias = input.Alias + + // No need. + //BaseUri = protocolInfo.BaseUri, + //Creator = protocolInfo.Creator, + //ProtocolName = protocolInfo.ProtocolName + }; + } + else + { + nftInfo.Quantity = nftInfo.Quantity.Add(quantity); + if (!nftInfo.Minters.Contains(Context.Sender)) nftInfo.Minters.Add(Context.Sender); + } + + State.NftInfoMap[tokenHash] = nftInfo; + var owner = input.Owner ?? Context.Sender; + State.BalanceMap[tokenHash][owner] = State.BalanceMap[tokenHash][owner].Add(quantity); + + var nftMinted = new NFTMinted + { + Symbol = input.Symbol, + ProtocolName = protocolInfo.ProtocolName, + TokenId = tokenId, + Metadata = nftMetadata, + Owner = owner, + Minter = Context.Sender, + Quantity = quantity, + Alias = input.Alias, + BaseUri = protocolInfo.BaseUri, + Uri = input.Uri ?? string.Empty, + Creator = protocolInfo.Creator, + NftType = protocolInfo.NftType, + TotalQuantity = nftInfo.Quantity, + TokenHash = tokenHash + }; + Context.Fire(nftMinted); + + return nftMinted; + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.NFT/NFTContract_View.cs b/contract/AElf.Contracts.NFT/NFTContract_View.cs new file mode 100644 index 00000000..2ad97f22 --- /dev/null +++ b/contract/AElf.Contracts.NFT/NFTContract_View.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.NFT; + +public partial class NFTContract +{ + public override NFTProtocolInfo GetNFTProtocolInfo(StringValue input) + { + return State.NftProtocolMap[input.Value]; + } + + public override NFTInfo GetNFTInfo(GetNFTInfoInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + return GetNFTInfoByTokenHash(tokenHash); + } + + public override NFTInfo GetNFTInfoByTokenHash(Hash input) + { + var nftInfo = State.NftInfoMap[input]; + if (nftInfo == null) return new NFTInfo(); + var nftProtocolInfo = State.NftProtocolMap[nftInfo.Symbol]; + nftInfo.ProtocolName = nftProtocolInfo.ProtocolName; + nftInfo.Creator = nftProtocolInfo.Creator; + nftInfo.BaseUri = nftProtocolInfo.BaseUri; + nftInfo.NftType = nftProtocolInfo.NftType; + return nftInfo; + } + + public override GetBalanceOutput GetBalance(GetBalanceInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + var balance = State.BalanceMap[tokenHash][input.Owner]; + return new GetBalanceOutput + { + Owner = input.Owner, + Balance = balance, + TokenHash = tokenHash + }; + } + + public override GetBalanceOutput GetBalanceByTokenHash(GetBalanceByTokenHashInput input) + { + return new GetBalanceOutput + { + Owner = input.Owner, + Balance = State.BalanceMap[input.TokenHash][input.Owner], + TokenHash = input.TokenHash + }; + } + + public override GetAllowanceOutput GetAllowance(GetAllowanceInput input) + { + var tokenHash = CalculateTokenHash(input.Symbol, input.TokenId); + return new GetAllowanceOutput + { + Owner = input.Owner, + Spender = input.Spender, + TokenHash = tokenHash, + Allowance = State.AllowanceMap[tokenHash][input.Owner][input.Spender] + }; + } + + public override GetAllowanceOutput GetAllowanceByTokenHash(GetAllowanceByTokenHashInput input) + { + return new GetAllowanceOutput + { + Owner = input.Owner, + Spender = input.Spender, + TokenHash = input.TokenHash, + Allowance = State.AllowanceMap[input.TokenHash][input.Owner][input.Spender] + }; + } + + public override MinterList GetMinterList(StringValue input) + { + return State.MinterListMap[input.Value]; + } + + public override Hash CalculateTokenHash(CalculateTokenHashInput input) + { + return CalculateTokenHash(input.Symbol, input.TokenId); + } + + public override NFTTypes GetNFTTypes(Empty input) + { + return State.NFTTypes.Value ?? InitialNFTTypeNameMap(); + } + + public override AddressList GetOperatorList(GetOperatorListInput input) + { + return State.OperatorMap[input.Symbol][input.Owner]; + } + + private List GetNftMetadataReservedKeys() + { + return new List + { + NftTypeMetadataKey, + NftBaseUriMetadataKey, + AssembledNftsKey, + AssembledFtsKey, + NftTokenIdReuseMetadataKey + }; + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/AElf.Contracts.Whitelist.csproj b/contract/AElf.Contracts.Whitelist/AElf.Contracts.Whitelist.csproj new file mode 100644 index 00000000..8d997b73 --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/AElf.Contracts.Whitelist.csproj @@ -0,0 +1,34 @@ + + + + net6.0 + AElf.Contracts.Whitelist + true + WhiteList Contract. + + + + true + + + + true + + + + + Protobuf\Proto\authority_info.proto + + + + + + Protobuf\Proto\acs1.proto + + + + + Protobuf\Proto\whitelist_contract.proto + + + diff --git a/contract/AElf.Contracts.Whitelist/Extensions/WhitelistExtensions.cs b/contract/AElf.Contracts.Whitelist/Extensions/WhitelistExtensions.cs new file mode 100644 index 00000000..4f3ead52 --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/Extensions/WhitelistExtensions.cs @@ -0,0 +1,11 @@ +using AElf.Types; + +namespace AElf.Contracts.Whitelist.Extensions; + +public static class WhitelistExtensions +{ + public static Hash CalculateExtraInfoId(this Hash whitelistId, Hash projectId, string tagName) + { + return HashHelper.ComputeFrom($"{whitelistId}{projectId}{tagName}"); + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/WhitelistContract.cs b/contract/AElf.Contracts.Whitelist/WhitelistContract.cs new file mode 100644 index 00000000..0b5cd804 --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/WhitelistContract.cs @@ -0,0 +1,11 @@ +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.Whitelist; + +public partial class WhitelistContract : WhitelistContractContainer.WhitelistContractBase +{ + public override Empty Initialize(Empty input) + { + return new Empty(); + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/WhitelistContractState.cs b/contract/AElf.Contracts.Whitelist/WhitelistContractState.cs new file mode 100644 index 00000000..4ff74090 --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/WhitelistContractState.cs @@ -0,0 +1,71 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.Whitelist; + +public class WhitelistContractState : ContractState +{ + + /// + ///TagInfo id -> TagInfo(name,info). + /// + public MappedState TagInfoMap { get; set; } + + /// + ///Project Id -> whitelistId -> TagInfo Id List. + /// + public MappedState ManagerTagInfoMap { get; set; } + + /// + ///WhitelistId -> TagInfoId -> AddressList. + /// + public MappedState TagInfoIdAddressListMap { get; set; } + + /// + ///WhitelistId -> Address -> TagInfo Id. + /// + public MappedState AddressTagInfoIdMap { get; set; } + + /// + /// Manager address -> Whitelist id list. + /// + public MappedState WhitelistIdMap { get; set; } + + /// + /// whitelist id -> project id. + /// + public MappedState ProjectWhitelistIdMap { get; set; } + + /// + /// Whitelist id -> Whitelist Info. + /// + public MappedState WhitelistInfoMap { get; set; } + + /// + ///Project id -> Whitelist id list. + /// + /// + public MappedState WhitelistProjectMap { get; set; } + + /// + /// whitelist_id -> manager address list. + /// + public MappedState ManagerListMap { get; set; } + + /// + ///Subscribe_id -> SubscribeWhitelistInfo. + /// + public MappedState SubscribeWhitelistInfoMap { get; set; } + + /// + ///Subscribe_id -> manager address list. + /// + public MappedState SubscribeManagerListMap { get; set; } + + /// + ///Manager -> subscribe id list. + /// + public MappedState ManagerSubscribeIdListMap { get; set; } + + public MappedState ConsumedListMap { get; set; } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/WhitelistContract_ACS1.cs b/contract/AElf.Contracts.Whitelist/WhitelistContract_ACS1.cs new file mode 100644 index 00000000..f985b93e --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/WhitelistContract_ACS1.cs @@ -0,0 +1,31 @@ +using AElf.Standards.ACS1; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.Whitelist; + +public partial class WhitelistContract +{ + #region Views + + public override MethodFees GetMethodFee(StringValue input) + { + return new MethodFees(); + } + + public override AuthorityInfo GetMethodFeeController(Empty input) + { + return new AuthorityInfo(); + } + + #endregion + + public override Empty SetMethodFee(MethodFees input) + { + return new Empty(); + } + + public override Empty ChangeMethodFeeController(AuthorityInfo input) + { + return new Empty(); + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/WhitelistContract_Helpers.cs b/contract/AElf.Contracts.Whitelist/WhitelistContract_Helpers.cs new file mode 100644 index 00000000..581f0ee7 --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/WhitelistContract_Helpers.cs @@ -0,0 +1,275 @@ +using System.Collections.Generic; +using System.Linq; +using AElf.Contracts.Whitelist.Extensions; +using AElf.Sdk.CSharp; +using AElf.Types; + +namespace AElf.Contracts.Whitelist; + +public partial class WhitelistContract +{ + private Hash CalculateWhitelistHash(Address address, Hash projectId) + { + return Context.GenerateId(Context.Self, + ByteArrayHelper.ConcatArrays(address.ToByteArray(), projectId.ToByteArray())); + } + + private Hash CalculateSubscribeWhitelistHash(Address address, Hash projectId, Hash whitelistId) + { + return HashHelper.ComputeFrom($"{address}{projectId}{whitelistId}"); + } + + private Hash CalculateCloneWhitelistHash(Address address, Hash whitelistId) + { + return HashHelper.ComputeFrom($"{address}{whitelistId}"); + } + + private WhitelistInfo AssertWhitelistInfo(Hash whitelistId) + { + var whitelistInfo = State.WhitelistInfoMap[whitelistId]; + Assert(whitelistInfo != null, $"Whitelist not found.{whitelistId.ToHex()}"); + return whitelistInfo; + } + + private void AssertWhitelistIsAvailable(Hash whitelistId) + { + var whitelistInfo = State.WhitelistInfoMap[whitelistId]; + Assert(whitelistInfo.IsAvailable, $"Whitelist is not available.{whitelistId.ToHex()}"); + } + + private WhitelistInfo AssertWhitelistManager(Hash whitelistId) + { + var whitelistInfo = GetWhitelist(whitelistId); + Assert(whitelistInfo.Manager.Value.Contains(Context.Sender), + $"{Context.Sender} is not the manager of the whitelist."); + return whitelistInfo; + } + + private WhitelistInfo AssertWhitelistCreator(Hash whitelistId) + { + var whitelistInfo = GetWhitelist(whitelistId); + Assert(whitelistInfo.Creator == Context.Sender, $"{Context.Sender}No permission."); + return whitelistInfo; + } + + private void MakeSureProjectCorrect(Hash whitelistId, Hash projectId) + { + Assert(State.WhitelistProjectMap[projectId] != null, $"Incorrect project id.{projectId}"); + Assert(State.WhitelistProjectMap[projectId].WhitelistId.Contains(whitelistId), + $"Incorrect whitelist id.{whitelistId}"); + } + + private SubscribeWhitelistInfo AssertSubscribeWhitelistInfo(Hash subscribeId) + { + var subscribeInfo = State.SubscribeWhitelistInfoMap[subscribeId]; + Assert(subscribeInfo != null, $"Subscribe info not found.{subscribeId.ToHex()}"); + return subscribeInfo; + } + + private void AssertSubscribeManager(Hash subscribeId, Address address) + { + var managerList = State.SubscribeManagerListMap[subscribeId]; + if (!managerList.Value.Contains(address)) + { + throw new AssertionException($"No permission.{address}"); + } + } + + private void AssertSubscriber(Hash subscribeId) + { + var subscribeWhitelistInfo = State.SubscribeWhitelistInfoMap[subscribeId]; + Assert(subscribeWhitelistInfo.Subscriber == Context.Sender, $"{Context.Sender}No permission."); + } + + private ExtraInfoId AssertExtraInfoDuplicate(Hash whitelistId, ExtraInfoId id) + { + var whitelist = State.WhitelistInfoMap[whitelistId]; + foreach (var addressList in whitelist.ExtraInfoIdList.Value.Select(i => i.AddressList)) + { + foreach (var address in id.AddressList.Value) + { + if (addressList.Value.Contains(address)) + { + throw new AssertionException($"Duplicate address.{address}"); + } + } + } + + return id; + } + + private ExtraInfoId AssertExtraInfoIsNotExist(Hash subscribeId, ExtraInfoId infoId) + { + var whitelist = GetAvailableWhitelist(subscribeId); + var extraInfo = whitelist.Value.SingleOrDefault(e => e.Id == infoId.Id); + if (extraInfo == null) + { + throw new AssertionException($"Incorrect ExtraInfo id.{infoId}"); + } + + if (infoId.AddressList.Value.Select(address => extraInfo.AddressList.Value.Contains(address)) + .Any(ifExist => !ifExist)) + { + throw new AssertionException($"ExtraInfo doesn't exist in the available whitelist.{infoId}"); + } + + return infoId; + } + + private ExtraInfo ConvertToInfo(ExtraInfoId extraInfoId) + { + var extraInfo = State.TagInfoMap[extraInfoId.Id]; + return new ExtraInfo() + { + AddressList = extraInfoId.AddressList, + Info = new TagInfo() + { + TagName = extraInfo.TagName, + Info = extraInfo.Info + } + }; + } + + private ExtraInfoList ConvertToInfoList(ExtraInfoIdList extraInfoIdList) + { + var extraInfo = extraInfoIdList.Value.Select(e => + { + if (e.Id != null) + { + var infoId = e.Id; + var info = State.TagInfoMap[infoId]; + return new ExtraInfo() + { + AddressList = e.AddressList, + Info = new TagInfo() + { + TagName = info.TagName, + Info = info.Info + } + }; + } + + return new ExtraInfo() + { + AddressList = e.AddressList, + Info = null + }; + }).ToList(); + return new ExtraInfoList() { Value = { extraInfo } }; + } + + /// + /// Create TagInfo when creating whitelist with tagInfo. + /// + /// + /// + /// + /// + /// + private Hash CreateTagInfo(TagInfo info, Hash projectId, Hash whitelistId) + { + if (info == null) + { + throw new AssertionException("TagInfo is null."); + } + + var id = whitelistId.CalculateExtraInfoId(projectId, info.TagName); + if (State.TagInfoMap[id] != null) + { + throw new AssertionException($"TagInfo already exist.{info}"); + } + + State.TagInfoMap[id] = info; + Context.Fire(new TagInfoAdded() + { + WhitelistId = whitelistId, + ProjectId = projectId, + TagInfoId = id, + TagInfo = State.TagInfoMap[id] + }); + return id; + } + + private AddressList SetManagerList(Hash whitelistId, Address creator, AddressList input) + { + var managerList = input != null ? input.Value.Distinct().ToList() : new List
(); + if (!managerList.Contains(creator ?? Context.Sender)) + { + managerList.Add(creator ?? Context.Sender); + } + + State.ManagerListMap[whitelistId] = new AddressList() { Value = { managerList } }; + return State.ManagerListMap[whitelistId]; + } + + private AddressList SetSubscribeManagerList(Hash subscribeId, Address creator, AddressList input) + { + var managerList = input != null ? input.Value.Distinct().ToList() : new List
(); + if (!managerList.Contains(creator ?? Context.Sender)) + { + managerList.Add(creator ?? Context.Sender); + } + + State.SubscribeManagerListMap[subscribeId] = new AddressList() { Value = { managerList } }; + return State.SubscribeManagerListMap[subscribeId]; + } + + private void SetWhitelistIdManager(Hash whitelistId, AddressList managerList) + { + foreach (var manager in managerList.Value) + { + var whitelistIdList = State.WhitelistIdMap[manager] ?? new WhitelistIdList(); + whitelistIdList.WhitelistId.Add(whitelistId); + State.WhitelistIdMap[manager] = whitelistIdList; + } + } + + private void SetManagerListToWhitelist(Hash whitelistId, AddressList addressList) + { + var whitelistInfo = GetWhitelist(whitelistId); + whitelistInfo.Manager.Value.Add(addressList.Value); + + } + + private void SetSubscribeIdManager(Hash subscribeId, AddressList managerList) + { + foreach (var manager in managerList.Value) + { + var subscribeIdList = State.ManagerSubscribeIdListMap[manager] ?? new HashList(); + subscribeIdList.Value.Add(subscribeId); + State.ManagerSubscribeIdListMap[manager] = subscribeIdList; + } + } + + private void RemoveWhitelistIdManager(Hash whitelistId, AddressList managerList) + { + foreach (var manager in managerList.Value) + { + var whitelistIdList = State.WhitelistIdMap[manager]; + if (whitelistIdList == null) continue; + whitelistIdList.WhitelistId.Remove(whitelistId); + State.WhitelistIdMap[manager] = whitelistIdList; + } + } + + private void RemoveManagerListFromWhitelist(Hash whitelistId, AddressList managerList) + { + var whitelistInfo = GetWhitelist(whitelistId); + foreach (var manager in managerList.Value) + { + Assert(whitelistInfo.Manager.Value.Contains(manager), $"Manager is not exist.{manager}"); + whitelistInfo.Manager.Value.Remove(manager); + } + } + + private void RemoveSubscribeIdManager(Hash subscribeId, AddressList managerList) + { + foreach (var manager in managerList.Value) + { + var subscribeIdList = State.ManagerSubscribeIdListMap[manager]; + if (subscribeIdList == null) continue; + subscribeIdList.Value.Remove(subscribeId); + State.ManagerSubscribeIdListMap[manager] = subscribeIdList; + } + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/WhitelistContract_Managers.cs b/contract/AElf.Contracts.Whitelist/WhitelistContract_Managers.cs new file mode 100644 index 00000000..334cf159 --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/WhitelistContract_Managers.cs @@ -0,0 +1,681 @@ +using System.Collections.Generic; +using System.Linq; +using AElf.Contracts.Whitelist.Extensions; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.Whitelist; + +public partial class WhitelistContract +{ + public override Hash CreateWhitelist(CreateWhitelistInput input) + { + var whitelistHash = CalculateWhitelistHash(Context.Sender, input.ProjectId); + + Assert(State.WhitelistInfoMap[whitelistHash] == null, $"Whitelist already exists.{whitelistHash.ToHex()}"); + + var managerList = SetManagerList(whitelistHash, input.Creator, input.ManagerList); + + var whitelistInfo = new WhitelistInfo + { + WhitelistId = whitelistHash, + ProjectId = input.ProjectId, + ExtraInfoIdList = null, + Creator = input.Creator ?? Context.Sender, + IsAvailable = true, + IsCloneable = input.IsCloneable, + Remark = input.Remark, + Manager = managerList, + StrategyType = input.StrategyType + }; + Context.Fire(new WhitelistCreated + { + WhitelistId = whitelistHash, + ProjectId = whitelistInfo.ProjectId, + ExtraInfoIdList = whitelistInfo.ExtraInfoIdList, + Creator = input.Creator ?? Context.Sender, + IsCloneable = whitelistInfo.IsCloneable, + IsAvailable = whitelistInfo.IsAvailable, + Remark = whitelistInfo.Remark, + Manager = whitelistInfo.Manager, + StrategyType = whitelistInfo.StrategyType + }); + var alreadyExistsAddressList = new List
(); + //Remove duplicate addresses. + var extraInfoList = input.ExtraInfoList.Value; + if (input.StrategyType == StrategyType.Basic) + { + var addressList = new AddressList(); + foreach (var extraInfo in extraInfoList) + { + foreach (var address in extraInfo.AddressList.Value) + { + if (addressList.Value.Contains(address)) + { + throw new AssertionException($"Duplicate address: ${address}."); + } + + addressList.Value.Add(address); + } + } + + var extraInfoIdList = new ExtraInfoIdList + { + Value = + { + new ExtraInfoId + { + AddressList = addressList + } + } + }; + + whitelistInfo.ExtraInfoIdList = extraInfoIdList; + Context.Fire(new WhitelistAddressInfoAdded() + { + WhitelistId = whitelistHash, + ExtraInfoIdList = whitelistInfo.ExtraInfoIdList + }); + } + else + { + var extraInfoIdList = extraInfoList.Select(e => + { + var id = CreateTagInfo(e.Info, input.ProjectId, whitelistHash); + //Set tagInfo list according to the owner and projectId. + var idList = State.ManagerTagInfoMap[input.ProjectId][whitelistHash] ?? + new HashList(); + idList.Value.Add(id); + State.ManagerTagInfoMap[input.ProjectId][whitelistHash] = idList; + //Set address list according to the tagInfoId. + var addressList = State.TagInfoIdAddressListMap[whitelistHash][id] ?? new AddressList(); + addressList.Value.AddRange(e.AddressList.Value); + State.TagInfoIdAddressListMap[whitelistHash][id] = addressList; + //Map address and tagInfoId. + foreach (var address in e.AddressList.Value) + { + State.AddressTagInfoIdMap[whitelistHash][address] = id; + if (alreadyExistsAddressList.Contains(address)) + { + throw new AssertionException($"Duplicate address: ${address}."); + } + + alreadyExistsAddressList.Add(address); + } + + return new ExtraInfoId + { + AddressList = e.AddressList, + Id = id + }; + }).ToList(); + whitelistInfo.ExtraInfoIdList = new ExtraInfoIdList() { Value = { extraInfoIdList } }; + Context.Fire(new WhitelistAddressInfoAdded() + { + WhitelistId = whitelistHash, + ExtraInfoIdList = whitelistInfo.ExtraInfoIdList + }); + } + + State.WhitelistInfoMap[whitelistHash] = whitelistInfo; + SetWhitelistIdManager(whitelistHash, managerList); + var projectWhitelist = State.WhitelistProjectMap[input.ProjectId] ?? new WhitelistIdList(); + projectWhitelist.WhitelistId.Add(whitelistHash); + State.WhitelistProjectMap[input.ProjectId] = projectWhitelist; + State.ProjectWhitelistIdMap[whitelistHash] = whitelistInfo.ProjectId; + + return whitelistHash; + } + + public override Hash AddExtraInfo(AddExtraInfoInput input) + { + if (input == null) + { + throw new AssertionException("Extra info is null"); + } + + AssertWhitelistInfo(input.WhitelistId); + MakeSureProjectCorrect(input.WhitelistId, input.ProjectId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelistInfo = AssertWhitelistManager(input.WhitelistId); + + var tagInfoId = whitelistInfo.WhitelistId.CalculateExtraInfoId(whitelistInfo.ProjectId, input.TagInfo.TagName); + Assert(State.TagInfoMap[tagInfoId] == null, $"The tag Info {input.TagInfo.TagName} already exists."); + State.TagInfoMap[tagInfoId] = new TagInfo() + { + TagName = input.TagInfo.TagName, + Info = input.TagInfo.Info + }; + var idList = State.ManagerTagInfoMap[input.ProjectId][input.WhitelistId] ?? new HashList(); + idList.Value.Add(tagInfoId); + State.ManagerTagInfoMap[input.ProjectId][input.WhitelistId] = idList; + State.TagInfoIdAddressListMap[input.WhitelistId][tagInfoId] = new AddressList(); + Context.Fire(new TagInfoAdded() + { + ProjectId = input.ProjectId, + WhitelistId = input.WhitelistId, + TagInfoId = tagInfoId, + TagInfo = new TagInfo() + { + TagName = State.TagInfoMap[tagInfoId].TagName, + Info = State.TagInfoMap[tagInfoId].Info + } + }); + + //Add tagInfo with address list. + if (input.AddressList == null) return tagInfoId; + var extraInfoIdList = new ExtraInfoIdList() + { + Value = + { + new ExtraInfoId + { + AddressList = input.AddressList, + Id = tagInfoId + } + } + }; + AddAddressInfoListToWhitelist(new AddAddressInfoListToWhitelistInput() + { + WhitelistId = input.WhitelistId, + ExtraInfoIdList = extraInfoIdList + }); + return tagInfoId; + } + + public override Empty RemoveTagInfo(RemoveTagInfoInput input) + { + if (input == null) + { + throw new AssertionException("Tag info is null"); + } + + MakeSureProjectCorrect(input.WhitelistId, input.ProjectId); + AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelist = AssertWhitelistManager(input.WhitelistId); + + Assert(State.ManagerTagInfoMap[input.ProjectId][input.WhitelistId].Value.Contains(input.TagId), + $"Incorrect tagInfoId.{input.TagId.ToHex()}"); + Assert(State.TagInfoIdAddressListMap[input.WhitelistId][input.TagId].Value.Count == 0, + $"Exist address list.{input.TagId.ToHex()}"); + State.ManagerTagInfoMap[input.ProjectId][input.WhitelistId].Value.Remove(input.TagId); + var tagInfo = State.TagInfoMap[input.TagId]; + State.TagInfoMap.Remove(input.TagId); + State.TagInfoIdAddressListMap[input.WhitelistId].Remove(input.TagId); + foreach (var extraInfoId in whitelist.ExtraInfoIdList.Value) + { + if (extraInfoId.Id != input.TagId) continue; + whitelist.ExtraInfoIdList.Value.Remove(extraInfoId); + break; + } + Context.Fire(new TagInfoRemoved() + { + ProjectId = input.ProjectId, + WhitelistId = input.WhitelistId, + TagInfoId = input.TagId, + TagInfo = tagInfo + }); + return new Empty(); + } + + public override Empty AddAddressInfoListToWhitelist(AddAddressInfoListToWhitelistInput input) + { + AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelistInfo = AssertWhitelistManager(input.WhitelistId); + var toAddExtraInfoIdList = new ExtraInfoIdList(); + foreach (var infoId in input.ExtraInfoIdList.Value) + { + var toAddAddressList = new AddressList(); + foreach (var address in infoId.AddressList.Value) + { + if (toAddAddressList.Value.Contains(address)) + { + throw new AssertionException($"Duplicate address.{address}"); + } + toAddAddressList.Value.Add(address); + } + AssertExtraInfoDuplicate(whitelistInfo.WhitelistId, infoId); + var targetExtraInfoId = whitelistInfo.ExtraInfoIdList.Value.SingleOrDefault(i => i.Id == infoId.Id); + if (targetExtraInfoId != null) + { + targetExtraInfoId.AddressList.Value.Add(infoId.AddressList.Value); + toAddExtraInfoIdList = RecordChangedExtraInfoIdList(toAddExtraInfoIdList, infoId); + } + else + { + var toAdd = new ExtraInfoId + { + Id = infoId.Id, + AddressList = infoId.AddressList + }; + whitelistInfo.ExtraInfoIdList.Value.Add(toAdd); + toAddExtraInfoIdList = RecordChangedExtraInfoIdList(toAddExtraInfoIdList, infoId); + } + + if (infoId.Id == null) continue; + //Whether tagId correct. + if (!State.ManagerTagInfoMap[State.ProjectWhitelistIdMap[whitelistInfo.WhitelistId]][ + whitelistInfo.WhitelistId].Value.Contains(infoId.Id)) + { + throw new AssertionException($"Incorrect TagId.{infoId.Id}"); + } + + foreach (var address in infoId.AddressList.Value) + { + State.AddressTagInfoIdMap[whitelistInfo.WhitelistId][address] = infoId.Id; + } + + var addressList = State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][infoId.Id] ?? new AddressList(); + addressList.Value.AddRange(infoId.AddressList.Value); + State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][infoId.Id] = addressList; + } + + State.WhitelistInfoMap[whitelistInfo.WhitelistId] = whitelistInfo; + Context.Fire(new WhitelistAddressInfoAdded() + { + WhitelistId = whitelistInfo.WhitelistId, + ExtraInfoIdList = toAddExtraInfoIdList + }); + + return new Empty(); + } + + public override Empty RemoveAddressInfoListFromWhitelist(RemoveAddressInfoListFromWhitelistInput input) + { + AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelistInfo = AssertWhitelistManager(input.WhitelistId); + if (whitelistInfo.StrategyType == StrategyType.Basic) + { + var toRemove = new ExtraInfoId + { + Id = null, + AddressList = new AddressList() + }; + var extraInfoId = whitelistInfo.ExtraInfoIdList.Value.SingleOrDefault(e => e.Id == null); + if (extraInfoId != null) + { + var addressInWhitelist = extraInfoId.AddressList; + var addressInput = input.ExtraInfoIdList.Value.Select(e => e.AddressList).ToList(); + foreach (var addressList in addressInput) + { + foreach (var address in addressList.Value) + { + if (!addressInWhitelist.Value.Contains(address)) + { + throw new AssertionException($"Address does not exist Or already been removed.{address}"); + } + + addressInWhitelist.Value.Remove(address); + toRemove.AddressList.Value.Add(address); + } + } + } + else + { + throw new AssertionException($"No addressList.{input.ExtraInfoIdList}"); + } + + Context.Fire(new WhitelistAddressInfoRemoved() + { + WhitelistId = whitelistInfo.WhitelistId, + ExtraInfoIdList = new ExtraInfoIdList { Value = { toRemove } } + }); + return new Empty(); + } + + //Format input=>{infoId,AddressList} + var extraInfoIdList = input.ExtraInfoIdList.Value.GroupBy(e => e.Id).Select(g => + { + var extraInfoId = new ExtraInfoId + { + Id = g.Key, + AddressList = new AddressList() + }; + var addressLists = g.Select(i => i.AddressList).ToList(); + foreach (var addressList in addressLists) + { + extraInfoId.AddressList.Value.AddRange(addressList.Value); + } + + return extraInfoId; + }).ToList(); + foreach (var infoId in extraInfoIdList) + { + //Whether tagId correct. + if (!State.ManagerTagInfoMap[State.ProjectWhitelistIdMap[whitelistInfo.WhitelistId]][ + whitelistInfo.WhitelistId].Value.Contains(infoId.Id)) + { + throw new AssertionException($"Incorrect TagId.{infoId.Id}"); + } + + var targetExtraInfo = whitelistInfo.ExtraInfoIdList.Value.SingleOrDefault(i => i.Id == infoId.Id); + if (targetExtraInfo == null) + { + throw new AssertionException($"No addressList under tagInfo id.{infoId}"); + } + + foreach (var address in infoId.AddressList.Value) + { + var addressList = State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][infoId.Id]; + if (!targetExtraInfo.AddressList.Value.Contains(address)) + { + throw new AssertionException($"ExtraInfo does not exist Or already been removed.{address}"); + } + + targetExtraInfo.AddressList.Value.Remove(address); + addressList.Value.Remove(address); + State.AddressTagInfoIdMap[whitelistInfo.WhitelistId].Remove(address); + } + } + + Context.Fire(new WhitelistAddressInfoRemoved() + { + WhitelistId = whitelistInfo.WhitelistId, + ExtraInfoIdList = new ExtraInfoIdList + { + Value = { extraInfoIdList } + } + }); + return new Empty(); + } + + public override Empty RemoveInfoFromWhitelist(RemoveInfoFromWhitelistInput input) + { + AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelistInfo = AssertWhitelistManager(input.WhitelistId); + var extraInfoIdList = new ExtraInfoIdList(); + foreach (var address in input.AddressList.Value) + { + extraInfoIdList.Value.Add(new ExtraInfoId + { + Id = State.AddressTagInfoIdMap[whitelistInfo.WhitelistId][address] ?? null, + AddressList = new AddressList + { + Value = { address } + } + }); + } + + RemoveAddressInfoListFromWhitelist(new RemoveAddressInfoListFromWhitelistInput + { + WhitelistId = whitelistInfo.WhitelistId, + ExtraInfoIdList = extraInfoIdList + }); + return new Empty(); + } + + public override Empty DisableWhitelist(Hash input) + { + AssertWhitelistInfo(input); + AssertWhitelistIsAvailable(input); + var whitelistInfo = AssertWhitelistManager(input); + whitelistInfo.IsAvailable = false; + State.WhitelistInfoMap[whitelistInfo.WhitelistId] = whitelistInfo; + Context.Fire(new WhitelistDisabled + { + WhitelistId = whitelistInfo.WhitelistId, + IsAvailable = whitelistInfo.IsAvailable, + }); + return new Empty(); + } + + public override Empty EnableWhitelist(Hash input) + { + AssertWhitelistInfo(input); + var whitelistInfo = AssertWhitelistManager(input); + Assert(whitelistInfo.IsAvailable == false, + $"The whitelist is already available.{whitelistInfo.WhitelistId}"); + whitelistInfo.IsAvailable = true; + State.WhitelistInfoMap[whitelistInfo.WhitelistId] = whitelistInfo; + Context.Fire(new WhitelistReenable() + { + WhitelistId = whitelistInfo.WhitelistId, + IsAvailable = whitelistInfo.IsAvailable, + }); + return new Empty(); + } + + public override Empty ChangeWhitelistCloneable(ChangeWhitelistCloneableInput input) + { + AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelistInfo = AssertWhitelistManager(input.WhitelistId); + + whitelistInfo.IsCloneable = input.IsCloneable; + State.WhitelistInfoMap[whitelistInfo.WhitelistId] = whitelistInfo; + Context.Fire(new IsCloneableChanged() + { + WhitelistId = whitelistInfo.WhitelistId, + IsCloneable = whitelistInfo.IsCloneable + }); + return new Empty(); + } + + public override Empty UpdateExtraInfo(UpdateExtraInfoInput input) + { + if (input.ExtraInfoList == null) + { + throw new AssertionException("Address and extra info is null."); + } + + AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelistInfo = AssertWhitelistManager(input.WhitelistId); + var projectId = State.ProjectWhitelistIdMap[whitelistInfo.WhitelistId]; + if (!State.ManagerTagInfoMap[projectId][whitelistInfo.WhitelistId].Value.Contains(input.ExtraInfoList.Id)) + { + throw new AssertionException($"Incorrect extraInfoId.{input.ExtraInfoList.Id}"); + } + + var tagIdBefore = new Hash(); + foreach (var address in input.ExtraInfoList.AddressList.Value) + { + tagIdBefore = State.AddressTagInfoIdMap[whitelistInfo.WhitelistId][address]; + var extraInfoAddressBefore = whitelistInfo.ExtraInfoIdList.Value.SingleOrDefault(i => i.Id == tagIdBefore); + var extraInfoAddressAfter = + whitelistInfo.ExtraInfoIdList.Value.SingleOrDefault(i => i.Id == input.ExtraInfoList.Id); + if (extraInfoAddressBefore == null) + { + throw new AssertionException($"Incorrect address and extraInfoId.{tagIdBefore}"); + } + + extraInfoAddressBefore.AddressList.Value.Remove(address); + if (extraInfoAddressAfter == null) + { + extraInfoAddressAfter = new ExtraInfoId() + { + Id = input.ExtraInfoList.Id, + AddressList = new AddressList() { Value = { address } } + }; + whitelistInfo.ExtraInfoIdList.Value.Add(extraInfoAddressAfter); + //Add address to the new tagIdInfo map. + var addressList = State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][input.ExtraInfoList.Id] ?? + new AddressList(); + addressList.Value.Add(address); + State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][input.ExtraInfoList.Id] = addressList; + } + else + { + extraInfoAddressAfter.AddressList.Value.Add(address); + //Add address to the new tagIdInfo map. + var addressList = State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][input.ExtraInfoList.Id] ?? + new AddressList(); + addressList.Value.Add(address); + State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][input.ExtraInfoList.Id] = addressList; + } + + State.AddressTagInfoIdMap[whitelistInfo.WhitelistId][address] = input.ExtraInfoList.Id; + State.TagInfoIdAddressListMap[whitelistInfo.WhitelistId][tagIdBefore].Value.Remove(address); + + } + + Context.Fire(new ExtraInfoUpdated() + { + WhitelistId = input.WhitelistId, + ExtraInfoIdBefore = new ExtraInfoId() + { + AddressList = input.ExtraInfoList.AddressList, + Id = tagIdBefore + }, + ExtraInfoIdAfter = new ExtraInfoId() + { + AddressList = input.ExtraInfoList.AddressList, + Id = input.ExtraInfoList.Id + } + }); + + return new Empty(); + } + + public override Empty TransferManager(TransferManagerInput input) + { + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelist = AssertWhitelistManager(input.WhitelistId); + Assert(!whitelist.Manager.Value.Contains(input.Manager) && + !State.ManagerListMap[whitelist.WhitelistId].Value.Contains(input.Manager), + $"Manager already exists.{input}"); + whitelist.Manager.Value.Remove(Context.Sender); + whitelist.Manager.Value.Add(input.Manager); + var whitelistInfo = GetWhitelist(whitelist.WhitelistId); + whitelistInfo.Manager.Value.Remove(Context.Sender); + whitelistInfo.Manager.Value.Add(input.Manager); + State.WhitelistInfoMap[whitelist.WhitelistId] = whitelist; + State.ManagerListMap[whitelist.WhitelistId].Value.Remove(Context.Sender); + State.ManagerListMap[whitelist.WhitelistId].Value.Add(input.Manager); + Context.Fire(new ManagerTransferred() + { + WhitelistId = whitelist.WhitelistId, + TransferFrom = Context.Sender, + TransferTo = input.Manager + }); + return new Empty(); + } + + public override Empty AddManagers(AddManagersInput input) + { + var whitelist = AssertWhitelistCreator(input.WhitelistId); + var managerList = State.ManagerListMap[whitelist.WhitelistId]; + var addedManager = new AddressList(); + foreach (var manager in input.ManagerList.Value) + { + if (!managerList.Value.Contains(manager)) + { + managerList.Value.Add(manager); + addedManager.Value.Add(manager); + } + else + { + throw new AssertionException($"Managers already exists.{manager}"); + } + } + + State.ManagerListMap[whitelist.WhitelistId] = managerList; + SetWhitelistIdManager(whitelist.WhitelistId, addedManager); + SetManagerListToWhitelist(whitelist.WhitelistId, addedManager); + + Context.Fire(new ManagerAdded + { + WhitelistId = whitelist.WhitelistId, + ManagerList = addedManager + }); + + return new Empty(); + } + + public override Empty RemoveManagers(RemoveManagersInput input) + { + var whitelist = AssertWhitelistCreator(input.WhitelistId); + var managerList = State.ManagerListMap[whitelist.WhitelistId]; + var removedList = new AddressList(); + foreach (var manager in input.ManagerList.Value) + { + if (managerList.Value.Contains(manager)) + { + managerList.Value.Remove(manager); + removedList.Value.Add(manager); + } + else + { + throw new AssertionException($"Managers doesn't exists.{manager}"); + } + } + + State.ManagerListMap[whitelist.WhitelistId] = managerList; + RemoveWhitelistIdManager(whitelist.WhitelistId, removedList); + RemoveManagerListFromWhitelist(whitelist.WhitelistId, removedList); + Context.Fire(new ManagerRemoved() + { + WhitelistId = whitelist.WhitelistId, + ManagerList = removedList + }); + return new Empty(); + } + + public override Empty ResetWhitelist(ResetWhitelistInput input) + { + AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(input.WhitelistId); + var whitelist = AssertWhitelistManager(input.WhitelistId); + Assert(State.ProjectWhitelistIdMap[whitelist.WhitelistId] == input.ProjectId, + $"Incorrect projectId.{input.ProjectId}"); + if (whitelist.ExtraInfoIdList == null) + { + throw new AssertionException($"No extraInfo.{whitelist.WhitelistId}"); + } + if (whitelist.StrategyType != StrategyType.Basic) + { + var idList = State.ManagerTagInfoMap[input.ProjectId][input.WhitelistId].Clone(); + if (idList == null) + { + throw new AssertionException($"No tagInfo.{whitelist.WhitelistId}"); + } + + var addressList = whitelist.ExtraInfoIdList.Value.Select(e => e.AddressList).ToList(); + State.ManagerTagInfoMap[input.ProjectId][input.WhitelistId].Value.Clear(); + foreach (var id in idList.Value) + { + State.TagInfoMap.Remove(id); + State.TagInfoIdAddressListMap[input.WhitelistId].Remove(id); + } + foreach (var addresses in addressList) + { + foreach (var address in addresses.Value) + { + State.AddressTagInfoIdMap[input.WhitelistId].Remove(address); + } + } + } + whitelist.ExtraInfoIdList.Value.Clear(); + Context.Fire(new WhitelistReset + { + WhitelistId = whitelist.WhitelistId, + ProjectId = whitelist.ProjectId + }); + return new Empty(); + } + + private ExtraInfoIdList RecordChangedExtraInfoIdList(ExtraInfoIdList toAddExtraInfoIdList, ExtraInfoId infoId) + { + var targetToAddList = toAddExtraInfoIdList.Value.SingleOrDefault(i => i.Id == infoId.Id); + if (targetToAddList != null) + { + targetToAddList.AddressList.Value.Add(infoId.AddressList.Value); + } + else + { + var extraInfoId = new ExtraInfoId + { + Id = infoId.Id, + AddressList = infoId.AddressList + }; + toAddExtraInfoIdList.Value.Add(extraInfoId); + } + + return toAddExtraInfoIdList; + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/WhitelistContract_Subscribers.cs b/contract/AElf.Contracts.Whitelist/WhitelistContract_Subscribers.cs new file mode 100644 index 00000000..a03ccc1b --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/WhitelistContract_Subscribers.cs @@ -0,0 +1,202 @@ +using System.Linq; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.Whitelist; + +public partial class WhitelistContract +{ + public override Hash SubscribeWhitelist(SubscribeWhitelistInput input) + { + var whitelistInfo = AssertWhitelistInfo(input.WhitelistId); + AssertWhitelistIsAvailable(whitelistInfo.WhitelistId); + var subscribeId = CalculateSubscribeWhitelistHash(Context.Sender, input.ProjectId, input.WhitelistId); + Assert(State.SubscribeWhitelistInfoMap[subscribeId] == null, "Subscribe info already exist."); + var managerList = SetManagerList(subscribeId, input.Subscriber, input.ManagerList); + var subscribeWhiteListInfo = new SubscribeWhitelistInfo + { + SubscribeId = subscribeId, + ProjectId = input.ProjectId, + WhitelistId = whitelistInfo.WhitelistId, + Subscriber = input.Subscriber ?? Context.Sender, + ManagerList = input.ManagerList + }; + State.SubscribeWhitelistInfoMap[subscribeId] = subscribeWhiteListInfo; + State.SubscribeManagerListMap[subscribeId] = + SetSubscribeManagerList(subscribeId, input.Subscriber, input.ManagerList); + State.ConsumedListMap[subscribeId] = new ConsumedList(); + SetSubscribeIdManager(subscribeId, managerList); + Context.Fire(new WhitelistSubscribed + { + SubscribeId = subscribeId, + ProjectId = subscribeWhiteListInfo.ProjectId, + WhitelistId = subscribeWhiteListInfo.WhitelistId, + Subscriber = input.Subscriber ?? Context.Sender, + ManagerList = input.ManagerList + }); + return subscribeId; + } + + public override Empty UnsubscribeWhitelist(Hash input) + { + var subscribeInfo = AssertSubscribeWhitelistInfo(input); + AssertSubscribeManager(subscribeInfo.SubscribeId, Context.Sender); + State.ConsumedListMap.Remove(input); + Context.Fire(new WhitelistUnsubscribed() + { + SubscribeId = subscribeInfo.SubscribeId, + ProjectId = subscribeInfo.ProjectId, + WhitelistId = subscribeInfo.WhitelistId + }); + return new Empty(); + } + + public override Empty ConsumeWhitelist(ConsumeWhitelistInput input) + { + var subscribeInfo = AssertSubscribeWhitelistInfo(input.SubscribeId); + AssertSubscribeManager(subscribeInfo.SubscribeId, Context.Sender); + var extraInfoId = AssertExtraInfoIsNotExist(subscribeInfo.SubscribeId, input.ExtraInfoId); + if (State.ConsumedListMap[subscribeInfo.SubscribeId].ExtraInfoIdList != null) + { + var consumedList = GetConsumedList(subscribeInfo.SubscribeId); + var targetConsume = consumedList.ExtraInfoIdList.Value.SingleOrDefault(e => e.Id == extraInfoId.Id); + if (targetConsume != null) + { + targetConsume.AddressList.Value.AddRange(extraInfoId.AddressList.Value); + } + else + { + consumedList.ExtraInfoIdList.Value.Add(extraInfoId); + } + + State.ConsumedListMap[subscribeInfo.SubscribeId] = consumedList; + Context.Fire(new ConsumedListAdded + { + SubscribeId = consumedList.SubscribeId, + WhitelistId = subscribeInfo.WhitelistId, + ExtraInfoIdList = new ExtraInfoIdList { Value = { extraInfoId } } + }); + } + else + { + var addressInfoList = new ExtraInfoIdList(); + addressInfoList.Value.Add(extraInfoId); + var consumedList = new ConsumedList + { + SubscribeId = subscribeInfo.SubscribeId, + WhitelistId = subscribeInfo.WhitelistId, + ExtraInfoIdList = addressInfoList + }; + State.ConsumedListMap[subscribeInfo.SubscribeId] = consumedList; + Context.Fire(new ConsumedListAdded + { + SubscribeId = consumedList.SubscribeId, + WhitelistId = subscribeInfo.WhitelistId, + ExtraInfoIdList = new ExtraInfoIdList { Value = { extraInfoId } } + }); + } + + return new Empty(); + } + + public override Empty AddSubscribeManagers(AddSubscribeManagersInput input) + { + AssertSubscriber(input.SubscribeId); + var managerList = State.SubscribeManagerListMap[input.SubscribeId]; + var addedManager = new AddressList(); + foreach (var manager in input.ManagerList.Value) + { + if (!managerList.Value.Contains(manager)) + { + managerList.Value.Add(manager); + addedManager.Value.Add(manager); + } + else + { + throw new AssertionException($"Managers already exists.{manager}"); + } + } + + State.SubscribeManagerListMap[input.SubscribeId] = managerList; + SetSubscribeIdManager(input.SubscribeId, addedManager); + + Context.Fire(new SubscribeManagerAdded() + { + SubscribeId = input.SubscribeId, + ManagerList = addedManager + }); + return new Empty(); + } + + public override Empty RemoveSubscribeManagers(RemoveSubscribeManagersInput input) + { + AssertSubscriber(input.SubscribeId); + var managerList = State.SubscribeManagerListMap[input.SubscribeId]; + var removedList = new AddressList(); + foreach (var manager in input.ManagerList.Value) + { + if (managerList.Value.Contains(manager)) + { + managerList.Value.Remove(manager); + removedList.Value.Add(manager); + } + else + { + throw new AssertionException($"Managers doesn't exists.{manager}"); + } + } + + State.SubscribeManagerListMap[input.SubscribeId] = managerList; + RemoveSubscribeIdManager(input.SubscribeId, removedList); + Context.Fire(new SubscribeManagerRemoved + { + SubscribeId = input.SubscribeId, + ManagerList = removedList + }); + return new Empty(); + } + + public override Hash CloneWhitelist(CloneWhitelistInput input) + { + var whiteListInfo = AssertWhitelistInfo(input.WhitelistId).Clone(); + AssertWhitelistIsAvailable(whiteListInfo.WhitelistId); + Assert(whiteListInfo.IsCloneable, $"Whitelist is not allowed to be cloned.{whiteListInfo.WhitelistId.ToHex()}"); + + var cloneWhiteListId = CalculateCloneWhitelistHash(Context.Sender, input.WhitelistId); + Assert(State.WhitelistInfoMap[cloneWhiteListId] == null, "WhiteList has already been cloned."); + + var managerList = SetManagerList(cloneWhiteListId, input.Creator, input.ManagerList); + var whitelistClone = new WhitelistInfo() + { + WhitelistId = cloneWhiteListId, + ExtraInfoIdList = whiteListInfo.ExtraInfoIdList, + Creator = input.Creator ?? Context.Sender, + IsAvailable = whiteListInfo.IsAvailable, + IsCloneable = whiteListInfo.IsCloneable, + Remark = whiteListInfo.Remark, + CloneFrom = whiteListInfo.WhitelistId, + Manager = managerList, + ProjectId = input.ProjectId, + StrategyType = whiteListInfo.StrategyType + }; + State.WhitelistInfoMap[cloneWhiteListId] = whitelistClone; + + SetWhitelistIdManager(cloneWhiteListId, managerList); + + Context.Fire(new WhitelistCreated() + { + WhitelistId = whitelistClone.WhitelistId, + ExtraInfoIdList = whitelistClone.ExtraInfoIdList, + Creator = input.Creator ?? Context.Sender, + IsAvailable = whitelistClone.IsAvailable, + IsCloneable = whitelistClone.IsCloneable, + Remark = whitelistClone.Remark, + CloneFrom = whiteListInfo.WhitelistId, + Manager = managerList, + ProjectId = input.ProjectId, + StrategyType = whitelistClone.StrategyType + }); + return cloneWhiteListId; + } +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Whitelist/WhitelistContract_Views.cs b/contract/AElf.Contracts.Whitelist/WhitelistContract_Views.cs new file mode 100644 index 00000000..3aa7aa03 --- /dev/null +++ b/contract/AElf.Contracts.Whitelist/WhitelistContract_Views.cs @@ -0,0 +1,235 @@ +using System.Linq; +using AElf.Contracts.Whitelist.Extensions; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.Whitelist; + +public partial class WhitelistContract +{ + public override WhitelistInfo GetWhitelist(Hash input) + { + var whitelist = AssertWhitelistInfo(input); + return whitelist; + } + + public override ExtraInfoList GetWhitelistDetail(Hash input) + { + var whitelistInfo = GetWhitelist(input); + return ConvertToInfoList(whitelistInfo.ExtraInfoIdList); + } + + public override WhitelistIdList GetWhitelistByProject(Hash input) + { + return State.WhitelistProjectMap[input]; + } + + public override TagInfo GetTagInfoByHash(Hash input) + { + Assert(State.TagInfoMap[input] != null, $"Not found tag.{input.ToHex()}"); + return State.TagInfoMap[input]; + } + + public override ExtraInfo GetExtraInfoByTag(GetExtraInfoByTagInput input) + { + var whitelist = GetWhitelist(input.WhitelistId); + var tagInfo = GetTagInfoByHash(input.TagInfoId); + Assert(State.TagInfoIdAddressListMap[whitelist.WhitelistId][input.TagInfoId].Value.Count != 0, + $"No address list under the current tag.{input.TagInfoId.ToHex()}"); + var addressList = State.TagInfoIdAddressListMap[whitelist.WhitelistId][input.TagInfoId]; + return new ExtraInfo + { + Info = tagInfo, + AddressList = addressList + }; + } + + public override HashList GetExtraInfoIdList(GetExtraInfoIdListInput input) + { + var whitelist = GetWhitelist(input.WhitelistId); + Assert(State.ManagerTagInfoMap[input.ProjectId][whitelist.WhitelistId] != null, + $"ExtraInfo id list doesn't exist.{input.ProjectId.ToHex()}{input.WhitelistId.ToHex()}"); + var idList = State.ManagerTagInfoMap[input.ProjectId][whitelist.WhitelistId]; + Assert(idList.Value.Count != 0, $"No extraInfo id list.{input.ProjectId.ToHex()}{input.WhitelistId.ToHex()}"); + return idList; + } + + public override TagInfoList GetTagInfoListByWhitelist(GetTagInfoListByWhitelistInput input) + { + var tagInfoList = new TagInfoList(); + var tagIdList = GetExtraInfoIdList(new GetExtraInfoIdListInput + { + WhitelistId = input.WhitelistId, + ProjectId = input.ProjectId + }); + foreach (var tagId in tagIdList.Value) + { + tagInfoList.Value.Add(GetTagInfoByHash(tagId)); + } + return tagInfoList; + } + + public override TagInfo GetExtraInfoByAddress(GetExtraInfoByAddressInput input) + { + var whitelist = GetWhitelist(input.WhitelistId); + Assert(State.AddressTagInfoIdMap[whitelist.WhitelistId][input.Address] != null, + $"No Match tagInfo according to the address.{input.WhitelistId.ToHex()}{input.Address}"); + var tagInfoId = State.AddressTagInfoIdMap[whitelist.WhitelistId][input.Address]; + var tagInfo = GetTagInfoByHash(tagInfoId); + return tagInfo; + } + + public override Hash GetTagIdByAddress(GetTagIdByAddressInput input) + { + var whitelist = GetWhitelist(input.WhitelistId); + Assert(State.AddressTagInfoIdMap[whitelist.WhitelistId][input.Address] != null, + $"No Match tagInfo according to the address.{input.WhitelistId.ToHex()}{input.Address}"); + return State.AddressTagInfoIdMap[whitelist.WhitelistId][input.Address]; + } + + public override SubscribeWhitelistInfo GetSubscribeWhitelist(Hash input) + { + var subscribeInfo = AssertSubscribeWhitelistInfo(input); + return subscribeInfo; + } + + public override ConsumedList GetConsumedList(Hash input) + { + var subscribeInfo = GetSubscribeWhitelist(input); + return State.ConsumedListMap[subscribeInfo.SubscribeId]; + } + + public override ExtraInfoIdList GetAvailableWhitelist(Hash input) + { + var subscribe = GetSubscribeWhitelist(input); + var consumedList = State.ConsumedListMap[subscribe.SubscribeId]; + var whitelist = State.WhitelistInfoMap[subscribe.WhitelistId]; + if (consumedList.ExtraInfoIdList == null) + { + return whitelist.ExtraInfoIdList; + } + else + { + var consumedExtraList = consumedList.ExtraInfoIdList.Value; + var whitelistExtra = whitelist.ExtraInfoIdList.Value; + foreach (var extraInfoId in consumedExtraList) + { + var target = whitelistExtra.SingleOrDefault(e => e.Id == extraInfoId.Id); + if (target == null) continue; + var targetAddressList = target.AddressList; + var available = targetAddressList.Value.Except(extraInfoId.AddressList.Value); + target.AddressList = new AddressList() { Value = { available } }; + } + + return new ExtraInfoIdList() { Value = { whitelistExtra } }; + } + } + + public override BoolValue GetAddressFromWhitelist(GetAddressFromWhitelistInput input) + { + var whitelist = GetWhitelist(input.WhitelistId); + var addressLists = whitelist.ExtraInfoIdList.Value.Select(e => e.AddressList).ToList(); + return addressLists.Any(addressList => addressList.Value.Contains(input.Address)) + ? new BoolValue { Value = true } + : new BoolValue(); + } + + public override BoolValue GetExtraInfoFromWhitelist(GetExtraInfoFromWhitelistInput input) + { + var whitelist = GetWhitelist(input.WhitelistId); + var extraInfoId = whitelist.ExtraInfoIdList.Value.SingleOrDefault(i => i.Id == input.ExtraInfoId.Id); + if (extraInfoId == null) + { + throw new AssertionException($"TagInfo does not exist.{input.ExtraInfoId.Id}"); + } + + var addressList = State.TagInfoIdAddressListMap[whitelist.WhitelistId][input.ExtraInfoId.Id] ?? + new AddressList(); + return input.ExtraInfoId.AddressList.Value.Any(address => !addressList.Value.Contains(address)) + ? new BoolValue() { Value = false } + : new BoolValue() { Value = true }; + } + + public override BoolValue GetManagerExistFromWhitelist(GetManagerExistFromWhitelistInput input) + { + var whitelistIdList = State.ManagerListMap[input.WhitelistId] ?? new AddressList(); + var ifExist = whitelistIdList.Value.Contains(input.Manager); + return new BoolValue() { Value = ifExist }; + } + + public override BoolValue GetTagInfoFromWhitelist(GetTagInfoFromWhitelistInput input) + { + var whitelist = GetWhitelist(input.WhitelistId); + MakeSureProjectCorrect(whitelist.WhitelistId, input.ProjectId); + var tagId = whitelist.WhitelistId.CalculateExtraInfoId(whitelist.ProjectId, input.TagInfo.TagName); + var tagIdList = State.ManagerTagInfoMap[whitelist.ProjectId][whitelist.WhitelistId] ?? new HashList(); + var ifExist = tagIdList.Value.Contains(tagId); + return new BoolValue() { Value = ifExist }; + } + + + public override BoolValue GetFromAvailableWhitelist(GetFromAvailableWhitelistInput input) + { + var subscribeInfo = GetSubscribeWhitelist(input.SubscribeId); + var extraInfoIdList = GetAvailableWhitelist(input.SubscribeId); + var extraInfoId = extraInfoIdList.Value.SingleOrDefault(i => i.Id == input.ExtraInfoId.Id); + if (extraInfoId == null) + { + throw new AssertionException($"TagInfo does not exist.{input.ExtraInfoId.Id}"); + } + else + { + var addressList = State.TagInfoIdAddressListMap[subscribeInfo.WhitelistId][input.ExtraInfoId.Id]; + return input.ExtraInfoId.AddressList.Value.Any(address => !addressList.Value.Contains(address)) + ? new BoolValue() { Value = false } + : new BoolValue() { Value = true }; + } + } + + public override WhitelistIdList GetWhitelistByManager(Address input) + { + var whitelistIdList = State.WhitelistIdMap[input]; + Assert(whitelistIdList != null && whitelistIdList.WhitelistId.Count != 0, + $"No whitelist according to the manager.{input}"); + return State.WhitelistIdMap[input]; + } + + public override AddressList GetManagerList(Hash input) + { + AssertWhitelistInfo(input); + return State.ManagerListMap[input]; + } + + public override AddressList GetSubscribeManagerList(Hash input) + { + AssertSubscribeWhitelistInfo(input); + return State.SubscribeManagerListMap[input]; + } + + public override HashList GetSubscribeIdByManager(Address input) + { + var subscribeIdList = State.ManagerSubscribeIdListMap[input]; + Assert(subscribeIdList != null && subscribeIdList.Value.Count != 0, + $"No subscribe id according to the manager.{input}"); + return subscribeIdList; + } + + public override WhitelistIdList GetWhitelistIdList(GetWhitelistIdListInput input) + { + var whitelistIdList = new WhitelistIdList(); + foreach (var whitelistId in input.WhitelistIdList.WhitelistId) + { + var ifExist = GetAddressFromWhitelist(new GetAddressFromWhitelistInput + { + WhitelistId = whitelistId, + Address = input.Address + }).Value; + if (ifExist) + { + whitelistIdList.WhitelistId.Add(whitelistId); + } + } + return whitelistIdList; + } +} \ No newline at end of file diff --git a/contract/Directory.Build.props b/contract/Directory.Build.props new file mode 100755 index 00000000..5c33efda --- /dev/null +++ b/contract/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/contract/Forest/Forest.csproj b/contract/Forest/Forest.csproj new file mode 100755 index 00000000..9265f5c5 --- /dev/null +++ b/contract/Forest/Forest.csproj @@ -0,0 +1,76 @@ + + + net6.0 + true + Forest + Forest Contract is for the aelf official NFT Market. + Forest + + + + true + + + + true + + + + + Protobuf\Proto\authority_info.proto + + + + + + Protobuf\Proto\reference\acs0.proto + + + Protobuf\Proto\reference\acs3.proto + + + Protobuf\Proto\reference\acs6.proto + + + Protobuf\Proto\reference\acs7.proto + + + Protobuf\Proto\reference\acs10.proto + + + Protobuf\Proto\reference\parliament_contract.proto + + + Protobuf\Proto\reference\token_contract.proto + + + Protobuf\Proto\reference\nft_contract.proto + + + Protobuf\Proto\reference\whitelist_contract.proto + + + + + + + Protobuf\Proto\acs1.proto + + + Protobuf\Proto\acs2.proto + + + Protobuf\Proto\transaction_fee.proto + + + + + + Protobuf\Proto\forest_contract.proto + + + + + + + \ No newline at end of file diff --git a/contract/Forest/ForestContract.CompositionRoot.cs b/contract/Forest/ForestContract.CompositionRoot.cs new file mode 100644 index 00000000..ae7cd6a3 --- /dev/null +++ b/contract/Forest/ForestContract.CompositionRoot.cs @@ -0,0 +1,23 @@ +using Forest.Managers; +using Forest.Services; + +namespace Forest; + +public partial class ForestContract +{ + private WhitelistManager GetWhitelistManager() + { + return new WhitelistManager(Context, State.WhitelistIdMap, State.WhitelistContract); + } + + private MakeOfferService GetMakeOfferService(WhitelistManager whitelistManager = null) + { + return new MakeOfferService(State.NFTContract, State.WhitelistIdMap, State.ListedNFTInfoListMap, + whitelistManager ?? GetWhitelistManager(), Context); + } + + private DealService GetDealService() + { + return new DealService(Context); + } +} \ No newline at end of file diff --git a/contract/Forest/ForestContract.cs b/contract/Forest/ForestContract.cs new file mode 100755 index 00000000..e55de9d4 --- /dev/null +++ b/contract/Forest/ForestContract.cs @@ -0,0 +1,77 @@ +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace Forest +{ + /// + /// The C# implementation of the contract defined in forest_contract.proto that is located in the "protobuf" + /// folder. + /// Notice that it inherits from the protobuf generated code. + /// + public partial class ForestContract : ForestContractContainer.ForestContractBase + { + public override Empty Initialize(InitializeInput input) + { + Assert(State.NFTContract.Value == null, "Already initialized."); + State.NFTContract.Value = input.NftContractAddress; + State.Admin.Value = input.AdminAddress ?? Context.Sender; + State.ServiceFeeRate.Value = input.ServiceFeeRate == 0 ? DefaultServiceFeeRate : input.ServiceFeeRate; + State.ServiceFeeReceiver.Value = input.ServiceFeeReceiver ?? State.Admin.Value; + State.ServiceFee.Value = input.ServiceFee == 0 ? DefaultServiceFeeAmount : input.ServiceFee; + State.TokenContract.Value = + Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + State.GlobalTokenWhiteList.Value = new StringList + { + Value = {Context.Variables.NativeSymbol} + }; + return new Empty(); + } + + public override Empty SetServiceFee(SetServiceFeeInput input) + { + AssertSenderIsAdmin(); + State.ServiceFeeRate.Value = input.ServiceFeeRate; + State.ServiceFeeReceiver.Value = input.ServiceFeeReceiver ?? State.Admin.Value; + return new Empty(); + } + + public override Empty SetGlobalTokenWhiteList(StringList input) + { + AssertSenderIsAdmin(); + if (!input.Value.Contains(Context.Variables.NativeSymbol)) + { + input.Value.Add(Context.Variables.NativeSymbol); + } + State.GlobalTokenWhiteList.Value = input; + Context.Fire(new GlobalTokenWhiteListChanged + { + TokenWhiteList = input + }); + return new Empty(); + } + + public override Empty SetWhitelistContract(Address input) + { + AssertSenderIsAdmin(); + State.WhitelistContract.Value= input; + return new Empty(); + } + + private void AssertWhitelistContractInitialized() + { + Assert(State.WhitelistContract.Value != null,"Whitelist Contract not initialized."); + } + + private void AssertSenderIsAdmin() + { + AssertContractInitialized(); + Assert(Context.Sender == State.Admin.Value, "No permission."); + } + + private void AssertContractInitialized() + { + Assert(State.Admin.Value != null, "Contract not initialized."); + } + } +} \ No newline at end of file diff --git a/contract/Forest/ForestContractConstants.cs b/contract/Forest/ForestContractConstants.cs new file mode 100644 index 00000000..c16af4a0 --- /dev/null +++ b/contract/Forest/ForestContractConstants.cs @@ -0,0 +1,11 @@ +namespace Forest; + +public partial class ForestContract +{ + private const int FeeDenominator = 10000; + private const int DefaultExpireDays = 100000; + private const int DefaultServiceFeeRate = 10; + private const int DefaultServiceFeeAmount = 1_00000000; + private const int DefaultDepositConfirmRate = FeeDenominator / 2; + private const string BadgeMintWhitelistIdMetadataKey = "aelf_badge_whitelist"; +} \ No newline at end of file diff --git a/contract/Forest/ForestContractReferenceState.cs b/contract/Forest/ForestContractReferenceState.cs new file mode 100644 index 00000000..171c1f7d --- /dev/null +++ b/contract/Forest/ForestContractReferenceState.cs @@ -0,0 +1,13 @@ +using AElf.Contracts.MultiToken; +using AElf.Contracts.NFT; +using AElf.Contracts.Whitelist; + +namespace Forest; + +public partial class ForestContractState +{ + internal NFTContractContainer.NFTContractReferenceState NFTContract { get; set; } + internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; } + + internal WhitelistContractContainer.WhitelistContractReferenceState WhitelistContract { get; set; } +} \ No newline at end of file diff --git a/contract/Forest/ForestContractState.cs b/contract/Forest/ForestContractState.cs new file mode 100755 index 00000000..0623b62d --- /dev/null +++ b/contract/Forest/ForestContractState.cs @@ -0,0 +1,59 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace Forest +{ + /// + /// The state class of the contract, it inherits from the AElf.Sdk.CSharp.State.ContractState type. + /// + public partial class ForestContractState : ContractState + { + public SingletonState
Admin { get; set; } + + public SingletonState
ServiceFeeReceiver { get; set; } + + public Int32State ServiceFeeRate { get; set; } + public Int64State ServiceFee { get; set; } + + public SingletonState GlobalTokenWhiteList { get; set; } + + /// + /// Symbol -> Token Id -> Owner -> List NFT Info List + /// + public MappedState ListedNFTInfoListMap { get; set; } + + /// + /// Project Id -> Whitelist Id + /// + public MappedState WhitelistIdMap { get; set; } + + /// + /// Symbol -> Token Id -> Offer Address List + /// + public MappedState OfferAddressListMap { get; set; } + + /// + /// Symbol -> Token Id -> Offer Maker -> Offer List + /// + public MappedState OfferListMap { get; set; } + + public MappedState BidMap { get; set; } + + public MappedState BidAddressListMap { get; set; } + + /// + /// Symbol -> Token Id -> Royalty + /// + public MappedState RoyaltyMap { get; set; } + + public MappedState RoyaltyFeeReceiverMap { get; set; } + public MappedState CertainNFTRoyaltyMap { get; set; } + public MappedState TokenWhiteListMap { get; set; } + + public MappedState CustomizeInfoMap { get; set; } + public MappedState RequestInfoMap { get; set; } + + public MappedState EnglishAuctionInfoMap { get; set; } + public MappedState DutchAuctionInfoMap { get; set; } + } +} \ No newline at end of file diff --git a/contract/Forest/ForestContract_ACS1.cs b/contract/Forest/ForestContract_ACS1.cs new file mode 100644 index 00000000..e7136804 --- /dev/null +++ b/contract/Forest/ForestContract_ACS1.cs @@ -0,0 +1,31 @@ +using AElf.Standards.ACS1; +using Google.Protobuf.WellKnownTypes; + +namespace Forest; + +public partial class ForestContract +{ + public override Empty SetMethodFee(MethodFees input) + { + return new Empty(); + } + + public override Empty ChangeMethodFeeController(AuthorityInfo input) + { + return new Empty(); + } + + #region Views + + public override MethodFees GetMethodFee(StringValue input) + { + return new MethodFees(); + } + + public override AuthorityInfo GetMethodFeeController(Empty input) + { + return new AuthorityInfo(); + } + + #endregion +} \ No newline at end of file diff --git a/contract/Forest/ForestContract_Buyers.cs b/contract/Forest/ForestContract_Buyers.cs new file mode 100644 index 00000000..632b9bdd --- /dev/null +++ b/contract/Forest/ForestContract_Buyers.cs @@ -0,0 +1,872 @@ +using System; +using System.Linq; +using AElf.Contracts.NFT; +using AElf.Contracts.Whitelist; +using AElf.CSharp.Core; +using AElf.CSharp.Core.Extension; +using AElf.Sdk.CSharp; +using AElf.Types; +using Forest.Services; +using Google.Protobuf.WellKnownTypes; +using GetAllowanceInput = AElf.Contracts.MultiToken.GetAllowanceInput; +using GetBalanceInput = AElf.Contracts.MultiToken.GetBalanceInput; +using TransferFromInput = AElf.Contracts.MultiToken.TransferFromInput; +using TransferInput = AElf.Contracts.MultiToken.TransferInput; + + +namespace Forest; + +public partial class ForestContract +{ + /// + /// There are 2 types of making offer. + /// 1. Aiming a owner. + /// 2. Only aiming nft. Owner will be the nft protocol creator. + /// + /// + /// + public override Empty MakeOffer(MakeOfferInput input) + { + AssertContractInitialized(); + + var nftInfo = State.NFTContract.GetNFTInfo.Call(new GetNFTInfoInput + { + Symbol = input.Symbol, + TokenId = input.TokenId + }); + + var makeOfferService = GetMakeOfferService(); + makeOfferService.ValidateOffer(input); + + if (nftInfo.Quantity != 0 && input.OfferTo == null) + { + input.OfferTo = nftInfo.Creator; + } + + var listedNftInfoList = + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][input.OfferTo]; + + var whitelistManager = GetWhitelistManager(); + + if (makeOfferService.IsSenderInWhitelist(input,out var whitelistId) && whitelistManager.IsWhitelistAvailable(whitelistId)) + { + // Deal one NFT with whitelist price. + var price = whitelistManager.GetExtraInfoByAddress(whitelistId); + if (price != null && price.Amount <= input.Price.Amount && price.Symbol == input.Price.Symbol) + { + var minStartList = listedNftInfoList.Value.OrderBy(i => i.Duration.StartTime).ToList(); + if (minStartList.Count == 0) + { + PerformMakeOffer(input); + return new Empty(); + } + if (Context.CurrentBlockTime < minStartList[0].Duration.StartTime) + { + PerformMakeOffer(input); + return new Empty(); + } + if (TryDealWithFixedPriceWhitelist(input,price,whitelistId)) + { + MaybeRemoveRequest(input.Symbol, input.TokenId); + minStartList[0].Quantity = minStartList[0].Quantity.Sub(1); + if (minStartList[0].Quantity == 0) + { + listedNftInfoList.Value.Remove(minStartList[0]); + Context.Fire(new ListedNFTRemoved + { + Symbol = minStartList[0].Symbol, + TokenId = minStartList[0].TokenId, + Duration = minStartList[0].Duration, + Owner = minStartList[0].Owner, + Price = minStartList[0].Price + }); + } + else + { + Context.Fire(new ListedNFTChanged + { + Symbol = minStartList[0].Symbol, + TokenId = minStartList[0].TokenId, + Duration = minStartList[0].Duration, + Owner = minStartList[0].Owner, + PreviousDuration = minStartList[0].Duration, + Quantity = minStartList[0].Quantity, + Price = minStartList[0].Price, + WhitelistId = whitelistId + }); + } + } + input.Quantity = input.Quantity.Sub(1); + if (input.Quantity == 0) + { + return new Empty(); + } + } + } + + var dealStatus = makeOfferService.GetDealStatus(input, out var affordableNftInfoList); + switch(dealStatus) + { + case DealStatus.NFTNotMined: + PerformRequestNewItem(input.Symbol, input.TokenId, input.Price, input.ExpireTime); + return new Empty(); + case DealStatus.NotDeal: + PerformMakeOffer(input); + return new Empty(); + } + Assert(nftInfo.Quantity > 0, "NFT does not exist."); + + if (listedNftInfoList.Value.All(i => i.ListType != ListType.FixedPrice)) + { + var auctionInfo = listedNftInfoList.Value.FirstOrDefault(); + if (auctionInfo == null || IsListedNftTimedOut(auctionInfo)) + { + PerformMakeOffer(input); + } + else + { + if (auctionInfo.ListType == ListType.EnglishAuction) + { + TryPlaceBidForEnglishAuction(input); + } + + if (auctionInfo.ListType == ListType.DutchAuction) + { + if (PerformMakeOfferToDutchAuction(input)) + { + listedNftInfoList.Value.Remove(auctionInfo); + } + } + } + + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][input.OfferTo] = listedNftInfoList; + + return new Empty(); + } + + var dealService = GetDealService(); + var getDealResultListInput = new GetDealResultListInput + { + MakeOfferInput = input, + ListedNftInfoList = new ListedNFTInfoList + { + Value = {affordableNftInfoList} + } + }; + var normalPriceDealResultList = dealService.GetDealResultList(getDealResultListInput).ToList(); + if (normalPriceDealResultList.Count == 0) + { + PerformMakeOffer(input); + return new Empty(); + } + var toRemove = new ListedNFTInfoList(); + foreach (var dealResult in normalPriceDealResultList) + { + if (!TryDealWithFixedPrice(input, dealResult, listedNftInfoList.Value[dealResult.Index],out var dealQuantity)) continue; + dealResult.Quantity = dealResult.Quantity.Sub(dealQuantity); + var listedNftInfo = listedNftInfoList.Value[dealResult.Index]; + listedNftInfo.Quantity = listedNftInfoList.Value[dealResult.Index].Quantity.Sub(dealQuantity); + input.Quantity = input.Quantity.Sub(dealQuantity); + if (listedNftInfoList.Value[dealResult.Index].Quantity == 0) + { + toRemove.Value.Add(listedNftInfoList.Value[dealResult.Index]); + } + else + { + Context.Fire(new ListedNFTChanged + { + Symbol = listedNftInfo.Symbol, + TokenId = listedNftInfo.TokenId, + Duration = listedNftInfo.Duration, + Owner = listedNftInfo.Owner, + PreviousDuration = listedNftInfo.Duration, + Quantity = listedNftInfo.Quantity, + Price = listedNftInfo.Price, + WhitelistId = whitelistId + }); + } + } + + if (toRemove.Value.Count != 0) + { + foreach (var info in toRemove.Value) + { + listedNftInfoList.Value.Remove(info); + Context.Fire(new ListedNFTRemoved + { + Symbol = info.Symbol, + TokenId = info.TokenId, + Duration = info.Duration, + Owner = info.Owner, + Price = info.Price + }); + } + } + + if (input.Quantity > 0) + { + PerformMakeOffer(input); + } + + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][input.OfferTo] = listedNftInfoList; + + return new Empty(); + } + + private bool TryDealWithFixedPriceWhitelist(MakeOfferInput input,Price price,Hash whitelistId) + { + Assert(input.Price.Symbol == price.Symbol, + $"Need to use token {price.Symbol}, not {input.Price.Symbol}"); + //Get extraInfoId according to the sender. + var extraInfoId = State.WhitelistContract.GetTagIdByAddress.Call(new GetTagIdByAddressInput() + { + WhitelistId = whitelistId, + Address = Context.Sender + }); + State.WhitelistContract.RemoveAddressInfoListFromWhitelist.Send(new RemoveAddressInfoListFromWhitelistInput() + { + WhitelistId = whitelistId, + ExtraInfoIdList = new ExtraInfoIdList() + { + Value = { new ExtraInfoId + { + AddressList = new AElf.Contracts.Whitelist.AddressList {Value = {Context.Sender}}, + Id = extraInfoId + } } + } + }); + var totalAmount = price.Amount.Mul(1); + PerformDeal(new PerformDealInput + { + NFTFrom = input.OfferTo, + NFTTo = Context.Sender, + NFTSymbol = input.Symbol, + NFTTokenId = input.TokenId, + NFTQuantity = 1, + PurchaseSymbol = price.Symbol, + PurchaseAmount = totalAmount, + PurchaseTokenId = input.Price.TokenId + }); + return true; + } + public override Empty CancelOffer(CancelOfferInput input) + { + AssertContractInitialized(); + + OfferList offerList; + var newOfferList = new OfferList(); + var requestInfo = State.RequestInfoMap[input.Symbol][input.TokenId]; + + // Admin can remove expired offer. + if (input.OfferFrom != null && input.OfferFrom != Context.Sender) + { + AssertSenderIsAdmin(); + + offerList = State.OfferListMap[input.Symbol][input.TokenId][input.OfferFrom]; + + if (offerList != null) + { + foreach (var offer in offerList.Value) + { + if (offer.ExpireTime >= Context.CurrentBlockTime) + { + newOfferList.Value.Add(offer); + } + else + { + Context.Fire(new OfferRemoved + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = offer.From, + OfferTo = offer.To, + ExpireTime = offer.ExpireTime + }); + } + } + + State.OfferListMap[input.Symbol][input.TokenId][input.OfferFrom] = newOfferList; + } + + if (requestInfo != null && !requestInfo.IsConfirmed && + requestInfo.ExpireTime > Context.CurrentBlockTime) + { + MaybeRemoveRequest(input.Symbol, input.TokenId); + var protocolVirtualAddressFrom = CalculateTokenHash(input.Symbol); + var protocolVirtualAddress = + Context.ConvertVirtualAddressToContractAddress(protocolVirtualAddressFrom); + var balanceOfNftProtocolVirtualAddress = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Symbol = requestInfo.Price.Symbol, + Owner = protocolVirtualAddress + }).Balance; + + if (balanceOfNftProtocolVirtualAddress > 0) + { + State.TokenContract.Transfer.VirtualSend(protocolVirtualAddressFrom, new TransferInput + { + To = requestInfo.Requester, + Symbol = requestInfo.Price.Symbol, + Amount = balanceOfNftProtocolVirtualAddress + }); + } + + Context.Fire(new NFTRequestCancelled + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Requester = Context.Sender + }); + } + + var bid = State.BidMap[input.Symbol][input.TokenId][input.OfferFrom]; + + if (bid != null) + { + if (bid.ExpireTime < Context.CurrentBlockTime) + { + State.BidMap[input.Symbol][input.TokenId].Remove(input.OfferFrom); + var auctionInfo = State.EnglishAuctionInfoMap[input.Symbol][input.TokenId]; + if (auctionInfo != null && auctionInfo.EarnestMoney > 0) + { + State.TokenContract.Transfer.VirtualSend(CalculateTokenHash(input.Symbol, input.TokenId), + new TransferInput + { + To = bid.From, + Symbol = auctionInfo.PurchaseSymbol, + Amount = auctionInfo.EarnestMoney + }); + } + + var bidAddressList = State.BidAddressListMap[input.Symbol][input.TokenId]; + if (bidAddressList != null && bidAddressList.Value.Contains(Context.Sender)) + { + State.BidAddressListMap[input.Symbol][input.TokenId].Value.Remove(Context.Sender); + } + + Context.Fire(new BidCanceled + { + Symbol = input.Symbol, + TokenId = input.TokenId, + BidFrom = bid.From, + BidTo = bid.To + }); + } + } + + return new Empty(); + } + + offerList = State.OfferListMap[input.Symbol][input.TokenId][Context.Sender]; + + // Check Request Map first. + if (requestInfo != null) + { + PerformCancelRequest(input, requestInfo); + // Only one request for each token id. + Context.Fire(new OfferRemoved + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = Context.Sender, + OfferTo = offerList.Value.FirstOrDefault()?.To, + ExpireTime = offerList.Value.FirstOrDefault()?.ExpireTime + }); + State.OfferListMap[input.Symbol][input.TokenId].Remove(Context.Sender); + return new Empty(); + } + + var nftInfo = State.NFTContract.GetNFTInfo.Call(new GetNFTInfoInput + { + Symbol = input.Symbol, + TokenId = input.TokenId + }); + if (nftInfo.Creator == null) + { + // This nft does not exist. + State.OfferListMap[input.Symbol][input.TokenId].Remove(Context.Sender); + } + + if (input.IsCancelBid) + { + var bid = State.BidMap[input.Symbol][input.TokenId][Context.Sender]; + if (bid != null) + { + var auctionInfo = State.EnglishAuctionInfoMap[input.Symbol][input.TokenId]; + var finishTime = auctionInfo.Duration.StartTime.AddHours(auctionInfo.Duration.DurationHours); + if (auctionInfo.DealTo != null || Context.CurrentBlockTime >= finishTime || + Context.CurrentBlockTime >= bid.ExpireTime) + { + if (auctionInfo.EarnestMoney > 0) + { + State.TokenContract.Transfer.VirtualSend(CalculateTokenHash(input.Symbol, input.TokenId), + new TransferInput + { + To = Context.Sender, + Symbol = auctionInfo.PurchaseSymbol, + Amount = auctionInfo.EarnestMoney + }); + } + } + + State.BidMap[input.Symbol][input.TokenId].Remove(Context.Sender); + + var bidAddressList = State.BidAddressListMap[input.Symbol][input.TokenId]; + if (bidAddressList != null && bidAddressList.Value.Contains(Context.Sender)) + { + State.BidAddressListMap[input.Symbol][input.TokenId].Value.Remove(Context.Sender); + } + + Context.Fire(new BidCanceled + { + Symbol = input.Symbol, + TokenId = input.TokenId, + BidFrom = Context.Sender, + BidTo = bid.To + }); + } + } + + if (input.IndexList != null && input.IndexList.Value.Any()) + { + for (var i = 0; i < offerList.Value.Count; i++) + { + if (!input.IndexList.Value.Contains(i)) + { + newOfferList.Value.Add(offerList.Value[i]); + } + else + { + Context.Fire(new OfferRemoved + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = Context.Sender, + OfferTo = offerList.Value[i].To, + ExpireTime = offerList.Value[i].ExpireTime + }); + } + } + + Context.Fire(new OfferCanceled + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = Context.Sender, + IndexList = input.IndexList + }); + } + else + { + newOfferList.Value.Add(offerList.Value); + } + + State.OfferListMap[input.Symbol][input.TokenId][Context.Sender] = newOfferList; + + return new Empty(); + } + + private void PerformCancelRequest(CancelOfferInput input, RequestInfo requestInfo) + { + Assert(requestInfo.Requester == Context.Sender, "No permission."); + var virtualAddress = CalculateNFTVirtuaAddress(input.Symbol, input.TokenId); + var balanceOfNftVirtualAddress = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Symbol = requestInfo.Price.Symbol, + Owner = virtualAddress + }).Balance; + + var depositReceiver = requestInfo.Requester; + + if (requestInfo.IsConfirmed) + { + if (requestInfo.ConfirmTime.AddHours(requestInfo.WorkHours) < Context.CurrentBlockTime) + { + // Creator missed the deadline. + + var protocolVirtualAddressFrom = CalculateTokenHash(input.Symbol); + var protocolVirtualAddress = + Context.ConvertVirtualAddressToContractAddress(protocolVirtualAddressFrom); + var balanceOfNftProtocolVirtualAddress = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Symbol = requestInfo.Price.Symbol, + Owner = protocolVirtualAddress + }).Balance; + var deposit = balanceOfNftVirtualAddress.Mul(FeeDenominator).Div(DefaultDepositConfirmRate) + .Sub(balanceOfNftVirtualAddress); + if (balanceOfNftProtocolVirtualAddress > 0) + { + State.TokenContract.Transfer.VirtualSend(protocolVirtualAddressFrom, new TransferInput + { + To = requestInfo.Requester, + Symbol = requestInfo.Price.Symbol, + Amount = Math.Min(balanceOfNftProtocolVirtualAddress, deposit) + }); + } + } + else + { + depositReceiver = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue {Value = input.Symbol}) + .Creator; + } + } + + var virtualAddressFrom = CalculateTokenHash(input.Symbol, input.TokenId); + + if (balanceOfNftVirtualAddress > 0) + { + State.TokenContract.Transfer.VirtualSend(virtualAddressFrom, new TransferInput + { + To = depositReceiver, + Symbol = requestInfo.Price.Symbol, + Amount = balanceOfNftVirtualAddress + }); + } + + MaybeRemoveRequest(input.Symbol, input.TokenId); + + Context.Fire(new NFTRequestCancelled + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Requester = Context.Sender + }); + } + + /// + /// Sender is buyer. + /// + private bool TryDealWithFixedPrice(MakeOfferInput input, DealResult dealResult ,ListedNFTInfo listedNftInfo,out long actualQuantity) + { + var usePrice = input.Price.Clone(); + usePrice.Amount = Math.Min(input.Price.Amount, dealResult.PurchaseAmount); + actualQuantity = Math.Min(input.Quantity, listedNftInfo.Quantity); + + var totalAmount = usePrice.Amount.Mul(actualQuantity); + PerformDeal(new PerformDealInput + { + NFTFrom = input.OfferTo, + NFTTo = Context.Sender, + NFTSymbol = input.Symbol, + NFTTokenId = input.TokenId, + NFTQuantity = actualQuantity, + PurchaseSymbol = usePrice.Symbol, + PurchaseAmount = totalAmount, + PurchaseTokenId = input.Price.TokenId + }); + + return true; + } + + /// + /// Will go to Offer List. + /// + /// + private void PerformMakeOffer(MakeOfferInput input) + { + var offerList = State.OfferListMap[input.Symbol][input.TokenId][Context.Sender] ?? new OfferList(); + var expireTime = input.ExpireTime ?? Context.CurrentBlockTime.AddDays(DefaultExpireDays); + var maybeSameOffer = offerList.Value.SingleOrDefault(o => + o.Price.Symbol == input.Price.Symbol && o.Price.Amount == input.Price.Amount && + o.ExpireTime == expireTime && o.To == input.OfferTo && o.From == Context.Sender); + if (maybeSameOffer == null) + { + offerList.Value.Add(new Offer + { + From = Context.Sender, + To = input.OfferTo, + Price = input.Price, + ExpireTime = expireTime, + Quantity = input.Quantity + }); + Context.Fire(new OfferAdded + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = Context.Sender, + OfferTo = input.OfferTo, + ExpireTime = expireTime, + Price = input.Price, + Quantity = input.Quantity + }); + } + else + { + maybeSameOffer.Quantity = maybeSameOffer.Quantity.Add(input.Quantity); + Context.Fire(new OfferChanged + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = Context.Sender, + OfferTo = input.OfferTo, + Price = input.Price, + ExpireTime = expireTime, + Quantity = maybeSameOffer.Quantity + }); + } + + State.OfferListMap[input.Symbol][input.TokenId][Context.Sender] = offerList; + + var addressList = State.OfferAddressListMap[input.Symbol][input.TokenId] ?? new AddressList(); + + if (!addressList.Value.Contains(Context.Sender)) + { + addressList.Value.Add(Context.Sender); + State.OfferAddressListMap[input.Symbol][input.TokenId] = addressList; + } + + Context.Fire(new OfferMade + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = Context.Sender, + OfferTo = input.OfferTo, + ExpireTime = expireTime, + Price = input.Price, + Quantity = input.Quantity + }); + } + + private void TryPlaceBidForEnglishAuction(MakeOfferInput input) + { + var auctionInfo = State.EnglishAuctionInfoMap[input.Symbol][input.TokenId]; + if (auctionInfo == null) + { + throw new AssertionException($"Auction info of {input.Symbol}-{input.TokenId} not found."); + } + + var duration = auctionInfo.Duration; + if (Context.CurrentBlockTime < duration.StartTime) + { + PerformMakeOffer(input); + return; + } + + Assert(Context.CurrentBlockTime <= duration.StartTime.AddHours(duration.DurationHours), + "Auction already finished."); + Assert(input.Price.Symbol == auctionInfo.PurchaseSymbol, "Incorrect symbol."); + Assert(input.Price.TokenId == 0, "Do not support use NFT to purchase auction."); + + if (input.Price.Amount < auctionInfo.StartingPrice) + { + PerformMakeOffer(input); + return; + } + + var bidList = GetBidList(new GetBidListInput + { + Symbol = input.Symbol, + TokenId = input.TokenId + }); + var sortedBitList = new BidList + { + Value = + { + bidList.Value.OrderByDescending(o => o.Price.Amount) + } + }; + if (sortedBitList.Value.Any() && input.Price.Amount <= sortedBitList.Value.First().Price.Amount) + { + PerformMakeOffer(input); + return; + } + + var bid = new Bid + { + From = Context.Sender, + To = input.OfferTo, + Price = new Price + { + Symbol = input.Price.Symbol, + Amount = input.Price.Amount + }, + ExpireTime = input.ExpireTime ?? Context.CurrentBlockTime.AddDays(DefaultExpireDays) + }; + + var bidAddressList = State.BidAddressListMap[input.Symbol][input.TokenId] ?? new AddressList(); + if (!bidAddressList.Value.Contains(Context.Sender)) + { + bidAddressList.Value.Add(Context.Sender); + State.BidAddressListMap[input.Symbol][input.TokenId] = bidAddressList; + // Charge earnest if the Sender is the first time to place a bid. + ChargeEarnestMoney(input.Symbol, input.TokenId, auctionInfo.PurchaseSymbol, auctionInfo.EarnestMoney); + } + + State.BidMap[input.Symbol][input.TokenId][Context.Sender] = bid; + + var remainAmount = input.Price.Amount.Sub(auctionInfo.EarnestMoney); + Assert( + State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Symbol = auctionInfo.PurchaseSymbol, + Owner = Context.Sender + }).Balance >= remainAmount, + "Insufficient balance to bid."); + Assert( + State.TokenContract.GetAllowance.Call(new GetAllowanceInput + { + Symbol = auctionInfo.PurchaseSymbol, + Owner = Context.Sender, + Spender = Context.Self + }).Allowance >= remainAmount, + "Insufficient allowance to bid."); + + Context.Fire(new BidPlaced + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Price = bid.Price, + ExpireTime = bid.ExpireTime, + OfferFrom = bid.From, + OfferTo = input.OfferTo + }); + } + + private void ChargeEarnestMoney(string nftSymbol, long nftTokenId, string purchaseSymbol, long earnestMoney) + { + if (earnestMoney > 0) + { + var virtualAddress = CalculateNFTVirtuaAddress(nftSymbol, nftTokenId); + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = virtualAddress, + Symbol = purchaseSymbol, + Amount = earnestMoney + }); + } + } + + private bool PerformMakeOfferToDutchAuction(MakeOfferInput input) + { + var auctionInfo = State.DutchAuctionInfoMap[input.Symbol][input.TokenId]; + if (auctionInfo == null) + { + throw new AssertionException($"Auction info of {input.Symbol}-{input.TokenId} not found."); + } + + var duration = auctionInfo.Duration; + if (Context.CurrentBlockTime < duration.StartTime) + { + PerformMakeOffer(input); + return false; + } + + Assert(Context.CurrentBlockTime <= duration.StartTime.AddHours(duration.DurationHours), + "Auction already finished."); + Assert(input.Price.Symbol == auctionInfo.PurchaseSymbol, "Incorrect symbol"); + var currentBiddingPrice = CalculateCurrentBiddingPrice(auctionInfo.StartingPrice, auctionInfo.EndingPrice, + auctionInfo.Duration); + if (input.Price.Amount < currentBiddingPrice) + { + PerformMakeOffer(input); + return false; + } + + PerformDeal(new PerformDealInput + { + NFTFrom = auctionInfo.Owner, + NFTTo = Context.Sender, + NFTQuantity = 1, + NFTSymbol = input.Symbol, + NFTTokenId = input.TokenId, + PurchaseSymbol = input.Price.Symbol, + PurchaseAmount = input.Price.Amount, + PurchaseTokenId = 0 + }); + return true; + } + + private long CalculateCurrentBiddingPrice(long startingPrice, long endingPrice, ListDuration duration) + { + var passedSeconds = (Context.CurrentBlockTime - duration.StartTime).Seconds; + var durationSeconds = duration.DurationHours.Mul(3600); + if (passedSeconds == 0) + { + return startingPrice; + } + + var diffPrice = endingPrice.Sub(startingPrice); + return Math.Max(startingPrice.Sub(diffPrice.Mul(durationSeconds).Div(passedSeconds)), endingPrice); + } + + private void MaybeReceiveRemainDeposit(RequestInfo requestInfo) + { + if (requestInfo == null) return; + Assert(Context.CurrentBlockTime > requestInfo.WhiteListDueTime, "Due time not passed."); + var nftProtocolInfo = + State.NFTContract.GetNFTProtocolInfo.Call((new StringValue {Value = requestInfo.Symbol})); + Assert(nftProtocolInfo.Creator == Context.Sender, "Only NFT Protocol Creator can claim remain deposit."); + + var nftVirtualAddressFrom = CalculateTokenHash(requestInfo.Symbol, requestInfo.TokenId); + var nftVirtualAddress = Context.ConvertVirtualAddressToContractAddress(nftVirtualAddressFrom); + var balance = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Symbol = requestInfo.Price.Symbol, + Owner = nftVirtualAddress + }).Balance; + if (balance > 0) + { + State.TokenContract.Transfer.VirtualSend(nftVirtualAddressFrom, new TransferInput + { + To = nftProtocolInfo.Creator, + Symbol = requestInfo.Price.Symbol, + Amount = balance + }); + } + + MaybeRemoveRequest(requestInfo.Symbol, requestInfo.TokenId); + } + + public override Empty MintBadge(MintBadgeInput input) + { + var protocol = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue {Value = input.Symbol}); + Assert(!string.IsNullOrWhiteSpace(protocol.Symbol), $"Protocol {input.Symbol} not found."); + Assert(protocol.NftType.ToUpper() == NFTType.Badges.ToString().ToUpper(), + "This method is only for badges."); + var nftInfo = State.NFTContract.GetNFTInfo.Call(new GetNFTInfoInput + { + Symbol = input.Symbol, + TokenId = input.TokenId + }); + Assert(nftInfo.TokenId > 0, "Badge not found."); + Assert(nftInfo.Metadata.Value.ContainsKey(BadgeMintWhitelistIdMetadataKey), + $"Metadata {BadgeMintWhitelistIdMetadataKey} not found."); + var whitelistIdHex = nftInfo.Metadata.Value[BadgeMintWhitelistIdMetadataKey]; + Assert(!string.IsNullOrWhiteSpace(whitelistIdHex),$"No whitelist.{whitelistIdHex}"); + var whitelistId = Hash.LoadFromHex(whitelistIdHex); + //Whether NFT Market Contract is the manager. + var isManager = State.WhitelistContract.GetManagerExistFromWhitelist.Call(new GetManagerExistFromWhitelistInput() + { + WhitelistId = whitelistId, + Manager = Context.Self + }); + Assert(isManager.Value == true,"NFT Market Contract does not in the manager list."); + // Is Context.Sender in whitelist + var ifExist = State.WhitelistContract.GetAddressFromWhitelist.Call(new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = Context.Sender + }); + Assert(ifExist.Value,$"No permission.{Context.Sender}"); + State.NFTContract.Mint.Send(new MintInput + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Owner = Context.Sender, + Quantity = 1 + }); + State.WhitelistContract.RemoveAddressInfoListFromWhitelist.Send(new RemoveAddressInfoListFromWhitelistInput() + { + WhitelistId = whitelistId, + ExtraInfoIdList = new ExtraInfoIdList() + { + Value = { new ExtraInfoId() + { + AddressList = new AElf.Contracts.Whitelist.AddressList{Value = { Context.Sender }} + } } + } + }); + return new Empty(); + } +} \ No newline at end of file diff --git a/contract/Forest/ForestContract_Creators.cs b/contract/Forest/ForestContract_Creators.cs new file mode 100644 index 00000000..918da859 --- /dev/null +++ b/contract/Forest/ForestContract_Creators.cs @@ -0,0 +1,260 @@ +using System; +using AElf.Contracts.MultiToken; +using AElf.Contracts.NFT; +using AElf.CSharp.Core; +using AElf.CSharp.Core.Extension; +using AElf.Sdk.CSharp; +using Google.Protobuf.WellKnownTypes; +using GetBalanceInput = AElf.Contracts.MultiToken.GetBalanceInput; +using TransferFromInput = AElf.Contracts.MultiToken.TransferFromInput; +using TransferInput = AElf.Contracts.MultiToken.TransferInput; + +namespace Forest; + +public partial class ForestContract +{ + public override Empty SetRoyalty(SetRoyaltyInput input) + { + AssertContractInitialized(); + + // 0% - 10% + Assert(0 <= input.Royalty && input.Royalty <= 1000, "Royalty should be between 0% to 10%."); + var nftProtocolInfo = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue { Value = input.Symbol }); + Assert(!string.IsNullOrEmpty(nftProtocolInfo.Symbol), "NFT Protocol not found."); + if (input.TokenId == 0) + { + Assert(nftProtocolInfo.Creator == Context.Sender, + "Only NFT Protocol Creator can set royalty for whole protocol."); + // Set for whole NFT Protocol. + State.RoyaltyMap[input.Symbol] = input.Royalty; + } + else + { + var nftInfo = State.NFTContract.GetNFTInfo.Call(new GetNFTInfoInput + { + Symbol = input.Symbol, + TokenId = input.TokenId + }); + Assert(nftProtocolInfo.Creator == Context.Sender || nftInfo.Minters.Contains(Context.Sender), + "No permission."); + State.CertainNFTRoyaltyMap[input.Symbol][input.TokenId] = new CertainNFTRoyaltyInfo + { + IsManuallySet = true, + Royalty = input.Royalty + }; + } + + State.RoyaltyFeeReceiverMap[input.Symbol] = input.RoyaltyFeeReceiver; + return new Empty(); + } + + public override Empty SetTokenWhiteList(SetTokenWhiteListInput input) + { + AssertContractInitialized(); + + var nftProtocolInfo = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue { Value = input.Symbol }); + Assert(nftProtocolInfo.Creator != null, "NFT Protocol not found."); + Assert(nftProtocolInfo.Creator == Context.Sender, "Only NFT Protocol Creator can set token white list."); + State.TokenWhiteListMap[input.Symbol] = input.TokenWhiteList; + Context.Fire(new TokenWhiteListChanged + { + Symbol = input.Symbol, + TokenWhiteList = input.TokenWhiteList + }); + return new Empty(); + } + + public override Empty SetCustomizeInfo(CustomizeInfo input) + { + AssertContractInitialized(); + + if (input.Price == null) + { + var customizeInfo = State.CustomizeInfoMap[input.Symbol]; + if (customizeInfo?.Price == null) return new Empty(); + + State.CustomizeInfoMap[input.Symbol].Price.Amount = 0; + return new Empty(); + } + + Assert(input.StakingAmount >= 0, "Invalid staking amount."); + Assert(input.DepositRate >= 0, "Invalid deposit rate."); + var nftProtocolInfo = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue { Value = input.Symbol }); + Assert(!string.IsNullOrEmpty(nftProtocolInfo.Symbol), "NFT Protocol not found."); + Assert(nftProtocolInfo.Creator == Context.Sender, "Only NFT Protocol Creator can set customize info."); + Assert(!nftProtocolInfo.IsTokenIdReuse, "Not support customize."); + var tokenInfo = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput { Symbol = input.Price.Symbol }); + Assert(!string.IsNullOrEmpty(tokenInfo.Symbol), "Invalid staking token symbol."); + if (input.StakingAmount > 0) + { + var virtualAddress = CalculateNFTVirtuaAddress(input.Symbol); + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = virtualAddress, + Symbol = input.Price.Symbol, + Amount = input.StakingAmount + }); + } + + State.CustomizeInfoMap[input.Symbol] = input; + Context.Fire(new CustomizeInfoSet + { + Symbol = input.Symbol, + Price = input.Price, + DepositRate = input.DepositRate, + StakingAmount = input.StakingAmount, + WhiteListHours = input.WhiteListHours, + WorkHours = input.WorkHours + }); + return new Empty(); + } + + public override Empty StakeForRequests(StakeForRequestsInput input) + { + AssertContractInitialized(); + + Assert(input.StakingAmount > 0, "Invalid staking amount."); + var nftProtocolInfo = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue { Value = input.Symbol }); + Assert(!string.IsNullOrEmpty(nftProtocolInfo.Symbol), "NFT Protocol not found."); + Assert(nftProtocolInfo.Creator == Context.Sender, "Only NFT Protocol Creator can stake for requests."); + var customizeInfo = State.CustomizeInfoMap[input.Symbol]; + if (customizeInfo == null) throw new AssertionException("Customize info not found."); + + var virtualAddress = CalculateNFTVirtuaAddress(input.Symbol); + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = virtualAddress, + Symbol = customizeInfo.Price.Symbol, + Amount = input.StakingAmount + }); + customizeInfo.StakingAmount = customizeInfo.StakingAmount.Add(input.StakingAmount); + State.CustomizeInfoMap[input.Symbol] = customizeInfo; + Context.Fire(new StakingAmountChanged + { + Symbol = input.Symbol, + StakingAmount = customizeInfo.StakingAmount + }); + return new Empty(); + } + + public override Empty WithdrawStakingTokens(WithdrawStakingTokensInput input) + { + AssertContractInitialized(); + + Assert(input.WithdrawAmount > 0, "Invalid withdraw amount."); + var nftProtocolInfo = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue { Value = input.Symbol }); + Assert(nftProtocolInfo.Creator == Context.Sender, "NFT symbol does not exist."); + var customizeInfo = State.CustomizeInfoMap[input.Symbol]; + Assert(input.WithdrawAmount <= customizeInfo.StakingAmount, "Insufficient staking amount."); + Assert(customizeInfo.ReservedTokenIds.Count == 0, + "Cannot withdraw staking tokens before complete all the demands."); + var virtualAddressFrom = CalculateTokenHash(input.Symbol); + State.TokenContract.Transfer.VirtualSend(virtualAddressFrom, new TransferInput + { + To = Context.Sender, + Symbol = customizeInfo.Price.Symbol, + Amount = input.WithdrawAmount + }); + customizeInfo.StakingAmount = customizeInfo.StakingAmount.Sub(input.WithdrawAmount); + State.CustomizeInfoMap[input.Symbol] = customizeInfo; + Context.Fire(new StakingAmountChanged + { + Symbol = input.Symbol, + StakingAmount = customizeInfo.StakingAmount + }); + return new Empty(); + } + + public override Empty HandleRequest(HandleRequestInput input) + { + AssertContractInitialized(); + + var requestInfo = State.RequestInfoMap[input.Symbol][input.TokenId]; + if (requestInfo == null) throw new AssertionException("Request not exists."); + + var nftProtocolInfo = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue { Value = input.Symbol }); + Assert(nftProtocolInfo.Creator == Context.Sender, "Only NFT Protocol Creator can handle request."); + + var nftVirtualAddressFrom = CalculateTokenHash(input.Symbol, input.TokenId); + var nftVirtualAddress = Context.ConvertVirtualAddressToContractAddress(nftVirtualAddressFrom); + var nftVirtualAddressBalance = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Symbol = requestInfo.Price.Symbol, + Owner = nftVirtualAddress + }).Balance; + + if (input.IsConfirm) + { + requestInfo.IsConfirmed = true; + requestInfo.ConfirmTime = Context.CurrentBlockTime; + requestInfo.WorkHours = Math.Min(requestInfo.WorkHoursFromCustomizeInfo, + (requestInfo.ExpireTime - Context.CurrentBlockTime).Seconds.Div(3600)); + requestInfo.WhiteListDueTime = requestInfo.ConfirmTime.AddHours(requestInfo.WorkHours) + .AddHours(requestInfo.WhiteListHours); + State.RequestInfoMap[input.Symbol][input.TokenId] = requestInfo; + + var transferAmount = nftVirtualAddressBalance.Mul(DefaultDepositConfirmRate).Div(FeeDenominator); + var serviceFee = transferAmount.Mul(State.ServiceFeeRate.Value).Div(FeeDenominator); + transferAmount = transferAmount.Sub(serviceFee); + + if (transferAmount > 0) + State.TokenContract.Transfer.VirtualSend(nftVirtualAddressFrom, new TransferInput + { + To = Context.Sender, + Symbol = requestInfo.Price.Symbol, + Amount = transferAmount + }); + + if (serviceFee > 0) + State.TokenContract.Transfer.VirtualSend(nftVirtualAddressFrom, new TransferInput + { + To = State.ServiceFeeReceiver.Value, + Symbol = requestInfo.Price.Symbol, + Amount = serviceFee + }); + + Context.Fire(new NewNFTRequestConfirmed + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Requester = input.Requester, + ConfirmedWorkHours = requestInfo.WorkHours, + Price = requestInfo.Price + }); + } + else + { + MaybeRemoveRequest(input.Symbol, input.TokenId); + if (nftVirtualAddressBalance > 0) + State.TokenContract.Transfer.VirtualSend(nftVirtualAddressFrom, new TransferInput + { + To = requestInfo.Requester, + Symbol = requestInfo.Price.Symbol, + Amount = nftVirtualAddressBalance + }); + + Context.Fire(new NewNFTRequestRejected + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Requester = input.Requester + }); + } + + return new Empty(); + } + + public override Empty ClaimRemainDeposit(ClaimRemainDepositInput input) + { + AssertContractInitialized(); + + var requestInfo = State.RequestInfoMap[input.Symbol][input.TokenId]; + if (requestInfo == null) throw new AssertionException("Request info does not exist."); + + MaybeReceiveRemainDeposit(requestInfo); + + return new Empty(); + } +} \ No newline at end of file diff --git a/contract/Forest/ForestContract_Helpers.cs b/contract/Forest/ForestContract_Helpers.cs new file mode 100644 index 00000000..88a50926 --- /dev/null +++ b/contract/Forest/ForestContract_Helpers.cs @@ -0,0 +1,726 @@ +using System; +using System.Linq; +using AElf; +using AElf.Contracts.NFT; +using AElf.Contracts.Whitelist; +using AElf.CSharp.Core; +using AElf.CSharp.Core.Extension; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using TransferFromInput = AElf.Contracts.MultiToken.TransferFromInput; +using TransferInput = AElf.Contracts.MultiToken.TransferInput; + +namespace Forest; + +public partial class ForestContract +{ + private void CheckSenderNFTBalanceAndAllowance(string symbol, long tokenId, long quantity) + { + var balance = State.NFTContract.GetBalance.Call(new GetBalanceInput + { + Symbol = symbol, + TokenId = tokenId, + Owner = Context.Sender + }).Balance; + Assert(balance >= quantity, $"Check sender NFT balance failed. {balance} / {quantity}"); + var allowance = State.NFTContract.GetAllowance.Call(new GetAllowanceInput + { + Symbol = symbol, + TokenId = tokenId, + Owner = Context.Sender, + Spender = Context.Self + }).Allowance; + Assert(allowance >= quantity, $"Check sender NFT allowance failed. {allowance} / {quantity}"); + } + + private bool CheckAllowanceAndBalanceIsEnough(Address owner, string symbol, long enoughAmount) + { + var balance = State.TokenContract.GetBalance.Call(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = symbol, + Owner = owner + }).Balance; + if (balance < enoughAmount) return false; + var allowance = State.TokenContract.GetAllowance.Call(new AElf.Contracts.MultiToken.GetAllowanceInput + { + Symbol = symbol, + Owner = owner, + Spender = Context.Self + }).Allowance; + return allowance >= enoughAmount; + } + + private void PayRemainDepositInCustomizeCase(PerformDealInput performDealInput) + { + var requestInfo = State.RequestInfoMap[performDealInput.NFTSymbol][performDealInput.NFTTokenId]; + if (requestInfo == null) return; + var nftVirtualAddressFrom = CalculateTokenHash(performDealInput.NFTSymbol, performDealInput.NFTTokenId); + var nftVirtualAddress = Context.ConvertVirtualAddressToContractAddress(nftVirtualAddressFrom); + var balanceOfNftVirtualAddress = State.TokenContract.GetBalance.Call(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = performDealInput.PurchaseSymbol, + Owner = nftVirtualAddress + }).Balance; + var transferAmount = balanceOfNftVirtualAddress; + var serviceFee = transferAmount.Mul(State.ServiceFeeRate.Value).Div(FeeDenominator); + transferAmount = transferAmount.Sub(serviceFee); + if (transferAmount > 0) + { + State.TokenContract.Transfer.VirtualSend(nftVirtualAddressFrom, new TransferInput + { + To = performDealInput.NFTFrom, + Symbol = requestInfo.Price.Symbol, + Amount = transferAmount + }); + } + + if (serviceFee > 0) + { + State.TokenContract.Transfer.VirtualSend(nftVirtualAddressFrom, new TransferInput + { + To = State.ServiceFeeReceiver.Value, + Symbol = requestInfo.Price.Symbol, + Amount = serviceFee + }); + } + } + + private void PerformDeal(PerformDealInput performDealInput) + { + Assert(performDealInput.NFTFrom != performDealInput.NFTTo, "NFT From address cannot be NFT To address."); + PayRemainDepositInCustomizeCase(performDealInput); + if (performDealInput.PurchaseTokenId == 0) + { + var serviceFee = performDealInput.PurchaseAmount.Mul(State.ServiceFeeRate.Value).Div(FeeDenominator); + var royalty = GetRoyalty(new GetRoyaltyInput + { + Symbol = performDealInput.NFTSymbol, + TokenId = performDealInput.NFTTokenId + }); + + var royaltyFee = performDealInput.PurchaseAmount.Mul(royalty.Royalty).Div(FeeDenominator); + var royaltyFeeReceiver = State.RoyaltyFeeReceiverMap[performDealInput.NFTSymbol]; + if (royaltyFeeReceiver == null) + { + royaltyFee = 0; + } + + var actualAmount = performDealInput.PurchaseAmount.Sub(serviceFee).Sub(royaltyFee); + //Assert(actualAmount > 0, "Incorrect deal amount."); + if (actualAmount != 0) + { + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = performDealInput.NFTTo, + To = performDealInput.NFTFrom, + Symbol = performDealInput.PurchaseSymbol, + Amount = actualAmount + }); + if (serviceFee > 0 && performDealInput.NFTTo != State.ServiceFeeReceiver.Value) + { + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = performDealInput.NFTTo, + To = State.ServiceFeeReceiver.Value, + Symbol = performDealInput.PurchaseSymbol, + Amount = serviceFee + }); + } + + if (royaltyFeeReceiver != null && royaltyFee > 0) + { + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = performDealInput.NFTTo, + To = royaltyFeeReceiver, + Symbol = performDealInput.PurchaseSymbol, + Amount = royaltyFee + }); + } + } + } + else + { + // Exchange NFTs for NFTs. + + State.NFTContract.TransferFrom.Send(new AElf.Contracts.NFT.TransferFromInput + { + From = performDealInput.NFTTo, + To = performDealInput.NFTFrom, + Symbol = performDealInput.PurchaseSymbol, + TokenId = performDealInput.PurchaseTokenId, + Amount = performDealInput.PurchaseAmount + }); + + if (State.ServiceFee.Value > 0) + { + // Charge a fixed service fee. + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = performDealInput.NFTTo, + To = State.ServiceFeeReceiver.Value, + Symbol = Context.Variables.NativeSymbol, + Amount = State.ServiceFee.Value + }); + } + } + + State.NFTContract.TransferFrom.Send(new AElf.Contracts.NFT.TransferFromInput + { + From = performDealInput.NFTFrom, + To = performDealInput.NFTTo, + Symbol = performDealInput.NFTSymbol, + TokenId = performDealInput.NFTTokenId, + Amount = performDealInput.NFTQuantity + }); + + Context.Fire(new Sold + { + NftFrom = performDealInput.NFTFrom, + NftTo = performDealInput.NFTTo, + NftSymbol = performDealInput.NFTSymbol, + NftTokenId = performDealInput.NFTTokenId, + NftQuantity = performDealInput.NFTQuantity, + PurchaseSymbol = performDealInput.PurchaseSymbol, + PurchaseTokenId = performDealInput.PurchaseTokenId, + PurchaseAmount = performDealInput.PurchaseAmount + }); + } + + private struct PerformDealInput + { + public Address NFTFrom { get; set; } + public Address NFTTo { get; set; } + public string NFTSymbol { get; set; } + public long NFTTokenId { get; set; } + public long NFTQuantity { get; set; } + public string PurchaseSymbol { get; set; } + + /// + /// If PurchaseSymbol is a Fungible token, PurchaseTokenIs shall always be 0. + /// + public long PurchaseTokenId { get; set; } + + /// + /// Be aware of that this stands for total amount. + /// + public long PurchaseAmount { get; set; } + } + + private StringList GetTokenWhiteList(string symbol) + { + var tokenWhiteList = State.TokenWhiteListMap[symbol] ?? State.GlobalTokenWhiteList.Value; + foreach (var globalWhiteListSymbol in State.GlobalTokenWhiteList.Value.Value) + { + if (!tokenWhiteList.Value.Contains(globalWhiteListSymbol)) + { + tokenWhiteList.Value.Add(globalWhiteListSymbol); + } + } + + return tokenWhiteList; + } + + private void PerformRequestNewItem(string symbol, long tokenId, Price price, Timestamp expireTime) + { + var customizeInfo = State.CustomizeInfoMap[symbol]; + if (customizeInfo?.Price == null || customizeInfo.Price.Amount == 0) + { + throw new AssertionException("Cannot request new item for this protocol."); + } + + Assert(State.RequestInfoMap[symbol][tokenId] == null, "Request already existed."); + + var nftVirtualAddress = CalculateNFTVirtuaAddress(symbol, tokenId); + var priceSymbol = customizeInfo.Price.Symbol; + var priceAmount = price.Amount == 0 + ? customizeInfo.Price.Amount + : Math.Max(price.Amount, customizeInfo.Price.Amount); + Assert(price.Symbol == customizeInfo.Price.Symbol, "Incorrect symbol."); + Assert(priceAmount >= customizeInfo.Price.Amount, "Incorrect amount."); + // Check allowance. + var allowance = State.TokenContract.GetAllowance.Call(new AElf.Contracts.MultiToken.GetAllowanceInput + { + Symbol = priceSymbol, + Owner = Context.Sender, + Spender = Context.Self + }).Allowance; + Assert(allowance >= priceAmount, "Insufficient allowance."); + + var deposit = priceAmount.Mul(customizeInfo.DepositRate).Div(FeeDenominator); + if (deposit > 0) + { + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = nftVirtualAddress, + Symbol = priceSymbol, + Amount = deposit + }); + } + + var defaultExpireTime = Context.CurrentBlockTime.AddDays(DefaultExpireDays); + State.RequestInfoMap[symbol][tokenId] = new RequestInfo + { + Symbol = symbol, + TokenId = tokenId, + DepositRate = customizeInfo.DepositRate, + Price = new Price + { + Symbol = priceSymbol, + Amount = priceAmount + }, + WhiteListHours = customizeInfo.WhiteListHours, + WorkHoursFromCustomizeInfo = customizeInfo.WorkHours, + Requester = Context.Sender, + ExpireTime = expireTime ?? defaultExpireTime + }; + + customizeInfo.ReservedTokenIds.Add(tokenId); + State.CustomizeInfoMap[symbol] = customizeInfo; + + Context.Fire(new NewNFTRequested + { + Symbol = symbol, + Requester = Context.Sender, + TokenId = tokenId + }); + } + + private bool CanBeListedWithAuction(string symbol, long tokenId, out RequestInfo requestInfo) + { + requestInfo = State.RequestInfoMap[symbol][tokenId]; + + var nftProtocolInfo = State.NFTContract.GetNFTProtocolInfo.Call(new StringValue {Value = symbol}); + if (nftProtocolInfo.IsTokenIdReuse) + { + return false; + } + + var nftInfo = State.NFTContract.GetNFTInfo.Call(new GetNFTInfoInput + { + Symbol = symbol, + TokenId = tokenId + }); + if (nftInfo.Quantity != 1) + { + return false; + } + + if (requestInfo != null) + { + if (requestInfo.IsConfirmed && requestInfo.ListTime == null) + { + // Confirmed but never listed by fixed price. + return false; + } + + var whitelistDueTime1 = requestInfo.ConfirmTime.AddHours(requestInfo.WorkHours) + .AddHours(requestInfo.WhiteListHours); + var whiteListDueTime2 = requestInfo.ListTime.AddHours(requestInfo.WhiteListHours); + if (Context.CurrentBlockTime <= whitelistDueTime1 || Context.CurrentBlockTime <= whiteListDueTime2) + { + return false; + } + } + + return true; + } + + private Hash CalculateTokenHash(string symbol, long tokenId = 0) + { + return HashHelper.ComputeFrom($"{symbol}{tokenId}"); + } + + private Address CalculateNFTVirtuaAddress(string symbol, long tokenId = 0) + { + return Context.ConvertVirtualAddressToContractAddress(CalculateTokenHash(symbol, tokenId)); + } + + private ListDuration AdjustListDuration(ListDuration duration) + { + if (duration == null) + { + duration = new ListDuration + { + StartTime = Context.CurrentBlockTime, + PublicTime = Context.CurrentBlockTime, + DurationHours = int.MaxValue + }; + } + else + { + if (duration.StartTime == null || duration.StartTime < Context.CurrentBlockTime) + { + duration.StartTime = Context.CurrentBlockTime; + } + + if (duration.PublicTime == null || duration.PublicTime < duration.StartTime) + { + duration.PublicTime = duration.StartTime; + } + + if (duration.DurationHours == 0) + { + duration.DurationHours = int.MaxValue; + } + } + + return duration; + } + + private void ListRequestedNFT(ListWithFixedPriceInput input, RequestInfo requestInfo, + WhitelistInfoList whitelistInfo) + { + if (whitelistInfo == null) + { + throw new AssertionException("Incorrect white list address price list."); + } + var projectId = CalculateProjectId(input.Symbol, input.TokenId, Context.Sender); + var whitelistId = State.WhitelistIdMap[projectId]; + //TODO:Whether to adjust whitelist info to be correct. + Assert(whitelistInfo.Whitelists.Count == 1 && + whitelistInfo.Whitelists.Any(p => + p.AddressList.Value.Contains(requestInfo.Requester) && p.AddressList.Value.Count == 1), + "Incorrect white list address price list."); + Assert(input.Price.Symbol == requestInfo.Price.Symbol, $"Need to use token {requestInfo.Price.Symbol}"); + + var supposedPublicTime1 = Context.CurrentBlockTime.AddHours(requestInfo.WhiteListHours); + var supposedPublicTime2 = requestInfo.ConfirmTime.AddHours(requestInfo.WorkHours) + .AddHours(requestInfo.WhiteListHours); + Assert( + input.Duration.PublicTime >= supposedPublicTime1 && + input.Duration.PublicTime >= supposedPublicTime2, "Incorrect white list hours."); + + Assert(requestInfo.Price.Amount <= input.Price.Amount, "List price too low."); + + var whiteListRemainPrice = + requestInfo.Price.Amount.Sub(requestInfo.Price.Amount.Mul(requestInfo.DepositRate) + .Div(FeeDenominator)); + var delayDuration = Context.CurrentBlockTime - requestInfo.ConfirmTime.AddHours(requestInfo.WorkHours); + if (delayDuration.Seconds > 0) + { + var reducePrice = whiteListRemainPrice.Mul(delayDuration.Seconds) + .Div(delayDuration.Seconds.Add(requestInfo.WorkHours.Mul(3600))); + whiteListRemainPrice = whiteListRemainPrice.Sub(reducePrice); + } + + whitelistInfo.Whitelists[0].PriceTag.Price.Amount = Math.Min(input.Price.Amount, + Math.Min(whiteListRemainPrice, whitelistInfo.Whitelists[0].PriceTag.Price.Amount)); + var tagName = $"Requested {whitelistInfo.Whitelists[0].PriceTag.Price.Amount}{whitelistInfo.Whitelists[0].PriceTag.Price.Symbol}"; + if (whitelistId == null) + { + whitelistInfo.Whitelists[0].PriceTag.TagName = tagName; + var extraInfoList = ConvertToExtraInfo(whitelistInfo); + State.WhitelistContract.CreateWhitelist.Send(new CreateWhitelistInput() + { + ProjectId = projectId, + StrategyType = StrategyType.Price, + Creator = Context.Self, + ExtraInfoList = extraInfoList, + IsCloneable = true, + Remark = $"{input.Symbol}{input.TokenId}", + ManagerList = new AElf.Contracts.Whitelist.AddressList + { + Value = { Context.Sender } + } + }); + whitelistId = + Context.GenerateId(State.WhitelistContract.Value, + ByteArrayHelper.ConcatArrays(Context.Self.ToByteArray(), projectId.ToByteArray())); + State.WhitelistIdMap[projectId] = whitelistId; + } + else + { + var tagId = HashHelper.ComputeFrom( + $"{whitelistId}" + + $"{projectId}{tagName}"); + var ifExist = State.WhitelistContract.GetTagInfoFromWhitelist.Call(new GetTagInfoFromWhitelistInput() + { + WhitelistId = whitelistId, + ProjectId = projectId, + TagInfo = new TagInfo + { + TagName = tagName, + Info = new PriceTag + { + Symbol = whitelistInfo.Whitelists[0].PriceTag.Price.Symbol, + Amount = whitelistInfo.Whitelists[0].PriceTag.Price.Amount + }.ToByteString() + } + }).Value; + if (ifExist) + { + State.WhitelistContract.AddAddressInfoListToWhitelist.Send(new AddAddressInfoListToWhitelistInput() + { + WhitelistId = whitelistId, + ExtraInfoIdList = new ExtraInfoIdList + { + Value = { new ExtraInfoId + { + AddressList = new AElf.Contracts.Whitelist.AddressList + { + Value = {whitelistInfo.Whitelists[0].AddressList.Value.First()} + }, + Id = tagId + } } + } + }); + } + else + { + State.WhitelistContract.AddExtraInfo.Send(new AddExtraInfoInput() + { + WhitelistId = whitelistId, + ProjectId = projectId, + TagInfo = new TagInfo() + { + TagName = tagName, + Info = new PriceTag + { + Symbol = whitelistInfo.Whitelists[0].PriceTag.Price.Symbol, + Amount = whitelistInfo.Whitelists[0].PriceTag.Price.Amount + }.ToByteString() + }, + AddressList = new AElf.Contracts.Whitelist.AddressList() + { + Value = {whitelistInfo.Whitelists[0].AddressList.Value.First()} + } + }); + } + } + requestInfo.ListTime = Context.CurrentBlockTime; + State.RequestInfoMap[input.Symbol][input.TokenId] = requestInfo; + } + + private void ClearBids(string symbol, long tokenId, Address except = null) + { + var bidAddressList = State.BidAddressListMap[symbol][tokenId]; + var auctionInfo = State.EnglishAuctionInfoMap[symbol][tokenId]; + + if (bidAddressList == null || !bidAddressList.Value.Any() || auctionInfo == null) return; + + if (except != null) + { + bidAddressList.Value.Remove(except); + } + + var transferredEarnestMoney = 0L; + foreach (var bidAddress in bidAddressList.Value) + { + if (auctionInfo.EarnestMoney > 0) + { + var earnestMoneyReceiver = State.BidMap[symbol][tokenId] == null ? auctionInfo.Owner : bidAddress; + State.TokenContract.Transfer.VirtualSend(CalculateTokenHash(symbol, tokenId), + new TransferInput + { + To = earnestMoneyReceiver, + Symbol = auctionInfo.PurchaseSymbol, + Amount = auctionInfo.EarnestMoney + }); + transferredEarnestMoney = transferredEarnestMoney.Add(auctionInfo.EarnestMoney); + } + + State.BidMap[symbol][tokenId].Remove(bidAddress); + + Context.Fire(new BidCanceled + { + Symbol = symbol, + TokenId = tokenId, + BidFrom = bidAddress, + BidTo = Context.Sender, + }); + } + + State.BidAddressListMap[symbol].Remove(tokenId); + + var virtualAddressBalance = State.TokenContract.GetBalance.Call(new AElf.Contracts.MultiToken.GetBalanceInput + { + Owner = CalculateNFTVirtuaAddress(symbol, tokenId), + Symbol = auctionInfo.PurchaseSymbol + }).Balance; + var remainAmount = virtualAddressBalance.Sub(transferredEarnestMoney); + if (except != null) + { + remainAmount = remainAmount.Sub(auctionInfo.EarnestMoney.Mul(except.Value.Length)); + } + if (remainAmount > 0) + { + State.TokenContract.Transfer.VirtualSend(CalculateTokenHash(symbol, tokenId), + new TransferInput + { + To = auctionInfo.Owner, + Symbol = auctionInfo.PurchaseSymbol, + Amount = remainAmount + }); + } + } + + private void MaybeRemoveRequest(string symbol, long tokenId) + { + State.RequestInfoMap[symbol].Remove(tokenId); + if (State.CustomizeInfoMap[symbol] != null && State.CustomizeInfoMap[symbol].ReservedTokenIds != null && + State.CustomizeInfoMap[symbol].ReservedTokenIds.Any()) + { + if (State.CustomizeInfoMap[symbol].ReservedTokenIds.Contains(tokenId)) + { + State.CustomizeInfoMap[symbol].ReservedTokenIds.Remove(tokenId); + } + } + } + + private bool IsListedNftTimedOut(ListedNFTInfo listedNftInfo) + { + var expireTime = listedNftInfo.Duration.StartTime.AddHours(listedNftInfo.Duration.DurationHours); + return Context.CurrentBlockTime > expireTime; + } + + private Price DeserializedInfo(TagInfo tagInfo) + { + var deserializedInfo = new PriceTag(); + deserializedInfo.MergeFrom(tagInfo.Info); + return new Price + { + Symbol = deserializedInfo.Symbol, + Amount = deserializedInfo.Amount + }; + } + + private Hash CalculateProjectId(string symbol, long tokenId,Address sender) + { + return HashHelper.ComputeFrom($"{symbol}{tokenId}{sender}"); + } + + private ExtraInfoList ConvertToExtraInfo(WhitelistInfoList input) + { + var extraInfoList = new ExtraInfoList(); + if (input == null) + { + return extraInfoList; + } + foreach (var whitelist in input.Whitelists) + { + var extraInfo = new ExtraInfo + { + AddressList = new AElf.Contracts.Whitelist.AddressList {Value = {whitelist.AddressList.Value}}, + Info = new TagInfo + { + TagName = whitelist.PriceTag.TagName, + Info = new PriceTag + { + Symbol = whitelist.PriceTag.Price.Symbol, + Amount = whitelist.PriceTag.Price.Amount + }.ToByteString() + } + }; + extraInfoList.Value.Add(extraInfo); + } + + return extraInfoList; + } + + private void DealRequestInfoInWhitelist(ListWithFixedPriceInput input,ListDuration duration,RequestInfo requestInfo) + { + bool isWhiteListDueTimePassed; + if (requestInfo.ListTime == null) // Never listed before or delisted before. + { + isWhiteListDueTimePassed = requestInfo.WhiteListDueTime > Context.CurrentBlockTime; + } + else + { + isWhiteListDueTimePassed = requestInfo.ListTime.AddHours(requestInfo.WhiteListHours) > + Context.CurrentBlockTime; + } + + if (isWhiteListDueTimePassed) + { + // White list hours not passed -> will refresh list time and white list price. + ListRequestedNFT(input, requestInfo, input.Whitelists); + duration.StartTime = Context.CurrentBlockTime; + } + else + { + MaybeReceiveRemainDeposit(requestInfo); + } + } + + private Hash ExistWhitelist(Hash projectId, WhitelistInfoList whitelistInfoList, ExtraInfoList extraInfoList) + { + var whitelistManager = GetWhitelistManager(); + var whitelistId = State.WhitelistIdMap[projectId]; + //Format data. + var extraInfoIdList = whitelistInfoList?.Whitelists.GroupBy(p => p.PriceTag) + .ToDictionary(e => e.Key, e => e.ToList()) + .Select(extra => + { + //Whether price tag already exists. + var ifExist = whitelistManager.GetTagInfoFromWhitelist( + new GetTagInfoFromWhitelistInput() + { + ProjectId = projectId, + WhitelistId = whitelistId, + TagInfo = new TagInfo + { + TagName = extra.Key.TagName, + Info = new PriceTag + { + Symbol = extra.Key.Price.Symbol, + Amount = extra.Key.Price.Amount + }.ToByteString() + } + }); + if (!ifExist) + { + //Doesn't exist,add tag info. + whitelistManager.AddExtraInfo(new AddExtraInfoInput() + { + ProjectId = projectId, + WhitelistId = whitelistId, + TagInfo = new TagInfo + { + TagName = extra.Key.TagName, + Info = new PriceTag + { + Symbol = extra.Key.Price.Symbol, + Amount = extra.Key.Price.Amount + }.ToByteString() + } + }); + } + var tagId = + HashHelper.ComputeFrom( + $"{whitelistId}{projectId}{extra.Key.TagName}"); + var toAddExtraInfoIdList = new ExtraInfoIdList(); + foreach (var whitelistInfo in extra.Value.Where(whitelistInfo => + whitelistInfo.AddressList.Value.Any())) + { + toAddExtraInfoIdList.Value.Add(new ExtraInfoId() + { + AddressList = new AElf.Contracts.Whitelist.AddressList + { + Value = {whitelistInfo.AddressList.Value} + }, + Id = tagId + }); + } + return toAddExtraInfoIdList; + }).ToList(); + if (extraInfoList == null || extraInfoIdList == null || extraInfoIdList.Count == 0) return whitelistId; + { + var toAdd = new ExtraInfoIdList(); + foreach (var extra in extraInfoIdList) + { + toAdd.Value.Add(extra.Value); + } + whitelistManager.AddAddressInfoListToWhitelist( + new AddAddressInfoListToWhitelistInput() + { + WhitelistId = whitelistId, + ExtraInfoIdList = toAdd + }); + } + return whitelistId; + } +} \ No newline at end of file diff --git a/contract/Forest/ForestContract_Sellers.cs b/contract/Forest/ForestContract_Sellers.cs new file mode 100644 index 00000000..246d1fa6 --- /dev/null +++ b/contract/Forest/ForestContract_Sellers.cs @@ -0,0 +1,635 @@ +using System.Linq; +using AElf; +using AElf.Contracts.MultiToken; +using AElf.Contracts.Whitelist; +using AElf.CSharp.Core; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; +using GetBalanceInput = AElf.Contracts.NFT.GetBalanceInput; + +namespace Forest; + +public partial class ForestContract +{ + public override Empty ListWithFixedPrice(ListWithFixedPriceInput input) + { + AssertContractInitialized(); + AssertWhitelistContractInitialized(); + Assert(input.Price.Amount > 0, "Incorrect listing price."); + Assert(input.Quantity > 0, "Incorrect quantity."); + var duration = AdjustListDuration(input.Duration); + var whitelists = input.Whitelists; + var requestInfo = State.RequestInfoMap[input.Symbol][input.TokenId]; + var projectId = CalculateProjectId(input.Symbol, input.TokenId,Context.Sender); + var whitelistId = new Hash(); + var whitelistManager = GetWhitelistManager(); + if (input.IsWhitelistAvailable) + { + if (requestInfo != null) + { + DealRequestInfoInWhitelist(input, duration, requestInfo); + } + else + { + var extraInfoList = ConvertToExtraInfo(whitelists); + //Listed for the first time, create whitelist. + if (State.WhitelistIdMap[projectId] == null) + { + whitelistManager.CreateWhitelist(new CreateWhitelistInput + { + ProjectId = projectId, + StrategyType = StrategyType.Price, + Creator = Context.Self, + ExtraInfoList = extraInfoList, + IsCloneable = true, + Remark = $"{input.Symbol}{input.TokenId}", + ManagerList = new AElf.Contracts.Whitelist.AddressList + { + Value = {Context.Sender} + } + }); + whitelistId = + Context.GenerateId(State.WhitelistContract.Value, + ByteArrayHelper.ConcatArrays(Context.Self.ToByteArray(), projectId.ToByteArray())); + State.WhitelistIdMap[projectId] = whitelistId; + } + else + { + //Add address list to the existing whitelist. + whitelistId = ExistWhitelist(projectId,whitelists,extraInfoList); + } + } + } + else + { + whitelistId = State.WhitelistIdMap[projectId]; + if (whitelistId != null && whitelistManager.IsWhitelistAvailable(whitelistId)) + { + State.WhitelistContract.DisableWhitelist.Send(whitelistId); + } + } + + Assert(GetTokenWhiteList(input.Symbol).Value.Contains(input.Price.Symbol), + $"{input.Price.Symbol} is not in token white list."); + + var listedNftInfoList = State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender] ?? + new ListedNFTInfoList(); + ListedNFTInfo listedNftInfo; + if (input.IsMergeToPreviousListedInfo) + { + listedNftInfo = listedNftInfoList.Value.FirstOrDefault(i => + i.Price.Symbol == input.Price.Symbol && i.Price.Amount == input.Price.Amount && + i.Owner == Context.Sender); + } + else + { + listedNftInfo = listedNftInfoList.Value.FirstOrDefault(i => + i.Price.Symbol == input.Price.Symbol && i.Price.Amount == input.Price.Amount && + i.Owner == Context.Sender && i.Duration.StartTime == input.Duration.StartTime && + i.Duration.PublicTime == input.Duration.PublicTime && + i.Duration.DurationHours == input.Duration.DurationHours); + } + + bool isMergedToPreviousListedInfo; + if (listedNftInfo == null) + { + listedNftInfo = new ListedNFTInfo + { + ListType = ListType.FixedPrice, + Owner = Context.Sender, + Price = input.Price, + Quantity = input.Quantity, + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = duration, + }; + listedNftInfoList.Value.Add(listedNftInfo); + isMergedToPreviousListedInfo = false; + Context.Fire(new ListedNFTAdded + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = duration, + Owner = Context.Sender, + Price = input.Price, + Quantity = input.Quantity, + WhitelistId = whitelistId + }); + } + else + { + listedNftInfo.Quantity = listedNftInfo.Quantity.Add(input.Quantity); + var previousDuration = listedNftInfo.Duration.Clone(); + listedNftInfo.Duration = duration; + isMergedToPreviousListedInfo = true; + Context.Fire(new ListedNFTChanged + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = duration, + Owner = Context.Sender, + Price = input.Price, + Quantity = listedNftInfo.Quantity, + PreviousDuration = previousDuration, + WhitelistId = whitelistId + }); + } + + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender] = listedNftInfoList; + + var totalQuantity = listedNftInfoList.Value.Where(i => i.Owner == Context.Sender).Sum(i => i.Quantity); + CheckSenderNFTBalanceAndAllowance(input.Symbol, input.TokenId, totalQuantity); + + ClearBids(input.Symbol, input.TokenId); + State.EnglishAuctionInfoMap[input.Symbol].Remove(input.TokenId); + State.DutchAuctionInfoMap[input.Symbol].Remove(input.TokenId); + + Context.Fire(new FixedPriceNFTListed + { + Owner = listedNftInfo.Owner, + Price = listedNftInfo.Price, + Quantity = input.Quantity, + Symbol = listedNftInfo.Symbol, + TokenId = listedNftInfo.TokenId, + Duration = listedNftInfo.Duration, + IsMergedToPreviousListedInfo = isMergedToPreviousListedInfo, + WhitelistId = whitelistId + }); + + return new Empty(); + } + + // public override Empty ListForFree(ListForFreeInput input) + // { + // //TODO:List price is 0. + // return base.ListForFree(input); + // } + + public override Empty ListWithEnglishAuction(ListWithEnglishAuctionInput input) + { + AssertContractInitialized(); + Assert(input.StartingPrice > 0, "Incorrect listing price."); + Assert(input.EarnestMoney <= input.StartingPrice, "Earnest money too high."); + if (CanBeListedWithAuction(input.Symbol, input.TokenId, out var requestInfo)) + { + MaybeReceiveRemainDeposit(requestInfo); + } + else + { + throw new AssertionException("This NFT cannot be listed with auction for now."); + } + + CheckSenderNFTBalanceAndAllowance(input.Symbol, input.TokenId, 1); + + Assert(GetTokenWhiteList(input.Symbol).Value.Contains(input.PurchaseSymbol), + $"{input.PurchaseSymbol} is not in token white list."); + Assert( + string.IsNullOrEmpty(State.NFTContract.GetNFTProtocolInfo + .Call(new StringValue {Value = input.PurchaseSymbol}).Symbol), + $"Token {input.PurchaseSymbol} not support purchase for auction."); + + ClearBids(input.Symbol, input.TokenId); + + var duration = AdjustListDuration(input.Duration); + + var englishAuctionInfo = new EnglishAuctionInfo + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = duration, + PurchaseSymbol = input.PurchaseSymbol, + StartingPrice = input.StartingPrice, + Owner = Context.Sender, + EarnestMoney = input.EarnestMoney + }; + State.EnglishAuctionInfoMap[input.Symbol][input.TokenId] = englishAuctionInfo; + + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender] = new ListedNFTInfoList + { + Value = + { + new ListedNFTInfo + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = duration, + ListType = ListType.EnglishAuction, + Owner = Context.Sender, + Price = new Price + { + Symbol = input.PurchaseSymbol, + Amount = input.StartingPrice + }, + Quantity = 1 + } + } + }; + + State.DutchAuctionInfoMap[input.Symbol].Remove(input.TokenId); + + Context.Fire(new EnglishAuctionNFTListed + { + Owner = englishAuctionInfo.Owner, + Symbol = englishAuctionInfo.Symbol, + PurchaseSymbol = englishAuctionInfo.PurchaseSymbol, + StartingPrice = englishAuctionInfo.StartingPrice, + TokenId = englishAuctionInfo.TokenId, + Duration = englishAuctionInfo.Duration, + EarnestMoney = englishAuctionInfo.EarnestMoney + }); + + Context.Fire(new ListedNFTAdded + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = englishAuctionInfo.Duration, + Owner = englishAuctionInfo.Owner, + Price = new Price + { + Symbol = englishAuctionInfo.PurchaseSymbol, + Amount = englishAuctionInfo.StartingPrice + }, + Quantity = 1 + }); + + return new Empty(); + } + + public override Empty ListWithDutchAuction(ListWithDutchAuctionInput input) + { + AssertContractInitialized(); + Assert(input.StartingPrice > 0 && input.EndingPrice > 0 && input.StartingPrice > input.EndingPrice, + "Incorrect listing price."); + if (CanBeListedWithAuction(input.Symbol, input.TokenId, out var requestInfo)) + { + MaybeReceiveRemainDeposit(requestInfo); + } + else + { + throw new AssertionException("This NFT cannot be listed with auction for now."); + } + + CheckSenderNFTBalanceAndAllowance(input.Symbol, input.TokenId, 1); + + Assert(GetTokenWhiteList(input.Symbol).Value.Contains(input.PurchaseSymbol), + $"{input.PurchaseSymbol} is not in token white list."); + Assert( + string.IsNullOrEmpty(State.NFTContract.GetNFTProtocolInfo + .Call(new StringValue {Value = input.PurchaseSymbol}).Symbol), + $"Token {input.PurchaseSymbol} not support purchase for auction."); + + var duration = AdjustListDuration(input.Duration); + + var dutchAuctionInfo = new DutchAuctionInfo + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = duration, + PurchaseSymbol = input.PurchaseSymbol, + StartingPrice = input.StartingPrice, + EndingPrice = input.EndingPrice, + Owner = Context.Sender + }; + State.DutchAuctionInfoMap[input.Symbol][input.TokenId] = dutchAuctionInfo; + + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender] = new ListedNFTInfoList + { + Value = + { + new ListedNFTInfo + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = duration, + ListType = ListType.DutchAuction, + Owner = Context.Sender, + Price = new Price + { + Symbol = input.PurchaseSymbol, + Amount = input.StartingPrice + }, + Quantity = 1 + } + } + }; + + ClearBids(input.Symbol, input.TokenId); + State.EnglishAuctionInfoMap[input.Symbol].Remove(input.TokenId); + + Context.Fire(new DutchAuctionNFTListed + { + Owner = dutchAuctionInfo.Owner, + PurchaseSymbol = dutchAuctionInfo.PurchaseSymbol, + StartingPrice = dutchAuctionInfo.StartingPrice, + EndingPrice = dutchAuctionInfo.EndingPrice, + Symbol = dutchAuctionInfo.Symbol, + TokenId = dutchAuctionInfo.TokenId, + Duration = dutchAuctionInfo.Duration + }); + + Context.Fire(new ListedNFTAdded + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Duration = dutchAuctionInfo.Duration, + Owner = dutchAuctionInfo.Owner, + Price = new Price + { + Symbol = dutchAuctionInfo.PurchaseSymbol, + Amount = dutchAuctionInfo.StartingPrice + }, + Quantity = 1 + }); + + return new Empty(); + } + + public override Empty Delist(DelistInput input) + { + CheckSenderNFTBalanceAndAllowance(input.Symbol, input.TokenId, input.Quantity); + var listedNftInfoList = State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender]; + if (listedNftInfoList == null || listedNftInfoList.Value.All(i => i.ListType == ListType.NotListed)) + { + throw new AssertionException("Listed NFT Info not exists. (Or already delisted.)"); + } + + Assert(input.Price != null, "Need to specific list record."); + var listedNftInfo = listedNftInfoList.Value.FirstOrDefault(i => + i.Price.Amount == input.Price.Amount && i.Price.Symbol == input.Price.Symbol && + i.Owner == Context.Sender); + if (listedNftInfo == null) + { + throw new AssertionException("Listed NFT Info not exists. (Or already delisted.)"); + } + + var requestInfo = State.RequestInfoMap[input.Symbol][input.TokenId]; + if (requestInfo != null) + { + requestInfo.ListTime = null; + State.RequestInfoMap[input.Symbol][input.TokenId] = requestInfo; + } + + var projectId = CalculateProjectId(input.Symbol, input.TokenId, Context.Sender); + var whitelistId = State.WhitelistIdMap[projectId]; + + switch (listedNftInfo.ListType) + { + case ListType.FixedPrice when input.Quantity >= listedNftInfo.Quantity: + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender].Value.Remove(listedNftInfo); + // if (State.WhitelistIdMap[input.Symbol][input.TokenId][Context.Sender] != null) + // { + // var whitelistId = State.WhitelistIdMap[projectId]; + // State.WhitelistContract.DisableWhitelist.Send(whitelistId); + // } + Context.Fire(new ListedNFTRemoved + { + Symbol = listedNftInfo.Symbol, + TokenId = listedNftInfo.TokenId, + Duration = listedNftInfo.Duration, + Owner = listedNftInfo.Owner + }); + break; + case ListType.FixedPrice: + listedNftInfo.Quantity = listedNftInfo.Quantity.Sub(input.Quantity); + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender] = listedNftInfoList; + Context.Fire(new ListedNFTChanged + { + Symbol = listedNftInfo.Symbol, + TokenId = listedNftInfo.TokenId, + Duration = listedNftInfo.Duration, + Owner = listedNftInfo.Owner, + Price = listedNftInfo.Price, + Quantity = listedNftInfo.Quantity, + WhitelistId = whitelistId + }); + break; + case ListType.EnglishAuction: + var englishAuctionInfo = State.EnglishAuctionInfoMap[input.Symbol][input.TokenId]; + var bidAddressList = State.BidAddressListMap[input.Symbol][input.TokenId]; + if (bidAddressList != null && bidAddressList.Value.Any()) + { + // Charge service fee if anyone placed a bid. + ChargeSenderServiceFee(englishAuctionInfo.PurchaseSymbol, englishAuctionInfo.StartingPrice); + } + ClearBids(englishAuctionInfo.Symbol, englishAuctionInfo.TokenId); + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender].Value.Remove(listedNftInfo); + State.EnglishAuctionInfoMap[input.Symbol].Remove(input.TokenId); + Context.Fire(new ListedNFTRemoved + { + Symbol = listedNftInfo.Symbol, + TokenId = listedNftInfo.TokenId, + Duration = listedNftInfo.Duration, + Owner = listedNftInfo.Owner + }); + break; + case ListType.DutchAuction: + var dutchAuctionInfo = State.DutchAuctionInfoMap[input.Symbol][input.TokenId]; + State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender].Value.Remove(listedNftInfo); + State.DutchAuctionInfoMap[input.Symbol].Remove(input.TokenId); + ChargeSenderServiceFee(dutchAuctionInfo.PurchaseSymbol, dutchAuctionInfo.StartingPrice); + Context.Fire(new ListedNFTRemoved + { + Symbol = listedNftInfo.Symbol, + TokenId = listedNftInfo.TokenId, + Duration = listedNftInfo.Duration, + Owner = listedNftInfo.Owner + }); + break; + } + + Context.Fire(new NFTDelisted + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Owner = Context.Sender, + Quantity = input.Quantity + }); + + return new Empty(); + } + + private void ChargeSenderServiceFee(string symbol, long baseAmount) + { + var amount = baseAmount.Mul(State.ServiceFeeRate.Value).Div(FeeDenominator); + if (amount > 0) + { + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + Symbol = symbol, + Amount = amount, + From = Context.Sender, + To = State.ServiceFeeReceiver.Value ?? State.Admin.Value + }); + } + } + + /// + /// Sender is the seller. + /// + /// + /// + /// + public override Empty Deal(DealInput input) + { + AssertContractInitialized(); + + Assert(input.Symbol != null, "Incorrect symbol."); + Assert(input.TokenId != 0, "Incorrect token id."); + Assert(input.OfferFrom != null, "Incorrect offer maker."); + if (input.Price?.Symbol == null) + { + throw new AssertionException("Incorrect price."); + } + + CheckSenderNFTBalanceAndAllowance(input.Symbol, input.TokenId, input.Quantity); + + var balance = State.NFTContract.GetBalance.Call(new GetBalanceInput + { + Symbol = input.Symbol, + TokenId = input.TokenId, + Owner = Context.Sender + }); + Assert(balance.Balance >= input.Quantity, "Insufficient NFT balance."); + + var requestInfo = State.RequestInfoMap[input.Symbol][input.TokenId]; + if (requestInfo != null) + { + Assert(Context.CurrentBlockTime > requestInfo.WhiteListDueTime, "Due time not passed."); + } + + var offer = State.OfferListMap[input.Symbol][input.TokenId][input.OfferFrom]?.Value + .FirstOrDefault(o => + o.From == input.OfferFrom && o.Price.Symbol == input.Price.Symbol && + o.Price.Amount == input.Price.Amount && o.ExpireTime >= Context.CurrentBlockTime); + var bid = State.BidMap[input.Symbol][input.TokenId][input.OfferFrom]; + Price price; + long totalAmount; + if (offer == null) + { + // Check bid. + + if (bid == null || bid.From != input.OfferFrom || + bid.Price.Amount != input.Price.Amount || bid.Price.Symbol != input.Price.Symbol || + bid.ExpireTime < Context.CurrentBlockTime) + { + throw new AssertionException("Neither related offer nor bid are found."); + } + + price = bid.Price; + + Assert(price.TokenId == 0, "Do not support use NFT to purchase auction."); + + var auctionInfo = State.EnglishAuctionInfoMap[input.Symbol][input.TokenId]; + + totalAmount = price.Amount; + + // Transfer earnest money back to the bidder at first. + if (auctionInfo.EarnestMoney > 0) + { + State.TokenContract.Transfer.VirtualSend(CalculateTokenHash(input.Symbol, input.TokenId), + new TransferInput + { + To = bid.From, + Symbol = price.Symbol, + Amount = auctionInfo.EarnestMoney + }); + } + + if (!CheckAllowanceAndBalanceIsEnough(bid.From, price.Symbol, totalAmount.Sub(auctionInfo.EarnestMoney))) + { + State.BidMap[input.Symbol][input.TokenId].Remove(bid.From); + Context.Fire(new BidCanceled + { + Symbol = input.Symbol, + TokenId = input.TokenId, + BidFrom = input.OfferFrom, + BidTo = Context.Sender + }); + return new Empty(); + } + + auctionInfo.DealPrice = input.Price.Amount; + auctionInfo.DealTo = input.OfferFrom; + State.EnglishAuctionInfoMap[input.Symbol][input.TokenId] = auctionInfo; + + ClearBids(input.Symbol, input.TokenId, input.OfferFrom); + } + else + { + Assert(offer.Quantity >= input.Quantity, "Deal quantity exceeded."); + offer.Quantity = offer.Quantity.Sub(input.Quantity); + if (offer.Quantity == 0) + { + State.OfferListMap[input.Symbol][input.TokenId][input.OfferFrom].Value.Remove(offer); + Context.Fire(new OfferRemoved + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = input.OfferFrom, + OfferTo = offer.To, + ExpireTime = offer.ExpireTime + }); + } + else + { + Context.Fire(new OfferChanged + { + Symbol = input.Symbol, + TokenId = input.TokenId, + OfferFrom = input.OfferFrom, + OfferTo = offer.To, + Quantity = offer.Quantity, + Price = offer.Price, + ExpireTime = offer.ExpireTime + }); + } + price = offer.Price; + totalAmount = price.Amount.Mul(input.Quantity); + } + + var listedNftInfoList = State.ListedNFTInfoListMap[input.Symbol][input.TokenId][Context.Sender]; + if (listedNftInfoList != null && listedNftInfoList.Value.Any()) + { + var firstListedNftInfo = listedNftInfoList.Value.First(); + if (firstListedNftInfo.ListType != ListType.EnglishAuction && firstListedNftInfo.ListType != ListType.DutchAuction) + { + // Listed with fixed price. + + var nftBalance = State.NFTContract.GetBalance.Call(new GetBalanceInput + { + Symbol = input.Symbol, + Owner = Context.Sender, + TokenId = input.TokenId + }).Balance; + var listedQuantity = listedNftInfoList.Value.Where(i => i.Owner == Context.Sender).Sum(i => i.Quantity); + Assert(nftBalance >= listedQuantity.Add(input.Quantity), + $"Need to delist at least {listedQuantity.Add(input.Quantity).Sub(nftBalance)} NFT(s) before deal."); + } + else + { + State.ListedNFTInfoListMap[input.Symbol][input.TokenId].Remove(Context.Sender); + Context.Fire(new ListedNFTRemoved + { + Symbol = firstListedNftInfo.Symbol, + TokenId = firstListedNftInfo.TokenId, + Duration = firstListedNftInfo.Duration, + Owner = firstListedNftInfo.Owner + }); + } + } + + PerformDeal(new PerformDealInput + { + NFTFrom = Context.Sender, + NFTTo = offer?.From ?? bid.From, + NFTSymbol = input.Symbol, + NFTTokenId = input.TokenId, + NFTQuantity = input.Quantity, + PurchaseSymbol = price.Symbol, + PurchaseAmount = totalAmount, + PurchaseTokenId = price.TokenId + }); + return new Empty(); + } +} \ No newline at end of file diff --git a/contract/Forest/ForestContract_Views.cs b/contract/Forest/ForestContract_Views.cs new file mode 100644 index 00000000..e9ccd699 --- /dev/null +++ b/contract/Forest/ForestContract_Views.cs @@ -0,0 +1,162 @@ +using Google.Protobuf.WellKnownTypes; + +namespace Forest; + +public partial class ForestContract +{ + public override ListedNFTInfoList GetListedNFTInfoList(GetListedNFTInfoListInput input) + { + return State.ListedNFTInfoListMap[input.Symbol][input.TokenId][input.Owner]; + } + + public override GetWhitelistIdOutput GetWhitelistId(GetWhitelistIdInput input) + { + var projectId = CalculateProjectId(input.Symbol, input.TokenId,input.Owner); + Assert(State.WhitelistIdMap[projectId] != null, $"Whitelist id not found.Project id:{projectId}"); + var whitelistId = State.WhitelistIdMap[projectId]; + return new GetWhitelistIdOutput + { + WhitelistId = whitelistId, + ProjectId = projectId + }; + } + + public override AddressList GetOfferAddressList(GetAddressListInput input) + { + return State.OfferAddressListMap[input.Symbol][input.TokenId]; + } + + public override OfferList GetOfferList(GetOfferListInput input) + { + if (input.Address != null) + { + return State.OfferListMap[input.Symbol][input.TokenId][input.Address]; + } + + var addressList = GetOfferAddressList(new GetAddressListInput + { + Symbol = input.Symbol, + TokenId = input.TokenId + }) ?? new AddressList(); + var allOfferList = new OfferList(); + foreach (var address in addressList.Value) + { + var offerList = State.OfferListMap[input.Symbol][input.TokenId][address]; + if (offerList != null) + { + allOfferList.Value.Add(offerList.Value); + } + } + + return allOfferList; + } + + public override AddressList GetBidAddressList(GetAddressListInput input) + { + return State.BidAddressListMap[input.Symbol][input.TokenId]; + } + + public override Bid GetBid(GetBidInput input) + { + return State.BidMap[input.Symbol][input.TokenId][input.Address]; + } + + public override BidList GetBidList(GetBidListInput input) + { + var addressList = GetBidAddressList(new GetAddressListInput + { + Symbol = input.Symbol, + TokenId = input.TokenId + }) ?? new AddressList(); + var allBidList = new BidList(); + foreach (var address in addressList.Value) + { + var bid = State.BidMap[input.Symbol][input.TokenId][address]; + if (bid != null) + { + allBidList.Value.Add(bid); + } + } + + return allBidList; + } + + public override CustomizeInfo GetCustomizeInfo(StringValue input) + { + return State.CustomizeInfoMap[input.Value]; + } + + public override RequestInfo GetRequestInfo(GetRequestInfoInput input) + { + return State.RequestInfoMap[input.Symbol][input.TokenId]; + } + + public override EnglishAuctionInfo GetEnglishAuctionInfo(GetEnglishAuctionInfoInput input) + { + return State.EnglishAuctionInfoMap[input.Symbol][input.TokenId]; + } + + public override DutchAuctionInfo GetDutchAuctionInfo(GetDutchAuctionInfoInput input) + { + return State.DutchAuctionInfoMap[input.Symbol][input.TokenId]; + } + + public override StringList GetTokenWhiteList(StringValue input) + { + return GetTokenWhiteList(input.Value); + } + + public override StringList GetGlobalTokenWhiteList(Empty input) + { + return State.GlobalTokenWhiteList.Value; + } + + public override Price GetStakingTokens(StringValue input) + { + var customizeInfo = State.CustomizeInfoMap[input.Value]; + if (customizeInfo == null) + { + return new Price(); + } + + return new Price + { + Symbol = customizeInfo.Price.Symbol, + Amount = customizeInfo.StakingAmount + }; + } + + public override RoyaltyInfo GetRoyalty(GetRoyaltyInput input) + { + var royaltyInfo = new RoyaltyInfo + { + Royalty = State.RoyaltyMap[input.Symbol] + }; + + if (input.TokenId != 0) + { + var certainNftRoyalty = State.CertainNFTRoyaltyMap[input.Symbol][input.TokenId] ?? + new CertainNFTRoyaltyInfo(); + if (certainNftRoyalty.IsManuallySet) + { + royaltyInfo.Royalty = certainNftRoyalty.Royalty; + } + } + + if (State.RoyaltyFeeReceiverMap[input.Symbol] != null) + { + royaltyInfo.RoyaltyFeeReceiver = State.RoyaltyFeeReceiverMap[input.Symbol]; + } + + return royaltyInfo; + } + + public override ServiceFeeInfo GetServiceFeeInfo(Empty input) + { + return new ServiceFeeInfo + { + ServiceFeeRate = State.ServiceFeeRate.Value, + ServiceFeeReceiver = State.ServiceFeeReceiver.Value + }; + } +} \ No newline at end of file diff --git a/contract/Forest/Helpers/WhitelistHelper.cs b/contract/Forest/Helpers/WhitelistHelper.cs new file mode 100644 index 00000000..9cf2a0e7 --- /dev/null +++ b/contract/Forest/Helpers/WhitelistHelper.cs @@ -0,0 +1,25 @@ +using AElf; +using AElf.Contracts.Whitelist; +using AElf.Types; +using Google.Protobuf; + +namespace Forest.Helpers; + +public static class WhitelistHelper +{ + internal static Hash CalculateProjectId(string symbol, long tokenId, Address sender) + { + return HashHelper.ComputeFrom($"{symbol}{tokenId}{sender}"); + } + + internal static Price DeserializedInfo(TagInfo tagInfo) + { + var deserializedInfo = new PriceTag(); + deserializedInfo.MergeFrom(tagInfo.Info); + return new Price + { + Symbol = deserializedInfo.Symbol, + Amount = deserializedInfo.Amount + }; + } +} \ No newline at end of file diff --git a/contract/Forest/Managers/WhitelistManager.cs b/contract/Forest/Managers/WhitelistManager.cs new file mode 100644 index 00000000..2c49930c --- /dev/null +++ b/contract/Forest/Managers/WhitelistManager.cs @@ -0,0 +1,77 @@ +using AElf.Contracts.Whitelist; +using AElf.Sdk.CSharp; +using AElf.Sdk.CSharp.State; +using AElf.Types; +using Forest.Helpers; + +namespace Forest.Managers; + +internal class WhitelistManager +{ + private readonly CSharpSmartContractContext _context; + private readonly MappedState _whitelistIdMap; + private readonly WhitelistContractContainer.WhitelistContractReferenceState _whitelistContract; + + public WhitelistManager(CSharpSmartContractContext context, + MappedState whitelistIdMap, + WhitelistContractContainer.WhitelistContractReferenceState whitelistContract) + { + _context = context; + _whitelistIdMap = whitelistIdMap; + _whitelistContract = whitelistContract; + } + + public void CreateWhitelist(CreateWhitelistInput input) + { + _whitelistContract.CreateWhitelist.Send(input); + } + + public void AddExtraInfo(AddExtraInfoInput input) + { + _whitelistContract.AddExtraInfo.Send(input); + } + + public void AddAddressInfoListToWhitelist(AddAddressInfoListToWhitelistInput input) + { + _whitelistContract.AddAddressInfoListToWhitelist.Send(input); + } + + public void RemoveAddressInfoListFromWhitelist(RemoveAddressInfoListFromWhitelistInput input) + { + _whitelistContract.RemoveAddressInfoListFromWhitelist.Send(input); + } + + public bool IsAddressInWhitelist(Address address, Hash whitelistId) + { + if (whitelistId == null) + { + return false; + } + + return _whitelistContract.GetAddressFromWhitelist.Call(new GetAddressFromWhitelistInput + { + Address = address, + WhitelistId = whitelistId + }).Value; + } + + public bool IsWhitelistAvailable(Hash whitelistId) + { + return whitelistId != null && _whitelistContract.GetWhitelist.Call(whitelistId).IsAvailable; + } + + public Price GetExtraInfoByAddress(Hash whitelistId) + { + var tagInfo = _whitelistContract.GetExtraInfoByAddress.Call(new GetExtraInfoByAddressInput + { + WhitelistId = whitelistId, + Address = _context.Sender + }); + return WhitelistHelper.DeserializedInfo(tagInfo); + } + + public bool GetTagInfoFromWhitelist(GetTagInfoFromWhitelistInput input) + { + return _whitelistContract.GetTagInfoFromWhitelist.Call(input).Value; + } +} \ No newline at end of file diff --git a/contract/Forest/Services/DealService.cs b/contract/Forest/Services/DealService.cs new file mode 100644 index 00000000..61ed17d1 --- /dev/null +++ b/contract/Forest/Services/DealService.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Linq; +using AElf.CSharp.Core; +using AElf.Sdk.CSharp; + +namespace Forest.Services; + +public class DealService +{ + private readonly CSharpSmartContractContext _context; + + public DealService(CSharpSmartContractContext context) + { + _context = context; + } + + public IEnumerable GetDealResultList(GetDealResultListInput input) + { + var dealResultList = new List(); + var needToDealQuantity = input.MakeOfferInput.Quantity; + var currentIndex = 0; + foreach (var listedNftInfo in input.ListedNftInfoList.Value.Where(i => + i.Price.Symbol == input.MakeOfferInput.Price.Symbol && IsTimeOk(i)).OrderBy(i => i.Price.Amount)) + { + if (listedNftInfo.Quantity >= needToDealQuantity) + { + var dealResult = new DealResult + { + Symbol = input.MakeOfferInput.Symbol, + TokenId = input.MakeOfferInput.TokenId, + Quantity = needToDealQuantity, + PurchaseSymbol = input.MakeOfferInput.Price.Symbol, + PurchaseAmount = listedNftInfo.Price.Amount, + Duration = listedNftInfo.Duration, + Index = currentIndex + }; + // Fulfill demands. + dealResultList.Add(dealResult); + needToDealQuantity = 0; + } + else + { + var dealResult = new DealResult + { + Symbol = input.MakeOfferInput.Symbol, + TokenId = input.MakeOfferInput.TokenId, + Quantity = needToDealQuantity, + PurchaseSymbol = input.MakeOfferInput.Price.Symbol, + PurchaseAmount = listedNftInfo.Price.Amount, + Duration = listedNftInfo.Duration, + Index = currentIndex + }; + dealResultList.Add(dealResult); + needToDealQuantity = needToDealQuantity.Sub(listedNftInfo.Quantity); + } + + if (needToDealQuantity == 0) + { + break; + } + + currentIndex = currentIndex.Add(1); + } + + return dealResultList; + } + + private bool IsTimeOk(ListedNFTInfo listedNftInfo) + { + return _context.CurrentBlockTime >= listedNftInfo.Duration.StartTime && _context.CurrentBlockTime >= listedNftInfo.Duration.PublicTime; + } +} + +public class GetDealResultListInput +{ + internal MakeOfferInput MakeOfferInput { get; set; } + internal ListedNFTInfoList ListedNftInfoList{ get; set; } +} + + +public class DealResult +{ + internal string Symbol { get; set; } + internal long TokenId{ get; set; } + internal long Quantity{ get; set; } + internal string PurchaseSymbol{ get; set; } + internal long PurchaseTokenId{ get; set; } + internal long PurchaseAmount{ get; set; } + internal ListDuration Duration { get; set; } + internal int Index { get; set; } +} \ No newline at end of file diff --git a/contract/Forest/Services/MakeOfferService.cs b/contract/Forest/Services/MakeOfferService.cs new file mode 100644 index 00000000..9e7221c6 --- /dev/null +++ b/contract/Forest/Services/MakeOfferService.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using System.Linq; +using AElf.Contracts.NFT; +using AElf.Contracts.Whitelist; +using AElf.CSharp.Core.Extension; +using AElf.Sdk.CSharp; +using AElf.Sdk.CSharp.State; +using AElf.Types; +using Forest.Helpers; +using Forest.Managers; +using Google.Protobuf.WellKnownTypes; + +namespace Forest.Services; + +internal class MakeOfferService +{ + private readonly NFTContractContainer.NFTContractReferenceState _nftContract; + private readonly WhitelistContractContainer.WhitelistContractReferenceState _whitelistContract; + private readonly MappedState _whitelistIdMap; + private readonly MappedState _listedNFTInfoListMap; + private readonly WhitelistManager _whitelistManager; + private readonly CSharpSmartContractContext _context; + + public MakeOfferService(NFTContractContainer.NFTContractReferenceState nftContract, + MappedState whitelistIdMap, + MappedState listedNFTInfoListMap, + WhitelistManager whitelistManager, + CSharpSmartContractContext context) + { + _nftContract = nftContract; + _whitelistIdMap = whitelistIdMap; + _listedNFTInfoListMap = listedNFTInfoListMap; + _whitelistManager = whitelistManager; + _context = context; + } + + public void ValidateOffer(MakeOfferInput makeOfferInput) + { + if (_context.Sender == makeOfferInput.OfferTo) + { + throw new AssertionException("Origin owner cannot be sender himself."); + } + } + + public bool IsSenderInWhitelist(MakeOfferInput makeOfferInput,out Hash whitelistId) + { + var projectId = + WhitelistHelper.CalculateProjectId(makeOfferInput.Symbol, makeOfferInput.TokenId, makeOfferInput.OfferTo); + whitelistId = _whitelistIdMap[projectId]; + return whitelistId != null && _whitelistManager.IsAddressInWhitelist(_context.Sender, whitelistId); + } + + public DealStatus GetDealStatus(MakeOfferInput makeOfferInput, out List affordableNftInfoList) + { + affordableNftInfoList = new List(); + var nftInfo = _nftContract.GetNFTInfo.Call(new GetNFTInfoInput + { + Symbol = makeOfferInput.Symbol, + TokenId = makeOfferInput.TokenId + }); + var protocolInfo = _nftContract.GetNFTProtocolInfo.Call(new StringValue { Value = makeOfferInput.Symbol }); + if (nftInfo.Quantity == 0 && !protocolInfo.IsTokenIdReuse && makeOfferInput.Quantity == 1) + { + // NFT not minted. + return DealStatus.NFTNotMined; + } + + if (nftInfo.Quantity <= 0) + { + throw new AssertionException("NFT does not exist."); + } + + var listedNftInfoList = + _listedNFTInfoListMap[makeOfferInput.Symbol][makeOfferInput.TokenId][ + makeOfferInput.OfferTo ?? nftInfo.Creator]; + + if (listedNftInfoList == null || listedNftInfoList.Value.All(i => i.ListType == ListType.NotListed)) + { + // NFT not listed. + return DealStatus.NotDeal; + } + + affordableNftInfoList = GetAffordableNftInfoList(makeOfferInput, listedNftInfoList); + if (!affordableNftInfoList.Any()) + { + return DealStatus.NotDeal; + } + + if (affordableNftInfoList.Count == 1 || affordableNftInfoList.First().Quantity >= makeOfferInput.Quantity) + { + return DealStatus.DealWithOnePrice; + } + + return DealStatus.DealWithMultiPrice; + } + + private List GetAffordableNftInfoList(MakeOfferInput makeOfferInput, + ListedNFTInfoList listedNftInfoList) + { + return listedNftInfoList.Value.Where(i => + (i.Price.Symbol == makeOfferInput.Price.Symbol && i.Price.Amount <= makeOfferInput.Price.Amount || + i.ListType != ListType.FixedPrice) && + !IsListedNftTimedOut(i)).OrderBy(i => i.Price.Amount).ToList(); + } + + + private bool IsListedNftTimedOut(ListedNFTInfo listedNftInfo) + { + var expireTime = listedNftInfo.Duration.StartTime.AddHours(listedNftInfo.Duration.DurationHours); + return _context.CurrentBlockTime > expireTime; + } +} + +public enum DealStatus +{ + NFTNotMined, + NotDeal, + DealWithOnePrice, + DealWithMultiPrice, +} \ No newline at end of file diff --git a/protobuf/acs0.proto b/protobuf/acs0.proto new file mode 100755 index 00000000..dcaa3ad7 --- /dev/null +++ b/protobuf/acs0.proto @@ -0,0 +1,153 @@ +syntax = "proto3"; + +package acs0; +option csharp_namespace = "AElf.Standards.ACS0"; + +import public "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service ACS0 { + // Actions + rpc DeploySystemSmartContract (SystemContractDeploymentInput) returns (aelf.Address) { + } + rpc DeploySmartContract (ContractDeploymentInput) returns (aelf.Address) { + } + rpc UpdateSmartContract (ContractUpdateInput) returns (aelf.Address) { + } + rpc ProposeNewContract (ContractDeploymentInput) returns (aelf.Hash) { + } + rpc ProposeContractCodeCheck (ContractCodeCheckInput) returns (aelf.Hash) { + } + rpc ProposeUpdateContract (ContractUpdateInput) returns (aelf.Hash) { + } + rpc ReleaseApprovedContract (ReleaseContractInput) returns (google.protobuf.Empty) { + } + rpc ReleaseCodeCheckedContract (ReleaseContractInput) returns (google.protobuf.Empty) { + } + rpc ValidateSystemContractAddress(ValidateSystemContractAddressInput) returns (google.protobuf.Empty){ + } + rpc SetContractProposerRequiredState (google.protobuf.BoolValue) returns (google.protobuf.Empty) { + } + // Views + rpc CurrentContractSerialNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetContractInfo (aelf.Address) returns (ContractInfo) { + option (aelf.is_view) = true; + } + rpc GetContractAuthor (aelf.Address) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetContractHash (aelf.Address) returns (aelf.Hash) { + option (aelf.is_view) = true; + } + rpc GetContractAddressByName (aelf.Hash) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetSmartContractRegistrationByAddress (aelf.Address) returns (aelf.SmartContractRegistration) { + option (aelf.is_view) = true; + } + rpc GetSmartContractRegistrationByCodeHash (aelf.Hash) returns (aelf.SmartContractRegistration) { + option (aelf.is_view) = true; + } +} + +// Messages + +message ContractInfo +{ + int64 serial_number = 1; + aelf.Address author = 2; + sint32 category = 3; + aelf.Hash code_hash = 4; + bool is_system_contract = 5; + int32 version = 6; +} + +message ContractDeploymentInput { + sint32 category = 1; + bytes code = 2; +} + +message SystemContractDeploymentInput { + message SystemTransactionMethodCall { + string method_name = 1; + bytes params = 2; + } + message SystemTransactionMethodCallList { + repeated SystemTransactionMethodCall value = 1; + } + sint32 category = 1; + bytes code = 2; + aelf.Hash name = 3; + SystemTransactionMethodCallList transaction_method_call_list = 4; +} + +message ContractUpdateInput { + aelf.Address address = 1; + bytes code = 2; +} + +message ContractCodeCheckInput{ + bytes contract_input = 1; + bool is_contract_deployment = 2; + string code_check_release_method = 3; + aelf.Hash proposed_contract_input_hash = 4; + sint32 category = 5; + bool is_system_contract = 6; +} + +// Events +message ContractProposed +{ + option (aelf.is_event) = true; + aelf.Hash proposed_contract_input_hash = 1; +} + +message ContractDeployed +{ + option (aelf.is_event) = true; + aelf.Address author = 1 [(aelf.is_indexed) = true]; + aelf.Hash code_hash = 2 [(aelf.is_indexed) = true]; + aelf.Address address = 3; + int32 version = 4; + aelf.Hash Name = 5; +} + +message CodeCheckRequired +{ + option (aelf.is_event) = true; + bytes code = 1; + aelf.Hash proposed_contract_input_hash = 2; + sint32 category = 3; + bool is_system_contract = 4; +} + +message CodeUpdated +{ + option (aelf.is_event) = true; + aelf.Address address = 1 [(aelf.is_indexed) = true]; + aelf.Hash old_code_hash = 2; + aelf.Hash new_code_hash = 3; + int32 version = 4; +} + +message AuthorChanged +{ + option (aelf.is_event) = true; + aelf.Address address = 1 [(aelf.is_indexed) = true]; + aelf.Address old_author = 2; + aelf.Address new_author = 3; +} + +message ValidateSystemContractAddressInput { + aelf.Hash system_contract_hash_name = 1; + aelf.Address address = 2; +} + +message ReleaseContractInput { + aelf.Hash proposal_id = 1; + aelf.Hash proposed_contract_input_hash = 2; +} \ No newline at end of file diff --git a/protobuf/acs1.proto b/protobuf/acs1.proto new file mode 100755 index 00000000..7410de7b --- /dev/null +++ b/protobuf/acs1.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +package acs1; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; +import "authority_info.proto"; + +option (aelf.identity) = "acs1"; +option csharp_namespace = "AElf.Standards.ACS1"; + +service MethodFeeProviderContract { + + // Actions + rpc SetMethodFee (MethodFees) returns (google.protobuf.Empty) { + } + + rpc ChangeMethodFeeController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Views + rpc GetMethodFee (google.protobuf.StringValue) returns (MethodFees) { + option (aelf.is_view) = true; + } + + rpc GetMethodFeeController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } +} + +message MethodFees { + string method_name = 1; + repeated MethodFee fees = 2; + bool is_size_fee_free = 3;// Optional based on the implementation of SetMethodFee method. +} + +message MethodFee { + string symbol = 1; + int64 basic_fee = 2; +} + +// Events +message MethodFeeSet { + option (aelf.is_event) = true; + string method = 1; + string symbol = 2; + int64 old_fee = 3; + int64 new_fee = 4; +} \ No newline at end of file diff --git a/protobuf/acs10.proto b/protobuf/acs10.proto new file mode 100755 index 00000000..f53936ad --- /dev/null +++ b/protobuf/acs10.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package acs10; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs10"; +option csharp_namespace = "AElf.Standards.ACS10"; + +service DividendPoolContract { + // Actions + rpc Donate (DonateInput) returns (google.protobuf.Empty) { + } + rpc Release (ReleaseInput) returns (google.protobuf.Empty) { + } + rpc SetSymbolList (SymbolList) returns (google.protobuf.Empty) { + } + + // Views + rpc GetSymbolList (google.protobuf.Empty) returns (SymbolList) { + option (aelf.is_view) = true; + } + rpc GetUndistributedDividends (google.protobuf.Empty) returns (Dividends) { + option (aelf.is_view) = true; + } + rpc GetDividends (google.protobuf.Int64Value) returns (Dividends) { + option (aelf.is_view) = true; + } +} + +message DonateInput { + string symbol = 1; + int64 amount = 2; +} + +message ReleaseInput { + int64 period_number = 1; +} + +message SymbolList { + repeated string value = 1; +} + +message Dividends { + map value = 1; +} + +// Events + +message DonationReceived { + option (aelf.is_event) = true; + aelf.Address from = 1; + aelf.Address pool_contract = 2; + string symbol = 3; + int64 amount = 4; +} \ No newline at end of file diff --git a/protobuf/acs10_demo_contract.proto b/protobuf/acs10_demo_contract.proto new file mode 100755 index 00000000..877ba1aa --- /dev/null +++ b/protobuf/acs10_demo_contract.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +import "acs10.proto"; + +option csharp_namespace = "AElf.Contracts.ACS10DemoContract"; + +service ACS10DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS10DemoContract.ACS10DemoContractState"; + option (aelf.base) = "acs10.proto"; + + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } +} + +message InitializeInput { + int64 minimum_lock_minutes = 1; +} \ No newline at end of file diff --git a/protobuf/acs11.proto b/protobuf/acs11.proto new file mode 100755 index 00000000..e18dcf14 --- /dev/null +++ b/protobuf/acs11.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package acs11; +import "aelf/options.proto"; +import "aelf/core.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/empty.proto"; + +option (aelf.identity) = "acs11"; +option csharp_namespace = "AElf.Standards.ACS11"; + +service CrossChainInteractionContract { + rpc UpdateInformationFromCrossChain (google.protobuf.BytesValue) returns (google.protobuf.Empty) { + } + + rpc GetChainInitializationInformation (google.protobuf.BytesValue) returns (google.protobuf.BytesValue) { + option (aelf.is_view) = true; + } + rpc CheckCrossChainIndexingPermission (aelf.Address) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} \ No newline at end of file diff --git a/protobuf/acs1_demo_contract.proto b/protobuf/acs1_demo_contract.proto new file mode 100755 index 00000000..ceddb3c9 --- /dev/null +++ b/protobuf/acs1_demo_contract.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +import "acs1.proto"; + +option csharp_namespace = "AElf.Contracts.ACS1DemoContract"; + +service ACS1DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS1DemoContract.ACS1DemoContractState"; + option (aelf.base) = "acs1.proto"; + + rpc Foo (google.protobuf.Empty) returns (google.protobuf.Empty) { + } +} \ No newline at end of file diff --git a/protobuf/acs2.proto b/protobuf/acs2.proto new file mode 100755 index 00000000..29b290cb --- /dev/null +++ b/protobuf/acs2.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package acs2; + +import "aelf/core.proto"; +import "aelf/options.proto"; + +option (aelf.identity) = "acs2"; +option csharp_namespace = "AElf.Standards.ACS2"; + +service ACS2Base { + // Views + rpc GetResourceInfo (aelf.Transaction) returns (ResourceInfo) { + option (aelf.is_view) = true; + } +} + +message ResourceInfo { + repeated aelf.ScopedStatePath write_paths = 1; + repeated aelf.ScopedStatePath read_paths = 2; + bool non_parallelizable = 3; +} diff --git a/protobuf/acs2_demo_contract.proto b/protobuf/acs2_demo_contract.proto new file mode 100755 index 00000000..03a16fcf --- /dev/null +++ b/protobuf/acs2_demo_contract.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +import "acs2.proto"; +import "google/protobuf/empty.proto"; +import "aelf/options.proto"; +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.ACS2DemoContract"; + +service ACS2DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS2DemoContract.ACS2DemoContractState"; + option (aelf.base) = "acs2.proto"; + + rpc TransferCredits (TransferCreditsInput) returns (google.protobuf.Empty) { + } +} + +message TransferCreditsInput { + aelf.Address to = 1; + int64 amount = 2; +} \ No newline at end of file diff --git a/protobuf/acs3.proto b/protobuf/acs3.proto new file mode 100755 index 00000000..5014520c --- /dev/null +++ b/protobuf/acs3.proto @@ -0,0 +1,130 @@ +syntax = "proto3"; + +package acs3; + +import public "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import public "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option (aelf.identity) = "acs3"; +option csharp_namespace = "AElf.Standards.ACS3"; + +service AuthorizationContract { + rpc CreateProposal (CreateProposalInput) returns (aelf.Hash) { + } + rpc Approve (aelf.Hash) returns (google.protobuf.Empty) { + } + rpc Reject(aelf.Hash) returns (google.protobuf.Empty) { + } + rpc Abstain(aelf.Hash) returns (google.protobuf.Empty){ + } + rpc Release(aelf.Hash) returns (google.protobuf.Empty){ + } + rpc ChangeOrganizationThreshold(ProposalReleaseThreshold)returns(google.protobuf.Empty) { + } + rpc ChangeOrganizationProposerWhiteList(ProposerWhiteList) returns (google.protobuf.Empty){ + } + rpc CreateProposalBySystemContract(CreateProposalBySystemContractInput) returns (aelf.Hash){ + } + rpc ClearProposal(aelf.Hash) returns (google.protobuf.Empty){ + } + rpc GetProposal(aelf.Hash) returns (ProposalOutput) { + option (aelf.is_view) = true; + } + rpc ValidateOrganizationExist(aelf.Address) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } + rpc ValidateProposerInWhiteList(ValidateProposerInWhiteListInput) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } +} + +message CreateProposalInput { + string contract_method_name = 1; + aelf.Address to_address = 2; + bytes params = 3; + google.protobuf.Timestamp expired_time = 4; + aelf.Address organization_address = 5; + string proposal_description_url = 6; + aelf.Hash token = 7; +} + +message ProposalOutput { + aelf.Hash proposal_id = 1; + string contract_method_name = 2; + aelf.Address to_address = 3; + bytes params = 4; + google.protobuf.Timestamp expired_time = 5; + aelf.Address organization_address = 6; + aelf.Address proposer = 7; + bool to_be_released = 8; + int64 approval_count = 9; + int64 rejection_count = 10; + int64 abstention_count = 11; +} + +message ProposalReleaseThreshold { + int64 minimal_approval_threshold = 1; + int64 maximal_rejection_threshold = 2; + int64 maximal_abstention_threshold = 3; + int64 minimal_vote_threshold = 4; +} + +message ProposerWhiteList{ + repeated aelf.Address proposers = 1; +} + +message OrganizationHashAddressPair{ + aelf.Hash organization_hash = 1; + aelf.Address organization_address = 2; +} + +message CreateProposalBySystemContractInput { + acs3.CreateProposalInput proposal_input =1; + aelf.Address origin_proposer = 2; +} + +message ValidateProposerInWhiteListInput{ + aelf.Address proposer = 1; + aelf.Address organization_address = 2; +} + +message ProposalCreated{ + option (aelf.is_event) = true; + aelf.Hash proposal_id = 1; + aelf.Address organization_address=2 [(aelf.is_indexed) = true]; +} + +message ProposalReleased{ + option (aelf.is_event) = true; + aelf.Hash proposal_id = 1; + aelf.Address organization_address=2 [(aelf.is_indexed) = true]; +} + +message OrganizationCreated{ + option (aelf.is_event) = true; + aelf.Address organization_address = 1; +} + +message ReceiptCreated { + option (aelf.is_event) = true; + aelf.Hash proposal_id = 1; + aelf.Address address = 2; + string receipt_type = 3; + google.protobuf.Timestamp time = 4; + aelf.Address organization_address = 5 [(aelf.is_indexed) = true]; +} + +message OrganizationWhiteListChanged{ + option (aelf.is_event) = true; + aelf.Address organization_address = 1; + ProposerWhiteList proposer_white_list = 2; +} + +message OrganizationThresholdChanged{ + option (aelf.is_event) = true; + aelf.Address organization_address = 1; + ProposalReleaseThreshold proposer_release_threshold = 2; +} \ No newline at end of file diff --git a/protobuf/acs3_demo_contract.proto b/protobuf/acs3_demo_contract.proto new file mode 100755 index 00000000..0c8bea00 --- /dev/null +++ b/protobuf/acs3_demo_contract.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +import "acs3.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.ACS3DemoContract"; + +service ACS3DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS3DemoContract.ACS3DemoContractState"; + option (aelf.base) = "acs3.proto"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc SetSlogan (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + rpc GetSlogan (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +message ProposalInfo { + aelf.Hash proposal_id = 1; + string contract_method_name = 2; + aelf.Address to_address = 3; + bytes params = 4; + google.protobuf.Timestamp expired_time = 5; + aelf.Address proposer = 6; + aelf.Address organization_address = 7; + repeated aelf.Address approvals = 8; + repeated aelf.Address rejections = 9; + repeated aelf.Address abstentions = 10; + string proposal_description_url = 11; +} \ No newline at end of file diff --git a/protobuf/acs4.proto b/protobuf/acs4.proto new file mode 100755 index 00000000..9fa25bf3 --- /dev/null +++ b/protobuf/acs4.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package acs4; + +import "aelf/options.proto"; +import "aelf/core.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option (aelf.identity) = "acs4"; +option csharp_namespace = "AElf.Standards.ACS4"; + +service ConsensusContract { + rpc GetConsensusCommand (google.protobuf.BytesValue) returns (ConsensusCommand) { + option (aelf.is_view) = true; + } + rpc GetConsensusExtraData (google.protobuf.BytesValue) returns (google.protobuf.BytesValue) { + option (aelf.is_view) = true; + } + rpc GenerateConsensusTransactions (google.protobuf.BytesValue) returns (TransactionList) { + option (aelf.is_view) = true; + } + rpc ValidateConsensusBeforeExecution (google.protobuf.BytesValue) returns (ValidationResult) { + option (aelf.is_view) = true; + } + rpc ValidateConsensusAfterExecution (google.protobuf.BytesValue) returns (ValidationResult) { + option (aelf.is_view) = true; + } +} + +message ConsensusCommand { + int32 limit_milliseconds_of_mining_block = 1;// Time limit of mining next block. + bytes hint = 2;// Context of Hint is diverse according to the consensus protocol we choose, so we use bytes. + google.protobuf.Timestamp arranged_mining_time = 3; + google.protobuf.Timestamp mining_due_time = 4; +} + +message ValidationResult { + bool success = 1; + string message = 2; + bool is_re_trigger = 3; +} + +message TransactionList { + repeated aelf.Transaction transactions = 1; +} \ No newline at end of file diff --git a/protobuf/acs4_demo_contract.proto b/protobuf/acs4_demo_contract.proto new file mode 100755 index 00000000..5a14754f --- /dev/null +++ b/protobuf/acs4_demo_contract.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +import "acs4.proto"; +import "google/protobuf/empty.proto"; +import "aelf/options.proto"; + +option csharp_namespace = "AElf.Contracts.ACS4DemoContract"; + +service ACS4DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS4DemoContract.ACS4DemoContractState"; + option (aelf.base) = "acs4.proto"; + + rpc Record (google.protobuf.Empty) returns (google.protobuf.Empty) { + } +} \ No newline at end of file diff --git a/protobuf/acs5.proto b/protobuf/acs5.proto new file mode 100755 index 00000000..4deb53fb --- /dev/null +++ b/protobuf/acs5.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package acs5; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option (aelf.identity) = "acs5"; +option csharp_namespace = "AElf.Standards.ACS5"; + +service ThresholdSettingContract { + // Actions + rpc SetMethodCallingThreshold (SetMethodCallingThresholdInput) returns (google.protobuf.Empty) { + } + + // Views + rpc GetMethodCallingThreshold (google.protobuf.StringValue) returns (MethodCallingThreshold) { + option (aelf.is_view) = true; + } +} + +message MethodCallingThreshold { + map symbol_to_amount = 1;// The order matters. + ThresholdCheckType threshold_check_type = 2; +} + +message SetMethodCallingThresholdInput { + string method = 1; + map symbol_to_amount = 2;// The order matters. + ThresholdCheckType threshold_check_type = 3; +} + +enum ThresholdCheckType { + BALANCE = 0; + ALLOWANCE = 1; +} \ No newline at end of file diff --git a/protobuf/acs5_demo_contract.proto b/protobuf/acs5_demo_contract.proto new file mode 100755 index 00000000..37746d0a --- /dev/null +++ b/protobuf/acs5_demo_contract.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +import "acs5.proto"; +import "aelf/options.proto"; +import "aelf/core.proto"; +import "google/protobuf/empty.proto"; + +option csharp_namespace = "AElf.Contracts.ACS5DemoContract"; + +service ACS5DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS5DemoContract.ACS5DemoContractState"; + option (aelf.base) = "acs5.proto"; + + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc Foo (google.protobuf.Empty) returns (google.protobuf.Empty) { + } +} + +message InitializeInput { + aelf.Address admin = 1; +} \ No newline at end of file diff --git a/protobuf/acs6.proto b/protobuf/acs6.proto new file mode 100755 index 00000000..27b20b59 --- /dev/null +++ b/protobuf/acs6.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package acs6; + +import "aelf/options.proto"; +import "aelf/core.proto"; +import "google/protobuf/wrappers.proto"; + +option (aelf.identity) = "acs6"; +option csharp_namespace = "AElf.Standards.ACS6"; + +service RandomNumberProviderContract { + rpc GetRandomBytes (google.protobuf.BytesValue) returns (google.protobuf.BytesValue) { + option (aelf.is_view) = true; + } +} + +// Events + +message RandomBytesGenerated { + option (aelf.is_event) = true; + bytes argument = 1; + bytes random_bytes = 2; +} \ No newline at end of file diff --git a/protobuf/acs6_demo_contract.proto b/protobuf/acs6_demo_contract.proto new file mode 100755 index 00000000..8a72edf7 --- /dev/null +++ b/protobuf/acs6_demo_contract.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +import "acs6.proto"; +import "google/protobuf/empty.proto"; +import "aelf/options.proto"; + +option csharp_namespace = "AElf.Contracts.ACS6DemoContract"; + +service ACS6DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS6DemoContract.ACS6DemoContractState"; + option (aelf.base) = "acs6.proto"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } +} + +message RequestSlot { + sint64 round_number = 1; + sint32 order = 2; + sint64 block_height = 3; +} \ No newline at end of file diff --git a/protobuf/acs7.proto b/protobuf/acs7.proto new file mode 100755 index 00000000..780c86f9 --- /dev/null +++ b/protobuf/acs7.proto @@ -0,0 +1,226 @@ +syntax = "proto3"; + +package acs7; + +import public "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import public "google/protobuf/timestamp.proto"; + +option (aelf.identity) = "acs7"; +option csharp_namespace = "AElf.Standards.ACS7"; + +service ACS7 { + + rpc ProposeCrossChainIndexing(CrossChainBlockData) returns (google.protobuf.Empty) { + } + rpc ReleaseCrossChainIndexingProposal(ReleaseCrossChainIndexingProposalInput) returns (google.protobuf.Empty) { + } + rpc RequestSideChainCreation(SideChainCreationRequest) returns (google.protobuf.Empty){ + } + rpc ReleaseSideChainCreation(ReleaseSideChainCreationInput) returns (google.protobuf.Empty){ + } + rpc CreateSideChain (CreateSideChainInput) returns (google.protobuf.Int32Value) { + } + rpc Recharge (RechargeInput) returns (google.protobuf.Empty) { + } + rpc DisposeSideChain (google.protobuf.Int32Value) returns (google.protobuf.Int32Value) { + } + rpc AdjustIndexingFeePrice(AdjustIndexingFeeInput)returns(google.protobuf.Empty){ + } + + rpc VerifyTransaction (VerifyTransactionInput) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetSideChainIdAndHeight (google.protobuf.Empty) returns (ChainIdAndHeightDict) { + option (aelf.is_view) = true; + } + rpc GetSideChainIndexingInformationList (google.protobuf.Empty) returns (SideChainIndexingInformationList) { + option (aelf.is_view) = true; + } + rpc GetAllChainsIdAndHeight (google.protobuf.Empty) returns (ChainIdAndHeightDict) { + option (aelf.is_view) = true; + } + rpc GetIndexedSideChainBlockDataByHeight (google.protobuf.Int64Value) returns (IndexedSideChainBlockData) { + option (aelf.is_view) = true; + } + rpc GetBoundParentChainHeightAndMerklePathByHeight (google.protobuf.Int64Value) returns (CrossChainMerkleProofContext) { + option (aelf.is_view) = true; + } + rpc GetChainInitializationData (google.protobuf.Int32Value) returns (ChainInitializationData) { + option (aelf.is_view) = true; + } +} + +message SideChainBlockData { + int64 height = 1; + aelf.Hash block_header_hash = 2; + aelf.Hash transaction_status_merkle_tree_root = 3; + int32 chain_id = 4; +} + +message RechargeInput { + int32 chain_id = 1; + int64 amount = 2; +} + +message IndexedSideChainBlockDataResult { + int64 height = 1; + aelf.Address miner = 2; + repeated SideChainBlockData side_chain_block_data = 3; +} + +message ParentChainBlockData { + int64 height = 1; + CrossChainExtraData cross_chain_extra_data = 2; + int32 chain_id = 3; + aelf.Hash transaction_status_merkle_tree_root = 4; + + // Indexed block height from side chain and merkle path for this side chain block + map indexed_merkle_path = 5; + map extra_data = 6; +} + +message CrossChainExtraData { + // Merkle tree root of side chain block transaction status root + aelf.Hash transaction_status_merkle_tree_root = 1; +} + +message ChainIdAndHeightDict { + map id_height_dict = 1; +} + +message SideChainIndexingInformationList { + repeated SideChainIndexingInformation indexing_information_list = 1; +} + +message SideChainIndexingInformation { + int32 chain_id = 1; + int64 indexed_height = 2; +} + +message CrossChainBlockData { + repeated SideChainBlockData side_chain_block_data_list = 1; + repeated ParentChainBlockData parent_chain_block_data_list = 2; +} + +message CrossChainMerkleProofContext { + int64 bound_parent_chain_height = 1; + aelf.MerklePath merkle_path_from_parent_chain = 2; +} + +message ChainInitializationData { + int32 chain_id = 1; + aelf.Address creator = 2; + google.protobuf.Timestamp creation_timestamp = 3; + int64 creation_height_on_parent_chain = 4; + bool chain_creator_privilege_preserved = 5; + aelf.Address parent_chain_token_contract_address = 6; + ChainInitializationConsensusInfo chain_initialization_consensus_info = 7; + bytes native_token_info_data = 8; + ResourceTokenInfo resource_token_info = 9; + ChainPrimaryTokenInfo chain_primary_token_info = 10; +} + +message ChainInitializationToken{ + bytes native_token_info_data = 1; +} + +message ResourceTokenInfo{ + bytes resource_token_list_data = 1; + map initial_resource_amount = 2; +} + +message ChainPrimaryTokenInfo{ + bytes chain_primary_token_data = 1; + repeated SideChainTokenInitialIssue side_chain_token_initial_issue_list = 2; +} + +message ChainInitializationConsensusInfo{ + bytes initial_consensus_data = 1; +} + +message SideChainCreationRequest { + int64 indexing_price = 1; + int64 locked_token_amount = 2; + bool is_privilege_preserved = 3; + SideChainTokenCreationRequest side_chain_token_creation_request = 4; + repeated SideChainTokenInitialIssue side_chain_token_initial_issue_list = 5; + map initial_resource_amount = 6; +} + +message SideChainTokenCreationRequest{ + string side_chain_token_symbol = 1; + string side_chain_token_name = 2; + int64 side_chain_token_total_supply = 3; + int32 side_chain_token_decimals = 4; +} + +message SideChainTokenInitialIssue{ + aelf.Address address = 1; + int64 amount = 2; +} + +message SideChainTokenInfo { + string symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + aelf.Address issuer = 5; + bool is_burnable = 6; +} + +message RequestChainCreationOutput { + int32 chain_id = 1; + aelf.Hash proposal_id = 2; +} + +message IndexedParentChainBlockData { + int64 local_chain_height = 1; + repeated acs7.ParentChainBlockData parent_chain_block_data_list = 2; +} + +message IndexedSideChainBlockData{ + repeated acs7.SideChainBlockData side_chain_block_data_list = 1; +} + +message ReleaseCrossChainIndexingProposalInput{ + repeated int32 chain_id_list = 1; +} + +message RecordCrossChainDataInput{ + CrossChainBlockData proposed_cross_chain_data = 1; + aelf.Address proposer = 2; +} + +message CreateSideChainInput{ + SideChainCreationRequest side_chain_creation_request = 1; + aelf.Address proposer = 2; +} + +message ReleaseSideChainCreationInput { + aelf.Hash proposal_id = 1; +} + +message AdjustIndexingFeeInput{ + int32 side_chain_id = 1; + int64 indexing_fee = 2; +} + +message SideChainBlockDataIndexed{ + option (aelf.is_event) = true; +} + +message CrossChainIndexingDataProposedEvent{ + option (aelf.is_event) = true; + acs7.CrossChainBlockData proposed_cross_chain_data = 1; + aelf.Hash proposal_id = 2; +} + +message VerifyTransactionInput { + aelf.Hash transaction_id = 1; + aelf.MerklePath path = 2; + int64 parent_chain_height = 3; + int32 verified_chain_id = 4; +} \ No newline at end of file diff --git a/protobuf/acs8.proto b/protobuf/acs8.proto new file mode 100755 index 00000000..51074b5b --- /dev/null +++ b/protobuf/acs8.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package acs8; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; + +option (aelf.identity) = "acs8"; +option csharp_namespace = "AElf.Standards.ACS8"; + +service ResourceConsumptionContract { + // Actions + rpc BuyResourceToken (BuyResourceTokenInput) returns (google.protobuf.Empty) { + } +} + +message BuyResourceTokenInput { + string symbol = 1; + int64 amount = 2; + int64 pay_limit = 3; // No buy if paying more than this, 0 if no limit +} \ No newline at end of file diff --git a/protobuf/acs8_demo_contract.proto b/protobuf/acs8_demo_contract.proto new file mode 100755 index 00000000..91af4e80 --- /dev/null +++ b/protobuf/acs8_demo_contract.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "acs8.proto"; + +option csharp_namespace = "AElf.Contracts.ACS8DemoContract"; + +service ACS8DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS8DemoContract.ACS8DemoContractState"; + option (aelf.base) = "acs8.proto"; +} \ No newline at end of file diff --git a/protobuf/acs9.proto b/protobuf/acs9.proto new file mode 100755 index 00000000..346ade93 --- /dev/null +++ b/protobuf/acs9.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; + +package acs9; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; + +option (aelf.identity) = "acs9"; +option csharp_namespace = "AElf.Standards.ACS9"; + +service ContractProfitsContract { + // Actions + rpc TakeContractProfits (TakeContractProfitsInput) returns (google.protobuf.Empty) { + } + + // Views + rpc GetProfitConfig (google.protobuf.Empty) returns (ProfitConfig) { + option (aelf.is_view) = true; + } + rpc GetProfitsAmount (google.protobuf.Empty) returns (ProfitsMap) { + option (aelf.is_view) = true; + } +} + +message TakeContractProfitsInput { + string symbol = 1; + int64 amount = 2; +} + +message ProfitConfig { + int32 donation_parts_per_hundred = 1; + repeated string profits_token_symbol_list = 2; + string staking_token_symbol = 3; +} + +message ProfitsMap { + map value = 1; +} \ No newline at end of file diff --git a/protobuf/acs9_demo_contract.proto b/protobuf/acs9_demo_contract.proto new file mode 100755 index 00000000..e0d086cf --- /dev/null +++ b/protobuf/acs9_demo_contract.proto @@ -0,0 +1,65 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "acs9.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "AElf.Contracts.ACS9DemoContract"; + +service ACS9DemoContract { + option (aelf.csharp_state) = "AElf.Contracts.ACS9DemoContract.ACS9DemoContractState"; + option (aelf.base) = "acs9.proto"; + + // Actions + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc SignUp (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc Deposit (DepositInput) returns (google.protobuf.Empty) { + } + rpc Withdraw (WithdrawInput) returns (google.protobuf.Empty) { + } + rpc Use (Record) returns (google.protobuf.Empty) { + } +} + +message InitializeInput { + aelf.Address profit_receiver = 1; + string symbol = 2; + aelf.Hash dividend_pool_contract_name = 3; +} + +message DepositInput { + sint64 amount = 1; +} + +message WithdrawInput { + string symbol = 1; + sint64 amount = 2; +} + +message Record { + RecordType type = 1; + google.protobuf.Timestamp timestamp = 2; + string description = 3; + string symbol = 4; +} + +message Profile { + aelf.Address user_address = 1; + repeated Record records = 2; +} + +message RecordAdded { + option (aelf.is_event) = true; + Record record = 1; +} + +enum RecordType { + SIGN_UP = 0; + DEPOSIT = 1; + WITHDRAW = 2; + USE = 3; +} \ No newline at end of file diff --git a/protobuf/aedpos_contract.proto b/protobuf/aedpos_contract.proto new file mode 100755 index 00000000..96a57c8e --- /dev/null +++ b/protobuf/aedpos_contract.proto @@ -0,0 +1,300 @@ +syntax = "proto3"; + +package AEDPoS; + +import "aelf/options.proto"; +import "aelf/core.proto"; +import "authority_info.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.Consensus.AEDPoS"; + +service AEDPoSContract { + + option (aelf.csharp_state) = "AElf.Contracts.Consensus.AEDPoS.AEDPoSContractState"; + + // Actions + // -> Process + rpc InitialAElfConsensusContract (InitialAElfConsensusContractInput) returns (google.protobuf.Empty) { + } + rpc FirstRound (Round) returns (google.protobuf.Empty) { + } + rpc UpdateValue (UpdateValueInput) returns (google.protobuf.Empty) { + } + rpc NextRound (Round) returns (google.protobuf.Empty) { + } + rpc NextTerm (Round) returns (google.protobuf.Empty) { + } + rpc UpdateTinyBlockInformation (TinyBlockInput) returns (google.protobuf.Empty) { + } + + rpc SetMaximumMinersCount (google.protobuf.Int32Value) returns (google.protobuf.Empty) { + } + rpc ChangeMaximumMinersCountController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Views + rpc GetCurrentMinerList (google.protobuf.Empty) returns (MinerList) { + option (aelf.is_view) = true; + } + rpc GetCurrentMinerPubkeyList (google.protobuf.Empty) returns (PubkeyList) { + option (aelf.is_view) = true; + } + rpc GetCurrentMinerListWithRoundNumber (google.protobuf.Empty) returns (MinerListWithRoundNumber) { + option (aelf.is_view) = true; + } + rpc GetRoundInformation (google.protobuf.Int64Value) returns (Round) { + option (aelf.is_view) = true; + } + rpc GetCurrentRoundNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetCurrentRoundInformation (google.protobuf.Empty) returns (Round) { + option (aelf.is_view) = true; + } + rpc GetPreviousRoundInformation (google.protobuf.Empty) returns (Round) { + option (aelf.is_view) = true; + } + rpc GetCurrentTermNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetCurrentTermMiningReward (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetMinerList (GetMinerListInput) returns (MinerList) { + option (aelf.is_view) = true; + } + rpc GetPreviousMinerList (google.protobuf.Empty) returns (MinerList) { + option (aelf.is_view) = true; + } + rpc GetMinedBlocksOfPreviousTerm (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetNextMinerPubkey (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + rpc GetCurrentMinerPubkey (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + rpc IsCurrentMiner (aelf.Address) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetNextElectCountDown (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetPreviousTermInformation (google.protobuf.Int64Value) returns (Round) { + option (aelf.is_view) = true; + } + rpc GetRandomHash (google.protobuf.Int64Value) returns (aelf.Hash) { + option (aelf.is_view) = true; + } + rpc GetMaximumBlocksCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (aelf.is_view) = true; + } + rpc GetMaximumMinersCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (aelf.is_view) = true; + } + rpc GetMaximumMinersCountController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } + rpc GetMainChainCurrentMinerList (google.protobuf.Empty) returns (MinerList) { + option (aelf.is_view) = true; + } + rpc GetPreviousTermMinerPubkeyList (google.protobuf.Empty) returns (PubkeyList) { + option (aelf.is_view) = true; + } + rpc GetCurrentMiningRewardPerBlock (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } +} + +message InitialAElfConsensusContractInput { + bool is_term_stay_one = 1; + bool is_side_chain = 2; + int64 period_seconds = 3; + int64 miner_increase_interval = 4; +} + +// Just used as parameter of consensus contract method. +message UpdateValueInput { + aelf.Hash out_value = 1; // Calculated from current in value. + aelf.Hash signature = 2; // Calculated from current in value and signatures of previous round. + int64 round_id = 3; // To ensure the values to update will be apply to correct round by comparing round id. + aelf.Hash previous_in_value = 4; // Publish previous in value for validation previous signature and previous out value. + google.protobuf.Timestamp actual_mining_time = 5; + int32 supposed_order_of_next_round = 6; + map tune_order_information = 7; + map encrypted_pieces = 8; // For others to recover his in value of current round. + map decrypted_pieces = 9; + int64 produced_blocks = 10; + map miners_previous_in_values = 11; + int64 implied_irreversible_block_height = 12; +} + +// The miners public key list for each term. +message MinerList { + repeated bytes pubkeys = 1; +} + +message PubkeyList { + repeated string pubkeys = 1; +} + +// To query first round number of each term. +message TermNumberLookUp { + map map = 1;// Term number -> Round number. +} + +// All the candidates. +message Candidates { + repeated bytes pubkeys = 1; +} + +// The information of a round. +message Round { + int64 round_number = 1; + map real_time_miners_information = 2; + int64 main_chain_miners_round_number = 3; + int64 blockchain_age = 4; + string extra_block_producer_of_previous_round = 5; + int64 term_number = 6; + int64 confirmed_irreversible_block_height = 7; + int64 confirmed_irreversible_block_round_number = 8; + bool is_miner_list_just_changed = 9; + int64 round_id_for_validation = 10; +} + +// The information of a miner in a specific round. +message MinerInRound { + int32 order = 1; + bool is_extra_block_producer = 2; + aelf.Hash in_value = 3; + aelf.Hash out_value = 4; + aelf.Hash signature = 5; + google.protobuf.Timestamp expected_mining_time = 6; + int64 produced_blocks = 7; + int64 missed_time_slots = 8; + string pubkey = 9; + aelf.Hash previous_in_value = 10; + int32 supposed_order_of_next_round = 11; + int32 final_order_of_next_round = 12; + repeated google.protobuf.Timestamp actual_mining_times = 13;// Miners must fill actual mining time when they do the mining. + map encrypted_pieces = 14; + map decrypted_pieces = 15; + int64 produced_tiny_blocks = 16; + int64 implied_irreversible_block_height = 17; +} + +message AElfConsensusHeaderInformation { + bytes sender_pubkey = 1; + Round round = 2; + AElfConsensusBehaviour behaviour = 3; +} + +message AElfConsensusHint { + AElfConsensusBehaviour behaviour = 1; + int64 round_id = 2; + int64 previous_round_id = 3; +} + +enum AElfConsensusBehaviour { + UPDATE_VALUE = 0; + NEXT_ROUND = 1; + NEXT_TERM = 2; + NOTHING = 3; + TINY_BLOCK = 4; +} + +message AElfConsensusTriggerInformation { + bytes pubkey = 1;// A must-have. + aelf.Hash in_value = 2; + aelf.Hash previous_in_value = 3;// Random hash of previous round for validation. + AElfConsensusBehaviour behaviour = 4; + map encrypted_pieces = 5; + map decrypted_pieces = 6; + map revealed_in_values = 7; +} + +message TermInfo { + int64 term_number = 1; + int64 round_number = 2; +} + +message MinerListWithRoundNumber { + MinerList miner_list = 1; + int64 round_number = 2; +} + +message TinyBlockInput { + int64 round_id = 1; + google.protobuf.Timestamp actual_mining_time = 2; + int64 produced_blocks = 3; +} + +message VoteMinersCountInput { + int32 miners_count = 1; + int64 amount = 2; +} + +message ConsensusInformation { + bytes value = 1; +} + +message GetMinerListInput { + int64 term_number = 1; +} + +message RandomNumberRequestInformation { + int64 target_round_number = 1;// The random hash is likely generated during this round. + int64 order = 2; + int64 expected_block_height = 3; +} + +message HashList { + repeated aelf.Hash values = 1; +} + +message LatestPubkeyToTinyBlocksCount { + string pubkey = 1; + int64 blocks_count = 2; +} + +// Events +message IrreversibleBlockFound { + option (aelf.is_event) = true; + int64 irreversible_block_height = 1 [(aelf.is_indexed) = true]; +} + +message IrreversibleBlockHeightUnacceptable { + option (aelf.is_event) = true; + int64 distance_to_irreversible_block_height = 1; +} + +message MiningInformationUpdated { + option (aelf.is_event) = true; + string pubkey = 1 [(aelf.is_indexed) = true]; + google.protobuf.Timestamp mining_time = 2 [(aelf.is_indexed) = true]; + string behaviour = 3 [(aelf.is_indexed) = true]; + int64 block_height = 4 [(aelf.is_indexed) = true]; + aelf.Hash previous_block_hash = 5 [(aelf.is_indexed) = true]; +} + +message SecretSharingInformation { + option (aelf.is_event) = true; + Round previous_round = 1 [(aelf.is_indexed) = true]; + int64 current_round_id = 2; + int64 previous_round_id = 3; +} + +message MiningRewardGenerated { + option (aelf.is_event) = true; + int64 term_number = 1 [(aelf.is_indexed) = true]; + int64 amount = 2; +} + +message MinerReplaced { + option (aelf.is_event) = true; + string new_miner_pubkey = 1; +} \ No newline at end of file diff --git a/protobuf/aedpos_contract_impl.proto b/protobuf/aedpos_contract_impl.proto new file mode 100755 index 00000000..fd38f09c --- /dev/null +++ b/protobuf/aedpos_contract_impl.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package AEDPoSImpl; + +import "acs1.proto"; +import "acs4.proto"; +import "acs6.proto"; +import "acs10.proto"; +import "acs11.proto"; +import "aedpos_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Consensus.AEDPoS"; + +service AEDPoSContractImpl { + option (aelf.csharp_state) = "AElf.Contracts.Consensus.AEDPoS.AEDPoSContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "acs4.proto"; + option (aelf.base) = "acs6.proto"; + // Dividend pool for side chain. + option (aelf.base) = "acs10.proto"; + // Interact with Cross Chain module. + option (aelf.base) = "acs11.proto"; + option (aelf.base) = "aedpos_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/aelf/core.proto b/protobuf/aelf/core.proto new file mode 100755 index 00000000..3ed1aa7f --- /dev/null +++ b/protobuf/aelf/core.proto @@ -0,0 +1,103 @@ +syntax = "proto3"; + +package aelf; + +option csharp_namespace = "AElf.Types"; + +message Transaction { + Address from = 1; + Address to = 2; + int64 ref_block_number = 3; + bytes ref_block_prefix = 4; + string method_name = 5; + bytes params = 6; + bytes signature = 10000; +} + +message StatePath { + repeated string parts = 1; +} + +message ScopedStatePath { + Address address = 1; + StatePath path = 2; +} + +enum TransactionResultStatus { + NOT_EXISTED = 0; + PENDING = 1; + FAILED = 2; + MINED = 3; + CONFLICT = 4; + PENDING_VALIDATION = 5; + NODE_VALIDATION_FAILED = 6; +} + +message TransactionResult { + Hash transaction_id = 1; + TransactionResultStatus status = 2; + repeated LogEvent logs = 3; + bytes bloom = 4; + bytes return_value = 5; + int64 block_number = 6; + Hash block_hash = 7; + string error = 10; +} + +message LogEvent { + Address address = 1; + string name = 2; + repeated bytes indexed = 3; + bytes non_indexed = 4; +} + +message SmartContractRegistration { + sint32 category = 1; + bytes code = 2; + Hash code_hash = 3; + bool is_system_contract = 4; + int32 version = 5; +} + +message TransactionExecutingStateSet { + map writes = 1; + map reads = 2; + map deletes = 3; +} + +// Common + +message Address +{ + bytes value = 1; +} + +message Hash +{ + bytes value = 1; +} + +message SInt32Value +{ + sint32 value = 1; +} + +message SInt64Value +{ + sint64 value = 1; +} + +message MerklePath { + repeated MerklePathNode merkle_path_nodes = 1; +} + +message MerklePathNode{ + Hash hash = 1; + bool is_left_child_node = 2; +} + +message BinaryMerkleTree { + repeated Hash nodes = 1; + Hash root = 2; + int32 leaf_count = 3; +} \ No newline at end of file diff --git a/protobuf/aelf/options.proto b/protobuf/aelf/options.proto new file mode 100755 index 00000000..34058baf --- /dev/null +++ b/protobuf/aelf/options.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; + +package aelf; + +import "google/protobuf/descriptor.proto"; + +option csharp_namespace = "AElf"; + +// All aelf custom options field numbers are like 50yxxx +// where y stands for the custom options type: +// 0 - FileOptions +// 1 - MessageOptions +// 2 - FieldOptions +// 3 - EnumOptions +// 4 - EnumValueOptions +// 5 - ServiceOptions +// 6 - MethodOptions + +extend google.protobuf.FileOptions { + string identity = 500001; +} + +extend google.protobuf.ServiceOptions { + repeated string base = 505001; + string csharp_state = 505030; +} + +extend google.protobuf.MethodOptions { + bool is_view = 506001; +} + +extend google.protobuf.MessageOptions { + bool is_event = 50100; +} + +extend google.protobuf.FieldOptions { + bool is_indexed = 502001; +} diff --git a/protobuf/association_contract.proto b/protobuf/association_contract.proto new file mode 100755 index 00000000..8bb7cca6 --- /dev/null +++ b/protobuf/association_contract.proto @@ -0,0 +1,97 @@ +syntax = "proto3"; + +package Association; + +import "acs3.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.Association"; + +service AssociationContract { + + option (aelf.csharp_state) = "AElf.Contracts.Association.AssociationState"; + + // Actions + rpc CreateOrganization (CreateOrganizationInput) returns (aelf.Address) { + } + rpc CreateOrganizationBySystemContract(CreateOrganizationBySystemContractInput) returns (aelf.Address){ + } + rpc AddMember(aelf.Address) returns (google.protobuf.Empty){ + } + rpc RemoveMember(aelf.Address) returns (google.protobuf.Empty){ + } + rpc ChangeMember(ChangeMemberInput) returns (google.protobuf.Empty){ + } + //view + rpc GetOrganization (aelf.Address) returns (Organization) { + option (aelf.is_view) = true; + } + rpc CalculateOrganizationAddress(CreateOrganizationInput) returns (aelf.Address){ + option (aelf.is_view) = true; + } +} + +message CreateOrganizationInput{ + OrganizationMemberList organization_member_list = 1; + acs3.ProposalReleaseThreshold proposal_release_threshold = 2; + acs3.ProposerWhiteList proposer_white_list = 3; + aelf.Hash creation_token = 4; +} + +message Organization{ + OrganizationMemberList organization_member_list = 1; + acs3.ProposalReleaseThreshold proposal_release_threshold = 2; + acs3.ProposerWhiteList proposer_white_list = 3; + aelf.Address organization_address = 4; + aelf.Hash organization_hash = 5; + aelf.Hash creation_token = 6; +} + +message ProposalInfo { + aelf.Hash proposal_id = 1; + string contract_method_name = 2; + aelf.Address to_address = 3; + bytes params = 4; + google.protobuf.Timestamp expired_time = 5; + aelf.Address proposer = 6; + aelf.Address organization_address = 7; + repeated aelf.Address approvals = 8; + repeated aelf.Address rejections = 9; + repeated aelf.Address abstentions = 10; + string proposal_description_url = 11; +} + +message OrganizationMemberList { + repeated aelf.Address organization_members = 1; +} + +message ChangeMemberInput{ + aelf.Address old_member = 1; + aelf.Address new_member = 2; +} + +message CreateOrganizationBySystemContractInput { + CreateOrganizationInput organization_creation_input = 1; + string organization_address_feedback_method = 2; +} + +message MemberAdded{ + option (aelf.is_event) = true; + aelf.Address member = 1; + aelf.Address organization_address = 2 [(aelf.is_indexed) = true]; +} + +message MemberRemoved{ + option (aelf.is_event) = true; + aelf.Address member = 1; + aelf.Address organization_address = 2 [(aelf.is_indexed) = true]; +} + +message MemberChanged{ + option (aelf.is_event) = true; + aelf.Address old_member = 1; + aelf.Address new_member = 2; + aelf.Address organization_address = 3 [(aelf.is_indexed) = true]; +} \ No newline at end of file diff --git a/protobuf/association_contract_impl.proto b/protobuf/association_contract_impl.proto new file mode 100755 index 00000000..b3e941a5 --- /dev/null +++ b/protobuf/association_contract_impl.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package AssociationImpl; + +import "acs1.proto"; +import "acs3.proto"; +import "association_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Association"; + +service AssociationContractImpl { + + option (aelf.csharp_state) = "AElf.Contracts.Association.AssociationState"; + option (aelf.base) = "acs3.proto"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "association_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/authority_info.proto b/protobuf/authority_info.proto new file mode 100755 index 00000000..495e5931 --- /dev/null +++ b/protobuf/authority_info.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/protobuf/basic_contract_zero.proto b/protobuf/basic_contract_zero.proto new file mode 100755 index 00000000..5701a9c5 --- /dev/null +++ b/protobuf/basic_contract_zero.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package Zero; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "aelf/core.proto"; +import "google/protobuf/timestamp.proto"; +import "authority_info.proto"; + +option csharp_namespace = "AElf.Contracts.Genesis"; + +service BasicContractZero { + option (aelf.csharp_state) = "AElf.Contracts.Genesis.BasicContractZeroState"; + + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc SetInitialControllerAddress (aelf.Address) returns (google.protobuf.Empty) { + } + rpc ChangeContractDeploymentController (AuthorityInfo) returns (google.protobuf.Empty) { + } + rpc ChangeCodeCheckController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + rpc GetContractDeploymentController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } + rpc GetCodeCheckController(google.protobuf.Empty) returns (AuthorityInfo){ + option (aelf.is_view) = true; + } +} + +message InitializeInput{ + bool contract_deployment_authority_required = 1; +} + +message ContractProposingInput{ + aelf.Address proposer = 1; + ContractProposingInputStatus status = 2; + google.protobuf.Timestamp expired_time = 3; +} + +enum ContractProposingInputStatus { + PROPOSED = 0; + APPROVED = 1; + CODE_CHECK_PROPOSED = 2; + CODE_CHECKED = 3; +} \ No newline at end of file diff --git a/protobuf/basic_contract_zero_impl.proto b/protobuf/basic_contract_zero_impl.proto new file mode 100755 index 00000000..a542c5ad --- /dev/null +++ b/protobuf/basic_contract_zero_impl.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package ZeroImpl; + +import "acs0.proto"; +import "acs1.proto"; +import "google/protobuf/timestamp.proto"; +import "basic_contract_zero.proto"; + +option csharp_namespace = "AElf.Contracts.Genesis"; + +service BasicContractZeroImpl { + option (aelf.csharp_state) = "AElf.Contracts.Genesis.BasicContractZeroState"; + option (aelf.base) = "acs0.proto"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "basic_contract_zero.proto"; +} \ No newline at end of file diff --git a/protobuf/basic_contract_zero_update.proto b/protobuf/basic_contract_zero_update.proto new file mode 100755 index 00000000..fbfb3422 --- /dev/null +++ b/protobuf/basic_contract_zero_update.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package Zero; + +import "aelf/options.proto"; +import "acs0.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.GenesisUpdate"; + +service BasicContractZero { + option (aelf.csharp_state) = "AElf.Contracts.GenesisUpdate.BasicContractZeroState"; + option (aelf.base) = "acs0.proto"; + + rpc GetContractDeploymentAuthorityRequired (google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc ChangeGenesisOwnerAddress (aelf.Address) returns (google.protobuf.Empty) { + } + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } +} + +message InitializeInput{ + bool contract_deployment_authority_required = 1; +} \ No newline at end of file diff --git a/protobuf/bingo_contract.proto b/protobuf/bingo_contract.proto new file mode 100755 index 00000000..e5335ebe --- /dev/null +++ b/protobuf/bingo_contract.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "AElf.Contracts.BingoContract"; + +service BingoContract { + option (aelf.csharp_state) = "AElf.Contracts.BingoContract.BingoContractState"; + + // Actions + // Buy or Bet + rpc Play (PlayInput) returns (google.protobuf.Int64Value) { + } + // Waiting few seconds, you can draw the prize. + // Play -> waiting seconds -> Bingo + rpc Bingo (aelf.Hash) returns (BingoOutput) { + } + rpc Quit (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + // Views + rpc GetAward (aelf.Hash) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetPlayerInformation (aelf.Address) returns (PlayerInformation) { + option (aelf.is_view) = true; + } + rpc GetPlayerInformationCompleted (aelf.Address) returns (PlayerInformation) { + option (aelf.is_view) = true; + } +} + +message PlayInput { + int64 buy_amount = 1; + int64 buy_type = 2; // type = {1: small, 2: big} + string token_symbol = 3; +} + +message BingoOutput { + int64 random = 1; + bool is_win = 2; + int64 bout_type = 3; + int64 award = 4; +} + +message PlayerInformation { + aelf.Hash seed = 1; + repeated BoutInformation bouts = 2; + google.protobuf.Timestamp register_time = 3; +} + +message BoutInformation { + int64 play_block_height = 1; + int64 amount = 2; + int64 award = 3; + bool is_complete = 4; + aelf.Hash play_id = 5; + int64 bingo_block_height = 6; + int64 bout_type = 7; + string token_symbol = 8; + int64 lottery_code = 9; + google.protobuf.Timestamp bet_time = 10; +} \ No newline at end of file diff --git a/protobuf/bingo_game_contract.proto b/protobuf/bingo_game_contract.proto new file mode 100755 index 00000000..b13b3724 --- /dev/null +++ b/protobuf/bingo_game_contract.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "AElf.Contracts.BingoGameContract"; + +service BingoGameContract { + option (aelf.csharp_state) = "AElf.Contracts.BingoGameContract.BingoGameContractState"; + + // Actions + rpc Register (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc Play (google.protobuf.Int64Value) returns (google.protobuf.Int64Value) { + } + rpc Bingo (aelf.Hash) returns (google.protobuf.BoolValue) { + } + rpc Quit (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + // Views + rpc GetAward (aelf.Hash) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetPlayerInformation (aelf.Address) returns (PlayerInformation) { + option (aelf.is_view) = true; + } +} + +message PlayerInformation { + aelf.Hash seed = 1; + repeated BoutInformation bouts = 2; + google.protobuf.Timestamp register_time = 3; +} + +message BoutInformation { + int64 play_block_height = 1; + int64 amount = 2; + int64 award = 3; + bool is_complete = 4; + aelf.Hash play_id = 5; + int64 bingo_block_height = 6; +} \ No newline at end of file diff --git a/protobuf/code_check.proto b/protobuf/code_check.proto new file mode 100755 index 00000000..05c662e1 --- /dev/null +++ b/protobuf/code_check.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package aelf; + +option csharp_namespace = "AElf.Kernel.CodeCheck"; + +message RequiredAcsInContracts { + repeated string acs_list = 1; + bool require_all = 2; +} diff --git a/protobuf/configuration_contract.proto b/protobuf/configuration_contract.proto new file mode 100755 index 00000000..1da43061 --- /dev/null +++ b/protobuf/configuration_contract.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package Configuration; + +import "aelf/core.proto"; +import "google/protobuf/wrappers.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "authority_info.proto"; + +option csharp_namespace = "AElf.Contracts.Configuration"; + +service Configuration { + option (aelf.csharp_state) = "AElf.Contracts.Configuration.ConfigurationState"; + // Actions + rpc SetConfiguration (SetConfigurationInput) returns (google.protobuf.Empty) { + } + rpc ChangeConfigurationController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Views + rpc GetConfiguration (google.protobuf.StringValue) returns (google.protobuf.BytesValue) { + option (aelf.is_view) = true; + } + rpc GetConfigurationController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } + +} + +message SetConfigurationInput { + string key = 1; + bytes value = 2; +} + +// Events + +message ConfigurationSet { + option (aelf.is_event) = true; + string key = 1; + bytes value = 2; +} \ No newline at end of file diff --git a/protobuf/configuration_contract_impl.proto b/protobuf/configuration_contract_impl.proto new file mode 100755 index 00000000..f6861784 --- /dev/null +++ b/protobuf/configuration_contract_impl.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package ConfigurationImpl; + +import "aelf/core.proto"; +import "google/protobuf/wrappers.proto"; +import "acs1.proto"; +import "configuration_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Configuration"; + +service ConfigurationImpl { + option (aelf.csharp_state) = "AElf.Contracts.Configuration.ConfigurationState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "configuration_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/cross_chain_contract.proto b/protobuf/cross_chain_contract.proto new file mode 100755 index 00000000..ea7e8054 --- /dev/null +++ b/protobuf/cross_chain_contract.proto @@ -0,0 +1,190 @@ +syntax = "proto3"; + +package CrossChain; + +import "acs7.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "authority_info.proto"; + +option csharp_namespace = "AElf.Contracts.CrossChain"; + +service CrossChainContract { + + option (aelf.csharp_state) = "AElf.Contracts.CrossChain.CrossChainContractState"; + + // Actions + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc SetInitialSideChainLifetimeControllerAddress(aelf.Address) returns (google.protobuf.Empty){ + } + rpc SetInitialIndexingControllerAddress(aelf.Address) returns (google.protobuf.Empty){ + } + rpc ChangeCrossChainIndexingController(AuthorityInfo) returns (google.protobuf.Empty) { + } + rpc ChangeSideChainLifetimeController(AuthorityInfo) returns (google.protobuf.Empty) { + } + rpc ChangeSideChainIndexingFeeController(ChangeSideChainIndexingFeeControllerInput) returns (google.protobuf.Empty){ + } + rpc AcceptCrossChainIndexingProposal(AcceptCrossChainIndexingProposalInput) returns (google.protobuf.Empty){ + } + + // Views + rpc GetSideChainCreator (google.protobuf.Int32Value) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetChainStatus (google.protobuf.Int32Value) returns (GetChainStatusOutput) { + option (aelf.is_view) = true; + } + rpc GetSideChainHeight (google.protobuf.Int32Value) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetParentChainHeight (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetParentChainId (google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (aelf.is_view) = true; + } + rpc GetSideChainBalance (google.protobuf.Int32Value) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetSideChainIndexingFeeDebt (google.protobuf.Int32Value) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetIndexingProposalStatus (google.protobuf.Empty) returns (GetIndexingProposalStatusOutput) { + option (aelf.is_view) = true; + } + rpc GetSideChainIndexingFeePrice(google.protobuf.Int32Value) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetSideChainLifetimeController(google.protobuf.Empty) returns (AuthorityInfo){ + option (aelf.is_view) = true; + } + rpc GetCrossChainIndexingController(google.protobuf.Empty) returns (AuthorityInfo){ + option (aelf.is_view) = true; + } + rpc GetSideChainIndexingFeeController(google.protobuf.Int32Value) returns (AuthorityInfo){ + option (aelf.is_view) = true; + } +} + +message InitializeInput { + int32 parent_chain_id = 1; + int64 creation_height_on_parent_chain = 2; + bool is_privilege_preserved = 3; +} + +message IndexedChainHeightDict{ + map indexed_chain_heights = 1; +} + +// Events +message SideChainCreatedEvent { + option (aelf.is_event) = true; + aelf.Address creator = 1; + int32 chainId = 2; +} + +message Disposed { + option (aelf.is_event) = true; + int32 chain_id = 1; +} + +message ProposedCrossChainIndexing{ + map ChainIndexingProposalCollections = 1; +} + +message ChainIndexingProposal{ + aelf.Hash proposal_id = 1; + aelf.Address proposer = 2; + acs7.CrossChainBlockData proposed_cross_chain_block_data = 3; + CrossChainIndexingProposalStatus status = 4; + int32 chain_id = 5; +} + +message GetIndexingProposalStatusOutput{ + map chain_indexing_proposal_status = 1; +} + +message PendingChainIndexingProposalStatus{ + aelf.Hash proposal_id = 1; + aelf.Address proposer = 2; + bool to_be_released = 3; + acs7.CrossChainBlockData proposed_cross_chain_block_data = 4; + google.protobuf.Timestamp expired_time = 5; +} + +message GetPendingCrossChainIndexingProposalOutput{ + aelf.Hash proposal_id = 1; + aelf.Address proposer = 2; + bool to_be_released = 3; + acs7.CrossChainBlockData proposed_cross_chain_block_data = 4; + google.protobuf.Timestamp expired_time = 5; +} + +enum CrossChainIndexingProposalStatus{ + NON_PROPOSED = 0; + PENDING = 1; + ACCEPTED = 2; +} + +message GetSideChainIndexingFeeControllerOutput { + AuthorityInfo authority_info = 1; + bytes organization_creation_input_bytes = 2; +} + +enum SideChainStatus +{ + FATAL = 0; + ACTIVE = 1; + INDEXING_FEE_DEBT = 2; + TERMINATED = 3; +} + +message SideChainInfo { + aelf.Address proposer = 1; + SideChainStatus side_chain_status = 2; + int32 side_chain_id = 3; + google.protobuf.Timestamp creation_timestamp = 4; + int64 creation_height_on_parent_chain = 5; + int64 indexing_price = 6; + bool is_privilege_preserved = 7; + map arrears_info = 8; + AuthorityInfo IndexingFeeController = 9; +} + +message GetChainStatusOutput{ + SideChainStatus status = 1; +} + +message SideChainCreationRequestState{ + acs7.SideChainCreationRequest side_chain_creation_request = 1; + google.protobuf.Timestamp expired_time = 2; + aelf.Address proposer = 3; +} + +message ChangeSideChainIndexingFeeControllerInput{ + int32 chain_id = 1; + AuthorityInfo authority_info = 2; +} + +message SideChainLifetimeControllerChanged{ + option (aelf.is_event) = true; + AuthorityInfo authority_info = 1; +} + +message CrossChainIndexingControllerChanged{ + option (aelf.is_event) = true; + AuthorityInfo authority_info = 1; +} + +message SideChainIndexingFeeControllerChanged{ + option (aelf.is_event) = true; + int32 chain_id = 1 [(aelf.is_indexed) = true]; + AuthorityInfo authority_info = 2; +} + +message AcceptCrossChainIndexingProposalInput{ + int32 chain_id = 1; +} \ No newline at end of file diff --git a/protobuf/cross_chain_contract_impl.proto b/protobuf/cross_chain_contract_impl.proto new file mode 100755 index 00000000..59d0b343 --- /dev/null +++ b/protobuf/cross_chain_contract_impl.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package CrossChainImpl; + +import "acs1.proto"; +import "acs7.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "cross_chain_contract.proto"; + +option csharp_namespace = "AElf.Contracts.CrossChain"; + +service CrossChainContractImpl { + + option (aelf.csharp_state) = "AElf.Contracts.CrossChain.CrossChainContractState"; + option (aelf.base) = "acs7.proto"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "cross_chain_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/crosschain_rpc.proto b/protobuf/crosschain_rpc.proto new file mode 100755 index 00000000..117e1f06 --- /dev/null +++ b/protobuf/crosschain_rpc.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +import "acs7.proto"; + +option csharp_namespace = "AElf.CrossChain"; + +message CrossChainRequest { + int32 chain_id = 1; // own chain id + int64 next_height = 2; +} + +message CrossChainExtraData { + aelf.Hash transaction_status_merkle_tree_root = 1; // Merkle tree root of side chain block transaction roots +} + +message HandShake { + int32 listening_port = 1; + int32 chain_id = 2; +} + +message HandShakeReply { + HandShakeStatus status = 1; + enum HandShakeStatus{ + SUCCESS = 0; + INVALID_HANDSHAKE_REQUEST = 1; + FATAL = 2; + } +} + +message SideChainInitializationRequest{ + int32 chain_id = 1; +} + +service ParentChainRpc { + rpc RequestIndexingFromParentChain (CrossChainRequest) returns (stream acs7.ParentChainBlockData) {} + rpc RequestChainInitializationDataFromParentChain (SideChainInitializationRequest) returns (acs7.ChainInitializationData) {} +} + +service SideChainRpc{ + rpc RequestIndexingFromSideChain (CrossChainRequest) returns (stream acs7.SideChainBlockData) {} +} + +service BasicCrossChainRpc{ + rpc CrossChainHandShake (HandShake) returns (HandShakeReply) {} +} + + diff --git a/protobuf/economic_contract.proto b/protobuf/economic_contract.proto new file mode 100755 index 00000000..a3aba11b --- /dev/null +++ b/protobuf/economic_contract.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package Economic; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; + +option csharp_namespace = "AElf.Contracts.Economic"; + +service EconomicContract { + option (aelf.csharp_state) = "AElf.Contracts.Economic.EconomicContractState"; + rpc IssueNativeToken (IssueNativeTokenInput) returns (google.protobuf.Empty) { + } + rpc InitialEconomicSystem (InitialEconomicSystemInput) returns (google.protobuf.Empty) { + } +} + +message InitialEconomicSystemInput { + string native_token_symbol = 1; + string native_token_name = 2; + int64 native_token_total_supply = 3; + int32 native_token_decimals = 4; + bool is_native_token_burnable = 5; + int64 mining_reward_total_amount = 6; + int64 transaction_size_fee_unit_price = 7; +} + +message IssueNativeTokenInput { + int64 amount = 1; + string memo = 2; + aelf.Address to = 3; +} + +message IssueResourceTokenInput { + string symbol = 1; + int64 amount = 2; + string memo = 3; + aelf.Address to = 4; +} \ No newline at end of file diff --git a/protobuf/economic_contract_impl.proto b/protobuf/economic_contract_impl.proto new file mode 100755 index 00000000..8b1fbf05 --- /dev/null +++ b/protobuf/economic_contract_impl.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package EconomicImpl; + +import "aelf/core.proto"; +import "acs1.proto"; +import "economic_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Economic"; + +service EconomicContractImpl { + option (aelf.csharp_state) = "AElf.Contracts.Economic.EconomicContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "economic_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/election_contract.proto b/protobuf/election_contract.proto new file mode 100755 index 00000000..f849853b --- /dev/null +++ b/protobuf/election_contract.proto @@ -0,0 +1,549 @@ +/** + * Election contract. + */ +syntax = "proto3"; + +package Election; + +import "aelf/core.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/empty.proto"; +import "aelf/options.proto"; +import "authority_info.proto"; + +option csharp_namespace = "AElf.Contracts.Election"; + +service ElectionContract { + + option (aelf.csharp_state) = "AElf.Contracts.Election.ElectionContractState"; + + // Initialize the election contract. + rpc InitialElectionContract (InitialElectionContractInput) returns (google.protobuf.Empty) { + } + + // Register a new voting item through vote contract. + rpc RegisterElectionVotingEvent (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + // Take snapshot according to term number, and distribute profits. + rpc TakeSnapshot (TakeElectionSnapshotInput) returns (google.protobuf.Empty) { + } + + // To be a block producer, a user should first register to be a candidate and lock some token as a deposit. + // If the data center is not full, the user will be added in automatically and get one weight + // for sharing bonus in the future. + rpc AnnounceElection (aelf.Address) returns (google.protobuf.Empty) { + } + rpc AnnounceElectionFor (AnnounceElectionForInput) returns (google.protobuf.Empty) { + } + + // A candidate is able to quit the election provided he is not currently elected. If you quit successfully, + // the candidate will get his locked tokens back and will not receive anymore bonus. + rpc QuitElection (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + // Used for voting for a candidate to be elected. The tokens you vote with will be locked until the end time. + // According to the number of token you voted and its lock time, you can get corresponding weight for + // sharing the bonus in the future. And return the vote id. + rpc Vote (VoteMinerInput) returns (aelf.Hash) { + } + + rpc VoteWithExpiredVotes (VoteMinerInput) returns (aelf.Hash) { + } + + // Before the end time, you are able to change your vote target to other candidates. + rpc ChangeVotingOption (ChangeVotingOptionInput) returns (google.protobuf.Empty) { + } + + // After the lock time, your locked tokens will be unlocked and you can withdraw them according to the vote id. + rpc Withdraw (aelf.Hash) returns (google.protobuf.Empty) { + } + + rpc AssistWithdraw (AssistWithdrawInput) returns (google.protobuf.Empty) { + } + + rpc FixTotalWeights (FixTotalWeightsInput) returns (google.protobuf.Empty) { + } + + // Update candidate information by consensus contract. + rpc UpdateCandidateInformation (UpdateCandidateInformationInput) returns (google.protobuf.Empty) { + } + + // Batch update candidate information by consensus contract. + rpc UpdateMultipleCandidateInformation (UpdateMultipleCandidateInformationInput) returns (google.protobuf.Empty) { + } + + // Update the count of miner by consensus contract. + rpc UpdateMinersCount (UpdateMinersCountInput) returns (google.protobuf.Empty) { + } + + // Set the treasury profit ids. + rpc SetTreasurySchemeIds (SetTreasurySchemeIdsInput) returns (google.protobuf.Empty) { + } + + // Set the weight of vote interest. + rpc SetVoteWeightInterest (VoteWeightInterestList) returns (google.protobuf.Empty) { + } + + // Set the weight of lock time and votes in the calculation of voting weight. + rpc SetVoteWeightProportion (VoteWeightProportion) returns (google.protobuf.Empty) { + } + + // Change the controller for the weight of vote interest. + rpc ChangeVoteWeightInterestController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Candidate admin can replace candidate pubkey with a new pubkey. + rpc ReplaceCandidatePubkey (ReplaceCandidatePubkeyInput) returns (google.protobuf.Empty) { + } + + // Set admin address of candidate (mostly supply) + rpc SetCandidateAdmin (SetCandidateAdminInput) returns (google.protobuf.Empty) { + } + + rpc RemoveEvilNode (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + rpc EnableElection (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + rpc SetProfitsReceiver (SetProfitsReceiverInput) returns (google.protobuf.Empty) { + } + + // Views + + // Get all candidates’ public keys. + rpc GetCandidates (google.protobuf.Empty) returns (PubkeyList) { + option (aelf.is_view) = true; + } + + // Get all candidates whose number of votes is greater than 0. + rpc GetVotedCandidates (google.protobuf.Empty) returns (PubkeyList) { + option (aelf.is_view) = true; + } + + // Get a candidate’s information. + rpc GetCandidateInformation (google.protobuf.StringValue) returns (CandidateInformation) { + option (aelf.is_view) = true; + } + + // Get the victories of the latest term. + rpc GetVictories (google.protobuf.Empty) returns (PubkeyList) { + option (aelf.is_view) = true; + } + + // Get the snapshot of term according to term number. + rpc GetTermSnapshot (GetTermSnapshotInput) returns (TermSnapshot) { + option (aelf.is_view) = true; + } + + // Get the count of miner. + rpc GetMinersCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (aelf.is_view) = true; + } + + // Get the election result according to term id. + rpc GetElectionResult (GetElectionResultInput) returns (ElectionResult) { + option (aelf.is_view) = true; + } + + // Get the voter information according to voter public key. + rpc GetElectorVote (google.protobuf.StringValue) returns (ElectorVote) { + option (aelf.is_view) = true; + } + + // Gets the voter information including the active voting records (excluding withdrawn voting records.). + rpc GetElectorVoteWithRecords (google.protobuf.StringValue) returns (ElectorVote) { + option (aelf.is_view) = true; + } + + // Gets the voter information including the active and withdrawn voting records. + rpc GetElectorVoteWithAllRecords (google.protobuf.StringValue) returns (ElectorVote) { + option (aelf.is_view) = true; + } + + // Get voting information for candidate according to the public key of the candidate. + rpc GetCandidateVote (google.protobuf.StringValue) returns (CandidateVote) { + option (aelf.is_view) = true; + } + + // Get voting information for candidate according to the public key of the candidate. + rpc GetCandidateVoteWithRecords (google.protobuf.StringValue) returns (CandidateVote) { + option (aelf.is_view) = true; + } + + // Get voting information for candidate according to the public key of the candidate + // (including the active and withdrawn voting records). + rpc GetCandidateVoteWithAllRecords (google.protobuf.StringValue) returns (CandidateVote) { + option (aelf.is_view) = true; + } + + // Get the total number of voters. + rpc GetVotersCount (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + + // Get the total number of vote token. + rpc GetVotesAmount (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + + // Get candidate information according to the index and length. + rpc GetPageableCandidateInformation (PageInformation) returns (GetPageableCandidateInformationOutput) { + option (aelf.is_view) = true; + } + + // Get the voting item id of miner election. + rpc GetMinerElectionVotingItemId (google.protobuf.Empty) returns (aelf.Hash) { + option (aelf.is_view) = true; + } + + // Get the data center ranking list. + rpc GetDataCenterRankingList (google.protobuf.Empty) returns (DataCenterRankingList) { + option (aelf.is_view) = true; + } + + // Get the weight of vote interest. + rpc GetVoteWeightSetting (google.protobuf.Empty) returns (VoteWeightInterestList) { + option (aelf.is_view) = true; + } + + // Get the weight of lock time and votes in the calculation of voting weight. + rpc GetVoteWeightProportion (google.protobuf.Empty) returns (VoteWeightProportion) { + option (aelf.is_view) = true; + } + + // Used to calculate the bonus weights that users can get by voting. + rpc GetCalculateVoteWeight (VoteInformation) returns (google.protobuf.Int64Value){ + option (aelf.is_view) = true; + } + + // Query the controller for the weight of vote interest. + rpc GetVoteWeightInterestController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } + + // Inspect the evil nodes included in the specified miners and return to the replacement node. + rpc GetMinerReplacementInformation (GetMinerReplacementInformationInput) returns (MinerReplacementInformation) { + option (aelf.is_view) = true; + } + + // Query candidate admin. + rpc GetCandidateAdmin (google.protobuf.StringValue) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Query the newest pubkey of an old pubkey. + rpc GetNewestPubkey (google.protobuf.StringValue) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + + // Query the old pubkey. + rpc GetReplacedPubkey (google.protobuf.StringValue) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + + rpc GetSponsor (google.protobuf.StringValue) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + rpc GetManagedPubkeys (aelf.Address) returns (PubkeyList) { + option (aelf.is_view) = true; + } +} + +message InitialElectionContractInput { + // Minimum number of seconds for locking. + int64 minimum_lock_time = 1; + // Maximum number of seconds for locking. + int64 maximum_lock_time = 2; + // The current miner list. + repeated string miner_list = 3; + // The number of seconds per term. + int64 time_each_term = 4; + // The interval second that increases the number of miners. + int64 miner_increase_interval = 5; +} + +message GetTermSnapshotInput { + // The term number. + int64 term_number = 1; +} + +message UpdateCandidateInformationInput { + // The candidate public key. + string pubkey = 1; + // The number of blocks recently produced. + int64 recently_produced_blocks = 2; + // The number of time slots recently missed. + int64 recently_missed_time_slots = 3; + // Is it a evil node. If true will remove the candidate. + bool is_evil_node = 4; +} + +message UpdateMultipleCandidateInformationInput { + // The candidate information to update. + repeated UpdateCandidateInformationInput value = 1; +} + +message TakeElectionSnapshotInput { + // The term number to take snapshot. + int64 term_number = 1; + // The number of mined blocks of this term. + int64 mined_blocks = 2; + // The end round number of this term. + int64 round_number = 3; +} + +message VoteMinerInput { + // The candidate public key. + string candidate_pubkey = 1; + // The amount token to vote. + int64 amount = 2; + // The end timestamp of this vote. + google.protobuf.Timestamp end_timestamp = 3; + // Used to generate vote id. + aelf.Hash token = 4; +} + +message ChangeVotingOptionInput { + // The vote id to change. + aelf.Hash vote_id = 1; + // The new candidate public key. + string candidate_pubkey = 2; +} + +message UpdateTermNumberInput { + // The term number. + int64 term_number = 1; +} + +message GetElectionResultInput { + // The term number. + int64 term_number = 1; +} + +message ElectionResult { + // The term number + int64 term_number = 1; + // The election result, candidates’ public key -> number of votes. + map results = 2; + // Whether an election is currently being held. + bool is_active = 3; +} + +message ElectorVote { + // The active voting record ids. + repeated aelf.Hash active_voting_record_ids = 1; + // The voting record ids that were withdrawn. + repeated aelf.Hash withdrawn_voting_record_ids = 2; + // The total number of active votes. + int64 active_voted_votes_amount = 3; + // The total number of votes (including the number of votes withdrawn). + int64 all_voted_votes_amount = 4; + // The active voting records. + repeated ElectionVotingRecord active_voting_records = 5; + // The voting records that were withdrawn. + repeated ElectionVotingRecord withdrawn_votes_records = 6; + // Public key for voter. + bytes pubkey = 7; +} + +message CandidateVote { + // The active voting record ids obtained. + repeated aelf.Hash obtained_active_voting_record_ids = 1; + // The active voting record ids that were withdrawn. + repeated aelf.Hash obtained_withdrawn_voting_record_ids = 2; + // The total number of active votes obtained. + int64 obtained_active_voted_votes_amount = 3; + // The total number of votes obtained. + int64 all_obtained_voted_votes_amount = 4; + // The active voting records. + repeated ElectionVotingRecord obtained_active_voting_records = 5; + // The voting records that were withdrawn. + repeated ElectionVotingRecord obtained_withdrawn_votes_records = 6; + // Public key for candidate. + bytes pubkey = 7; +} + +message CandidateInformation { + // Candidate’s public key. + string pubkey = 1; + // The number of terms that the candidate is elected. + repeated int64 terms = 2; + // The number of blocks the candidate has produced. + int64 produced_blocks = 3; + // The time slot for which the candidate failed to produce blocks. + int64 missed_time_slots = 4; + // The count of continual appointment. + int64 continual_appointment_count = 5; + // The transaction id when the candidate announced. + aelf.Hash announcement_transaction_id = 6; + // Indicate whether the candidate can be elected in the current term. + bool is_current_candidate = 7; +} + +message CandidateDetail { + // The candidate information. + CandidateInformation candidate_information = 1; + // The number of votes a candidate has obtained. + int64 obtained_votes_amount = 2; +} + +message ElectionVotingRecord { + // The address of voter. + aelf.Address voter = 1; + // The public key of candidate. + string candidate = 2; + // Amount of voting. + int64 amount = 3; + // The term number of voting. + int64 term_number = 4; + // The vote id. + aelf.Hash vote_id = 5; + // Vote lock time. + int64 lock_time = 6; + // The unlock timestamp. + google.protobuf.Timestamp unlock_timestamp = 7; + // The withdraw timestamp. + google.protobuf.Timestamp withdraw_timestamp = 8; + // The vote timestamp. + google.protobuf.Timestamp vote_timestamp = 9; + // Indicates if the vote has been withdrawn. + bool is_withdrawn = 10; + // Vote weight for sharing bonus. + int64 weight = 11; + // Whether vote others. + bool is_change_target = 12; +} + +message PageInformation { + // The start index. + int32 start = 1; + // The number of records. + int32 length = 2; +} + +message PubkeyList { + // Candidates’ public keys + repeated bytes value = 1; +} + +message TermSnapshot { + // The end round number of this term. + int64 end_round_number = 1; + // The number of blocks mined in this term. + int64 mined_blocks = 2; + // The election result, candidates’ public key -> number of votes. + map election_result = 3; +} + +message UpdateMinersCountInput { + // The count of miner. + int32 miners_count = 1; +} + +message GetPageableCandidateInformationOutput { + // The details of the candidates. + repeated CandidateDetail value = 1; +} + +message SetTreasurySchemeIdsInput { + // The scheme id of treasury reward. + aelf.Hash treasury_hash = 1; + // The scheme id of welfare reward. + aelf.Hash welfare_hash = 2; + // The scheme id of subsidy reward. + aelf.Hash subsidy_hash = 3; + // The scheme id of welcome reward. + aelf.Hash welcome_hash = 4; + // The scheme id of flexible reward. + aelf.Hash flexible_hash = 5; +} + +message DataCenterRankingList { + // The top n * 5 candidates with vote amount, candidate public key -> vote amount. + map data_centers = 1; +} + +message VoteWeightInterest{ + // Number of days locked. + int32 day = 1; + // Locked interest. + int32 interest = 2; + int32 capital = 3; +} + +message VoteWeightInterestList { + // The weight of vote interest. + repeated VoteWeightInterest vote_weight_interest_infos = 1; +} + +message VoteWeightProportion { + // The weight of lock time. + int32 time_proportion = 1; + // The weight of the votes cast. + int32 amount_proportion = 2; +} + +message VoteInformation{ + // Amount of voting. + int64 amount = 1; + // Vote lock time. + int64 lock_time = 2; +} + +message GetMinerReplacementInformationInput { + // The current miner list to inspect. + repeated string current_miner_list = 1; +} + +message MinerReplacementInformation { + // The alternative candidate public keys. + repeated string alternative_candidate_pubkeys = 1; + // The evil miner public keys. + repeated string evil_miner_pubkeys = 2; +} + +message ReplaceCandidatePubkeyInput { + string old_pubkey = 1; + string new_pubkey = 2; +} + +message SetCandidateAdminInput { + string pubkey = 1; + aelf.Address admin = 2; +} + +message AssistWithdrawInput { + string pubkey = 1; + aelf.Hash vote_id = 2; +} + +message AnnounceElectionForInput { + string pubkey = 1; + aelf.Address admin = 2; +} + +message SetProfitsReceiverInput { + aelf.Address candidate_address = 1; + aelf.Address receiver_address = 2; +} + +message FixTotalWeightsInput { + repeated aelf.Hash vote_ids = 1; + +} + +message EvilMinerDetected { + option (aelf.is_event) = true; + // The public key of evil miner. + string pubkey = 1; +} + +message CandidatePubkeyReplaced { + option (aelf.is_event) = true; + string old_pubkey = 1; + string new_pubkey = 2; +} \ No newline at end of file diff --git a/protobuf/election_contract_impl.proto b/protobuf/election_contract_impl.proto new file mode 100755 index 00000000..03ad7597 --- /dev/null +++ b/protobuf/election_contract_impl.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package ElectionImpl; + +import "acs1.proto"; +import "election_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Election"; + +service ElectionContractImpl { + + option (aelf.csharp_state) = "AElf.Contracts.Election.ElectionContractState"; + + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "election_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/execution.proto b/protobuf/execution.proto new file mode 100755 index 00000000..b0aff51e --- /dev/null +++ b/protobuf/execution.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package aelf; + +option csharp_namespace = "AElf.Kernel.SmartContractExecution"; + +message ExecutionObserverThreshold +{ + int32 execution_call_threshold = 1; + int32 execution_branch_threshold = 2; +} \ No newline at end of file diff --git a/protobuf/forest_contract.proto b/protobuf/forest_contract.proto new file mode 100755 index 00000000..2a857f8c --- /dev/null +++ b/protobuf/forest_contract.proto @@ -0,0 +1,690 @@ +/** + * Forest Contract. + */ +syntax = "proto3"; + +package Forest; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "acs1.proto"; +import "transaction_fee.proto"; +import "authority_info.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "Forest"; + +service ForestContract { + option (aelf.csharp_state) = "Forest.ForestContractState"; + option (aelf.base) = "acs1.proto"; + + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + + // For Sellers. + rpc ListWithFixedPrice (ListWithFixedPriceInput) returns (google.protobuf.Empty) { + } + rpc ListWithEnglishAuction (ListWithEnglishAuctionInput) returns (google.protobuf.Empty) { + } + rpc ListWithDutchAuction (ListWithDutchAuctionInput) returns (google.protobuf.Empty) { + } + rpc ListForFree(ListForFreeInput) returns (google.protobuf.Empty){ + } + rpc Deal (DealInput) returns (google.protobuf.Empty) { + } + rpc Delist (DelistInput) returns (google.protobuf.Empty) { + } + + // For Buyers. + rpc MakeOffer (MakeOfferInput) returns (google.protobuf.Empty) { + } + rpc CancelOffer (CancelOfferInput) returns (google.protobuf.Empty) { + } + + // For Creators. + rpc SetRoyalty (SetRoyaltyInput) returns (google.protobuf.Empty) { + } + rpc SetTokenWhiteList (SetTokenWhiteListInput) returns (google.protobuf.Empty) { + } + rpc SetCustomizeInfo (CustomizeInfo) returns (google.protobuf.Empty) { + } + rpc StakeForRequests (StakeForRequestsInput) returns (google.protobuf.Empty) { + } + rpc WithdrawStakingTokens (WithdrawStakingTokensInput) returns (google.protobuf.Empty) { + } + rpc HandleRequest (HandleRequestInput) returns (google.protobuf.Empty) { + } + rpc ClaimRemainDeposit (ClaimRemainDepositInput) returns (google.protobuf.Empty) { + } + + // For Admin. + rpc SetServiceFee (SetServiceFeeInput) returns (google.protobuf.Empty) { + } + rpc SetGlobalTokenWhiteList (StringList) returns (google.protobuf.Empty) { + } + rpc SetWhitelistContract(aelf.Address) returns (google.protobuf.Empty){ + } + + // For badges owners. + rpc MintBadge (MintBadgeInput) returns (google.protobuf.Empty) { + } + + // Views. + rpc GetListedNFTInfoList (GetListedNFTInfoListInput) returns (ListedNFTInfoList) { + option (aelf.is_view) = true; + } + rpc GetWhitelistId (GetWhitelistIdInput) returns (GetWhitelistIdOutput) { + option (aelf.is_view) = true; + } + + rpc GetOfferAddressList (GetAddressListInput) returns (AddressList) { + option (aelf.is_view) = true; + } + rpc GetOfferList (GetOfferListInput) returns (OfferList) { + option (aelf.is_view) = true; + } + rpc GetBidAddressList (GetAddressListInput) returns (AddressList) { + option (aelf.is_view) = true; + } + rpc GetBid (GetBidInput) returns (Bid) { + option (aelf.is_view) = true; + } + rpc GetBidList (GetBidListInput) returns (BidList) { + option (aelf.is_view) = true; + } + rpc GetCustomizeInfo (google.protobuf.StringValue) returns (CustomizeInfo) { + option (aelf.is_view) = true; + } + rpc GetRequestInfo (GetRequestInfoInput) returns (RequestInfo) { + option (aelf.is_view) = true; + } + rpc GetEnglishAuctionInfo (GetEnglishAuctionInfoInput) returns (EnglishAuctionInfo) { + option (aelf.is_view) = true; + } + rpc GetDutchAuctionInfo (GetDutchAuctionInfoInput) returns (DutchAuctionInfo) { + option (aelf.is_view) = true; + } + rpc GetTokenWhiteList (google.protobuf.StringValue) returns (StringList) { + option (aelf.is_view) = true; + } + rpc GetGlobalTokenWhiteList (google.protobuf.Empty) returns (StringList) { + option (aelf.is_view) = true; + } + rpc GetStakingTokens (google.protobuf.StringValue) returns (Price) { + option (aelf.is_view) = true; + } + rpc GetRoyalty (GetRoyaltyInput) returns (RoyaltyInfo) { + option (aelf.is_view) = true; + } + rpc GetServiceFeeInfo (google.protobuf.Empty) returns (ServiceFeeInfo) { + option (aelf.is_view) = true; + } +} + +// Structs. + +message ListedNFTInfo { + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + int64 quantity = 4; + ListType list_type = 5; + Price price = 6; + ListDuration duration = 7; +} + +message ListedNFTInfoList { + repeated ListedNFTInfo value = 1; +} + + +message StringList { + repeated string value = 1; +} + +message AddressList { + repeated aelf.Address value = 1; +} + +message CustomizeInfo { + string symbol = 1; + int32 deposit_rate = 2; + Price price = 3; + int64 work_hours = 4; + int64 white_list_hours = 5; + int64 staking_amount = 6; + repeated int64 reserved_token_ids = 7; +} + +message Price { + string symbol = 1; + int64 amount = 2; + int64 token_id = 3;// Only valid if the purchase symbol is a nft protocol. +} + +message Offer { + aelf.Address from = 1; + aelf.Address to = 2; + Price price = 3; + google.protobuf.Timestamp expire_time = 4; + int64 quantity = 5; +} + +message OfferList { + repeated Offer value = 1; +} + +message Bid { + aelf.Address from = 1; + aelf.Address to = 2; + Price price = 3; + google.protobuf.Timestamp expire_time = 4; +} + +message BidList { + repeated Bid value = 1; +} + +enum ListType { + NOT_LISTED = 0; + FIXED_PRICE = 1; + ENGLISH_AUCTION = 2; + DUTCH_AUCTION = 3; +} + +message ListDuration { + google.protobuf.Timestamp start_time = 1; + google.protobuf.Timestamp public_time = 2; + int64 duration_hours = 3; +} + +message DealInfo { + string symbol = 1; + int64 token_id = 2; + int64 quantity = 3; + Price price = 4; + aelf.Address origin_owner = 5; + aelf.Address new_owner = 6; + google.protobuf.Timestamp deal_time = 7; +} + +message RequestInfo { + string symbol = 1; + int32 deposit_rate = 2; + Price price = 3; + int64 work_hours = 4; + int64 white_list_hours = 5; + int64 token_id = 6; + bool is_confirmed = 7; + google.protobuf.Timestamp expire_time = 8; + google.protobuf.Timestamp white_list_due_time = 9; + aelf.Address requester = 10; + google.protobuf.Timestamp confirm_time = 11; + google.protobuf.Timestamp list_time = 12; + int64 work_hours_from_customize_info = 13; +} + +message EnglishAuctionInfo { + string symbol = 1; + int64 token_id = 2; + int64 starting_price = 3; + string purchase_symbol = 4; + ListDuration duration = 5; + aelf.Address owner = 6; + int64 earnest_money = 7; + int64 deal_price = 8; + aelf.Address deal_to = 9; +} + +message DutchAuctionInfo { + string symbol = 1; + int64 token_id = 2; + int64 starting_price = 3; + int64 ending_price = 4; + string purchase_symbol = 5; + ListDuration duration = 6; + aelf.Address owner = 7; +} + +message CertainNFTRoyaltyInfo { + bool is_manually_set = 1; + int32 royalty = 2; +} + +message ServiceFeeInfo { + int32 service_fee_rate = 1; + aelf.Address service_fee_receiver = 2; +} + +message PriceTagInfo{ + string tag_name = 1; + Price price = 2; +} + +message WhitelistInfo { + PriceTagInfo price_tag = 1; + AddressList address_list = 2; +} + +message WhitelistInfoList{ + repeated WhitelistInfo whitelists = 1; +} + +// Inputs. + +message InitializeInput { + aelf.Address nft_contract_address = 1; + aelf.Address admin_address = 2; + int32 service_fee_rate = 3; + aelf.Address service_fee_receiver = 4; + int64 service_fee = 5; +} + +message ListWithFixedPriceInput { + string symbol = 1; + int64 token_id = 2; + Price price = 3; + int64 quantity = 4; + ListDuration duration = 5; + WhitelistInfoList whitelists = 6; + bool is_merge_to_previous_listed_info = 7; + bool is_whitelist_available = 8; +} + +message ListForFreeInput{ + string symbol = 1; + int64 token_id = 2; + Price price = 3; + int64 quantity = 4; + ListDuration duration = 5; +} + +message ListWithEnglishAuctionInput { + string symbol = 1; + int64 token_id = 2; + int64 starting_price = 3; + string purchase_symbol = 4; + ListDuration duration = 5; + int64 earnest_money = 6; +} + +message ListWithDutchAuctionInput { + string symbol = 1; + int64 token_id = 2; + int64 starting_price = 3; + int64 ending_price = 4; + string purchase_symbol = 5; + ListDuration duration = 6; +} + +message DelistInput { + string symbol = 1; + Price price = 2; + int64 token_id = 3; + int64 quantity = 4; +} + +message MakeOfferInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_to = 3; + int64 quantity = 4; + Price price = 5; + google.protobuf.Timestamp expire_time = 6; +} + +message MintBadgeInput { + string symbol = 1; + int64 token_id = 2; +} + +message CancelOfferInput { + string symbol = 1; + int64 token_id = 2; + Int32List index_list = 3; + aelf.Address offer_from = 4; + bool is_cancel_bid = 5; +} + +message Int32List { + repeated int32 value = 1; +} + +message DealInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_from = 3; + Price price = 4; + int64 quantity = 5; +} + +message SetServiceFeeInput { + int32 service_fee_rate = 1; + aelf.Address service_fee_receiver = 2; +} + +message NFTList { + repeated aelf.Hash value = 1; +} + +message SetRoyaltyInput { + string symbol = 1; + int64 token_id = 2; + int32 royalty = 3; + aelf.Address royalty_fee_receiver = 4; +} + +message SetTokenWhiteListInput { + string symbol = 1; + StringList token_white_list = 2; +} + +message GetListedNFTInfoListInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; +} + +message GetWhitelistIdInput{ + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; +} + +message GetWhitelistIdOutput{ + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; +} + +message GetAddressListInput { + string symbol = 1; + int64 token_id = 2; +} + +message GetOfferListInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address address = 3; +} + +message HandleRequestInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address requester = 3; + bool is_confirm = 4; +} + +message StakeForRequestsInput { + string symbol = 1; + int64 staking_amount = 2; +} + +message WithdrawStakingTokensInput { + string symbol = 1; + int64 withdraw_amount = 2; +} + +message GetRequestInfoInput { + string symbol = 1; + int64 token_id = 2; +} + +message GetWhiteListAddressPriceListInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; +} + +message GetEnglishAuctionInfoInput { + string symbol = 1; + int64 token_id = 2; +} + +message GetDutchAuctionInfoInput { + string symbol = 1; + int64 token_id = 2; +} + +message GetBidInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address address = 3; +} + +message GetBidListInput { + string symbol = 1; + int64 token_id = 2; +} + +message ClaimRemainDepositInput { + string symbol = 1; + int64 token_id = 2; +} + +message GetRoyaltyInput { + string symbol = 1; + int64 token_id = 2; +} + +message RoyaltyInfo { + int32 royalty = 1; + aelf.Address royalty_fee_receiver = 2; +} + +// Events + +message FixedPriceNFTListed { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + int64 quantity = 4; + Price price = 5; + ListDuration duration = 6; + bool is_merged_to_previous_listed_info = 7; + aelf.Hash whitelist_id = 8; +} + +message EnglishAuctionNFTListed { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + string purchase_symbol = 4; + int64 starting_price = 5; + ListDuration duration = 6; + int64 earnest_money = 7; +} + +message DutchAuctionNFTListed { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + string purchase_symbol = 4; + int64 starting_price = 5; + int64 ending_price = 6; + ListDuration duration = 7; +} + +message NFTDelisted { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + int64 quantity = 4; +} + +message TokenWhiteListChanged { + option (aelf.is_event) = true; + string symbol = 1; + StringList token_white_list = 2; +} + +message GlobalTokenWhiteListChanged { + option (aelf.is_event) = true; + StringList token_white_list = 1; +} + +message OfferMade { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_from = 3; + aelf.Address offer_to = 4; + Price price = 5; + int64 quantity = 6; + google.protobuf.Timestamp expire_time = 7; +} + +message BidPlaced { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_from = 3; + aelf.Address offer_to = 4; + Price price = 5; + google.protobuf.Timestamp expire_time = 7; +} + +message OfferCanceled { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_from = 3; + aelf.Address offer_to = 4; + Int32List index_list = 5; +} + +message BidCanceled { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address bid_from = 3; + aelf.Address bid_to = 4; +} + +message CustomizeInfoSet { + option (aelf.is_event) = true; + string symbol = 1; + int32 deposit_rate = 2; + Price price = 3; + int64 work_hours = 4; + int64 white_list_hours = 5; + int64 staking_amount = 6; +} + +message NewNFTRequested { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address requester = 3; + Price price = 4; + int64 expected_work_hours = 5; +} + +message NFTRequestCancelled { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address requester = 3; +} + +message NewNFTRequestConfirmed { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address requester = 3; + Price price = 4; + int64 confirmed_work_hours = 5; +} + +message StakingAmountChanged { + option (aelf.is_event) = true; + string symbol = 1; + int64 staking_amount = 2; +} + +message NewNFTRequestRejected { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address requester = 3; +} + +message Sold { + option (aelf.is_event) = true; + aelf.Address nft_from = 1; + aelf.Address nft_to = 2; + string nft_symbol = 3; + int64 nft_token_id = 4; + int64 nft_quantity = 5; + string purchase_symbol = 6; + int64 purchase_amount = 7; + int64 purchase_token_id = 8; +} + +message OfferAdded { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_from = 3; + aelf.Address offer_to = 4; + Price price = 5; + int64 quantity = 6; + google.protobuf.Timestamp expire_time = 7; +} + +message OfferChanged { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_from = 3; + aelf.Address offer_to = 4; + Price price = 5; + int64 quantity = 6; + google.protobuf.Timestamp expire_time = 7; +} + +message OfferRemoved { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address offer_from = 3; + aelf.Address offer_to = 4; + google.protobuf.Timestamp expire_time = 5; +} + +message ListedNFTAdded { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + int64 quantity = 4; + Price price = 5; + ListDuration duration = 6; + aelf.Hash whitelist_id = 7; +} + +message ListedNFTChanged { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + int64 quantity = 4; + Price price = 5; + ListDuration duration = 6; + ListDuration previous_duration = 7; + aelf.Hash whitelist_id = 8; +} + +message ListedNFTRemoved { + option (aelf.is_event) = true; + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + ListDuration duration = 4; + Price price = 5; +} \ No newline at end of file diff --git a/protobuf/game_store_contract.proto b/protobuf/game_store_contract.proto new file mode 100755 index 00000000..9118bda3 --- /dev/null +++ b/protobuf/game_store_contract.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "GameStoreContract"; + +service GameStoreContract { + + option (aelf.csharp_state) = "GameStoreContractState"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { } + + // 添加游戏 + rpc AddGame (GameInfo) returns (google.protobuf.Empty) { } + + // 购买游戏 + rpc Buy (google.protobuf.StringValue) returns (google.protobuf.Empty) { } + + // 获取当前所有游戏 + rpc GetTotalGameList (google.protobuf.Empty) returns (GameList) { + option (aelf.is_view) = true; + } + + // 获取玩家拥有的游戏 + rpc GetOwnGameList (aelf.Address) returns (GameList) { + option (aelf.is_view) = true; + } +} + +message GameInfo { + string name = 1; + string description = 2; + int64 price = 3; + google.protobuf.Timestamp time = 4; +} + +message GameList { + repeated GameInfo value = 1; +} + +message StringList { + repeated string value = 1; +} + + +// Events + +message GameAdded { + option (aelf.is_event) = true; + string name = 1; + string description = 2; + int64 price = 3; + google.protobuf.Timestamp time = 4; +} \ No newline at end of file diff --git a/protobuf/greeter_contract.proto b/protobuf/greeter_contract.proto new file mode 100755 index 00000000..878870b3 --- /dev/null +++ b/protobuf/greeter_contract.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "aelf/options.proto"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.GreeterContract"; + +service GreeterContract { + option (aelf.csharp_state) = "AElf.Contracts.GreeterContract.GreeterContractState"; + + // Actions + rpc Greet (google.protobuf.Empty) returns (google.protobuf.StringValue) { } + rpc GreetTo (google.protobuf.StringValue) returns (GreetToOutput) { } + + // Views + rpc GetGreetedList (google.protobuf.Empty) returns (GreetedList) { + option (aelf.is_view) = true; + } +} + +message GreetToOutput { + string name = 1; + google.protobuf.Timestamp greet_time = 2; +} + +message GreetedList { + repeated string value = 1; +} \ No newline at end of file diff --git a/protobuf/kernel.proto b/protobuf/kernel.proto new file mode 100755 index 00000000..8e1eb982 --- /dev/null +++ b/protobuf/kernel.proto @@ -0,0 +1,183 @@ +syntax = "proto3"; + +package aelf; + +import "google/protobuf/timestamp.proto"; +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Kernel"; + +enum TransactionStatus { + UNKNOWN_TRANSACTION_STATUS = 0; + TRANSACTION_EXECUTING = 1; + TRANSACTION_EXECUTED = 2; +} + +message StateValue { + bytes current_value = 1; + bytes original_value = 2; +} + +message StateChange { + StatePath state_path = 1; + StateValue state_value = 2; +} + +// For failed transactions, its status follows the Min of +// its own status and its inline transactions' +enum ExecutionStatus { + UNDEFINED = 0; + // Successful => + EXECUTED = 1; + + // Failed => + // Infrastructure reasons + CANCELED = -1; + SYSTEM_ERROR = -2; + + // Contract reasons + CONTRACT_ERROR = -10; + EXCEEDED_MAX_CALL_DEPTH = -11; + + // Pre-failed + PREFAILED = -99; + + // Post-failed + POSTFAILED = -199; +} + +message TransactionTrace { + Hash transaction_id = 1; + bytes return_value = 2; + string error = 3; + repeated Transaction pre_transactions = 4; + repeated TransactionTrace pre_traces = 5; + repeated Transaction inline_transactions = 6; + repeated TransactionTrace inline_traces = 7; + repeated Transaction post_transactions = 8; + repeated TransactionTrace post_traces = 9; + repeated LogEvent logs = 10; + int64 elapsed = 11; + ExecutionStatus execution_status = 12; + TransactionExecutingStateSet state_set = 13; +} + +message ExecutionReturnSet { + Hash transaction_id = 1; + TransactionResultStatus status = 2; + map state_changes = 3; + map state_accesses = 4; + bytes bloom = 5; + bytes return_value = 6; + map state_deletes = 7; + TransactionResult transaction_result = 8; +} + +message BlockHeaderList { + repeated BlockHeader headers = 1; +} + +message BlockHeader { + int32 version = 1; + int32 chain_id = 2; + Hash previous_block_hash = 3; + Hash merkle_tree_root_of_transactions = 4; + Hash merkle_tree_root_of_world_state = 5; + bytes bloom = 6; + int64 height = 7; + map extra_data = 8; + google.protobuf.Timestamp time = 9; + Hash merkle_tree_root_of_transaction_status = 10; + bytes signer_pubkey = 9999; + bytes signature = 10000; +} + +message BlockBody { + repeated Hash transaction_ids = 1; +} + +message Block { + BlockHeader header = 1; + BlockBody body = 2; +} + +message VersionedState { + string key = 1; + bytes value = 2; + int64 block_height = 3; + Hash block_hash = 4; + Hash origin_block_hash = 5; +} + +message BlockStateSet { + Hash block_hash = 1; + Hash previous_hash = 2; + int64 block_height = 3; + map changes = 4; + repeated string deletes = 5; + map block_executed_data = 6; +} + +enum ChainStateMergingStatus { + COMMON = 0; + MERGING = 1; + MERGED = 2; +} + +message ChainStateInfo { + int32 chain_id = 1; + Hash block_hash = 2; + int64 block_height = 3; + Hash merging_block_hash = 4; + ChainStateMergingStatus status = 5; +} + +enum ChainBlockLinkExecutionStatus { + EXECUTION_NONE = 0; + EXECUTION_SUCCESS = 1; + EXECUTION_FAILED = 2; +} + +message ChainBlockLink { + Hash block_hash = 1; + int64 height = 2; + Hash previous_block_hash = 3; + ChainBlockLinkExecutionStatus execution_status = 4; + bool is_irreversible_block = 5; + bool is_linked = 6; + bool is_light_block = 7; //no block body +} + +message Chain { + int32 id = 1; + Hash genesis_block_hash = 2; + Hash longest_chain_hash = 3; + int64 longest_chain_height = 4; + map branches = 5; + map not_linked_blocks = 6; + Hash last_irreversible_block_hash = 7; + int64 last_irreversible_block_height = 8; + Hash best_chain_hash = 9; + int64 best_chain_height = 10; +} + +message ChainBlockIndex { + Hash block_hash = 1; +} + +message BlockIndex{ + Hash block_hash = 1; + int64 block_height = 2; +} + +message TransactionBlockIndex { + Hash block_hash = 1; + int64 block_height = 2; + repeated BlockIndex previous_execution_block_index_list = 3; +} + +message SmartContractAddress{ + Address address = 1; + Hash block_hash = 2; + int64 block_height = 3; +} \ No newline at end of file diff --git a/protobuf/lottery_contract.proto b/protobuf/lottery_contract.proto new file mode 100755 index 00000000..97015da0 --- /dev/null +++ b/protobuf/lottery_contract.proto @@ -0,0 +1,159 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "AElf.Contracts.LotteryContract"; + +service LotteryContract { + option (aelf.csharp_state) = "AElf.Contracts.LotteryContract.LotteryContractState"; + + // Basic Actions + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc Buy(google.protobuf.Int64Value) returns (BoughtLotteriesInformation) { + } + rpc Draw (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + rpc PrepareDraw (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc TakeReward (TakeRewardInput) returns (google.protobuf.Empty) { + } + + // Management Actions + rpc ResetMaximumBuyAmount (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + rpc ResetPrice (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + rpc ResetDrawingLag (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + rpc AddRewardList (RewardList) returns (google.protobuf.Empty) { + } + rpc SetRewardListForOnePeriod (RewardsInfo) returns (google.protobuf.Empty) { + } + rpc Suspend (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc Recover (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + rpc GetRewardResult (google.protobuf.Int64Value) returns (GetRewardResultOutput) { + option (aelf.is_view) = true; + } + rpc GetBoughtLotteries (GetBoughtLotteriesInput) returns (GetBoughtLotteriesOutput) { + option (aelf.is_view) = true; + } + rpc GetSales (google.protobuf.Int64Value) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetMaximumBuyAmount (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetPrice (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetDrawingLag (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetCurrentPeriodNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetPeriod (google.protobuf.Int64Value) returns (PeriodBody) { + option (aelf.is_view) = true; + } + rpc GetCurrentPeriod (google.protobuf.Empty) returns (PeriodBody) { + option (aelf.is_view) = true; + } + rpc GetLottery (google.protobuf.Int64Value) returns (Lottery) { + option (aelf.is_view) = true; + } + rpc GetRewardList (google.protobuf.Empty) returns (RewardList) { + option (aelf.is_view) = true; + } + rpc GetRewardName (google.protobuf.StringValue) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + rpc GetBoughtLotteriesCount (aelf.Address) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetAllLotteriesCount (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetNoRewardLotteriesCount (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } +} + +message InitializeInput { + string token_symbol = 1; + int64 maximum_amount = 2; + int64 price = 3; + int64 drawing_lag = 4; +} + +message BoughtLotteriesInformation { + int64 start_id = 1; + int64 amount = 2; +} + +message TakeRewardInput { + int64 lottery_id = 1; + string registration_information = 2; +} + +message Lottery { + int64 id = 1; + aelf.Address owner = 2; + string reward_name = 3; + int64 block = 4; + string registration_information = 5; +} + +message PeriodBody { + int64 block_number = 2; + aelf.Hash random_hash = 3; + repeated int64 reward_ids = 4; + int64 start_id = 5; + map rewards = 6; + google.protobuf.Timestamp supposed_draw_date = 7; +} + +message LotteryList { + repeated int64 ids = 1; +} + +message GetRewardResultInput { + int64 period = 1; +} + +message GetRewardResultOutput { + int64 period = 1; + aelf.Hash random_hash = 2; + repeated Lottery reward_lotteries = 3; +} + +message GetBoughtLotteriesInput { + int64 period = 1; + int32 start_id = 2; + aelf.Address owner = 3; +} + +message GetBoughtLotteriesOutput { + repeated Lottery lotteries = 3; +} + +message RewardList { + map reward_map = 1; +} + +message RewardsInfo { + int64 period = 1; + map rewards = 2; + google.protobuf.Timestamp supposed_draw_date = 3; +} + +message StringList { + repeated string value = 1; +} \ No newline at end of file diff --git a/protobuf/lottery_demo_contract.proto b/protobuf/lottery_demo_contract.proto new file mode 100755 index 00000000..90ed1b95 --- /dev/null +++ b/protobuf/lottery_demo_contract.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; + +option csharp_namespace = "AElf.Contracts.LotteryDemoContract"; + +service LotteryDemoContract { + option (aelf.csharp_state) = "AElf.Contracts.LotteryDemoContract.LotteryDemoContractState"; + + rpc InitializeLotteryDemoContract (InitializeLotteryDemoContractInput) returns (google.protobuf.Empty) { + } + rpc NewPeriod (NewPeriodInput) returns (google.protobuf.Empty) { + } + rpc Bet (Lotteries) returns (google.protobuf.Empty) { + } + rpc TakeReward (aelf.Hash) returns (google.protobuf.Empty) { + } +} + +message InitializeLotteryDemoContractInput { + string token_symbol = 1; +} + +message NewPeriodInput { + sint64 period_number = 1; + aelf.Hash random_number_token = 2; +} + +message Lotteries { + repeated Lottery lottery = 1; + aelf.Address saler_address = 2; // 分销员地址 + sint32 proportion_sale = 3; // 销售分成 [0, 15] default 0 + sint32 proportion_bonus = 4; // 兑奖分成 [0, 5] default 0 + sint32 target_period = 5; // 当前期数,在APP里做 + aelf.Address sender_address = 6; // 用户地址 +} + +message Lottery { + sint32 type = 1; + repeated sint32 value = 2; +} \ No newline at end of file diff --git a/protobuf/metadata.proto b/protobuf/metadata.proto new file mode 100755 index 00000000..2682414a --- /dev/null +++ b/protobuf/metadata.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option csharp_namespace = "AElf.Kernel.SmartContract.Metadata"; + +message DataAccessPath { + enum AccessMode { + UNDEFINED = 0; + READ = 1; + WRITE = 2; + } + repeated string path = 1; + AccessMode mode = 2; +} + +message InlineCall { + repeated string address_path = 1; + string method_name = 2; +} \ No newline at end of file diff --git a/protobuf/my_app_contract.proto b/protobuf/my_app_contract.proto new file mode 100755 index 00000000..f0fe19e1 --- /dev/null +++ b/protobuf/my_app_contract.proto @@ -0,0 +1,41 @@ +/* This files is part of the Hello World smart contract example that is included in Boilerplate. + * It is only the definition of the contract, implementation is located in the "contract" folder + * and tests are located in the "test" folder. + * + * You can use this as a basic template for new contracts. + * + * When building Boilerplate or the contract project located in the "../contract/MyAppContract/" + * protoc (the protobuf compiler) will be invoked and this file will produce a .c.cs file and .g.cs file, in the + * "../contract/MyAppContract/Protobuf/Generated/" folder. + */ + +// the version of the language, use proto3 for contracts +syntax = "proto3"; + +// some core imports for aelf chain types +import "aelf/core.proto"; +import "aelf/options.proto"; + +// import for using the google.protobuf.Empty type. +import "google/protobuf/empty.proto"; + +// the name of the C# namespace in which the contract code will be, +// generated code will also be in this namespace. +option csharp_namespace = "MyAppContract"; + +// the contract definition: a gRPC service definition. +service MyAppContract { + + // the full name of the C# class that will contain the state (here . format). + option (aelf.csharp_state) = "MyAppContract.MyAppContractState"; + + // an action defined as a gRPC service method. + // this action take a google.protobuf.Empty (placeholder for void) as input + // and returns a custom defined type: HelloReturn. + rpc Hello (google.protobuf.Empty) returns (HelloReturn) { } +} + +// a custom message, used as the return type of the Hello action +message HelloReturn { + string Value = 1; +} \ No newline at end of file diff --git a/protobuf/network_types.proto b/protobuf/network_types.proto new file mode 100755 index 00000000..809b4fa1 --- /dev/null +++ b/protobuf/network_types.proto @@ -0,0 +1,109 @@ +syntax = "proto3"; + +option csharp_namespace = "AElf.OS.Network"; + +import "google/protobuf/timestamp.proto"; +import "aelf/core.proto"; +import "kernel.proto"; + +// Messages related to block propagation/syncing + +message BlockAnnouncement { + aelf.Hash block_hash = 1; + int64 block_height = 2; +} + +message BlockWithTransactions { + aelf.BlockHeader header = 1; + repeated aelf.Transaction transactions = 2; +} + +message LibAnnouncement{ + aelf.Hash lib_hash = 1; + int64 lib_height = 2; +} + +message BlocksRequest { + aelf.Hash previous_block_hash = 1; + int32 count = 2; +} + +message BlockList { + repeated BlockWithTransactions blocks = 1; +} + +message BlockRequest { + aelf.Hash hash = 1; +} + +message BlockReply { + string error = 1; + BlockWithTransactions block = 2; +} + +// Messages related to handshaking + +message Handshake { + HandshakeData handshake_data = 1; + bytes signature = 2; + bytes session_id = 3; +} + +message HandshakeData { + int32 chain_id = 1; + int32 version = 2; + int32 listening_port = 3; + bytes pubkey = 4; + aelf.Hash best_chain_hash = 5; + int64 best_chain_height = 6; + aelf.Hash last_irreversible_block_hash = 7; + int64 last_irreversible_block_height = 8; + google.protobuf.Timestamp time = 9; +} + +message HandshakeRequest { + Handshake handshake = 1; +} + +message HandshakeReply { + Handshake handshake = 1; + HandshakeError error = 2; +} + +enum HandshakeError { + HANDSHAKE_OK = 0; + CHAIN_MISMATCH = 1; + PROTOCOL_MISMATCH = 2; + WRONG_SIGNATURE = 3; + REPEATED_CONNECTION = 4; + CONNECTION_REFUSED = 5; + INVALID_CONNECTION = 6; + SIGNATURE_TIMEOUT = 7; +} + +message ConfirmHandshakeRequest { +} + +// Messages related to disconnection + +message DisconnectReason { + Reason why = 1; + enum Reason { + SHUTDOWN = 0; + } +} + +// Messages related to peer discovery + +message NodeInfo { + string endpoint = 1; + bytes pubkey = 2; +} + +message NodeList { + repeated NodeInfo nodes = 1; +} + +message NodesRequest { + int32 max_count = 1; +} \ No newline at end of file diff --git a/protobuf/nft_contract.proto b/protobuf/nft_contract.proto new file mode 100644 index 00000000..063b2bbd --- /dev/null +++ b/protobuf/nft_contract.proto @@ -0,0 +1,505 @@ +/** + * NFT contract. + */ +syntax = "proto3"; + +package nft; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "acs1.proto"; +import "transaction_fee.proto"; +import "authority_info.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.NFT"; + +service NFTContract { + option (aelf.csharp_state) = "AElf.Contracts.NFT.NFTContractState"; + option (aelf.base) = "acs1.proto"; + + // Create a new nft protocol. + rpc Create (CreateInput) returns (google.protobuf.StringValue) { + } + rpc CrossChainCreate (CrossChainCreateInput) returns (google.protobuf.Empty) { + } + // Mint (Issue) an amount of nft. + rpc Mint (MintInput) returns (aelf.Hash) { + } + // Transfer nft to another address. + rpc Transfer (TransferInput) returns (google.protobuf.Empty) { + } + // Transfer nft from one address to another. + rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) { + } + // Approve another address to transfer nft from own account. + rpc Approve (ApproveInput) returns (google.protobuf.Empty) { + } + // De-approve. + rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { + } + // Approve or de-approve another address as the operator of all NFTs of a certain protocol. + rpc ApproveProtocol (ApproveProtocolInput) returns (google.protobuf.Empty) { + } + // Destroy nfts. + rpc Burn (BurnInput) returns (google.protobuf.Empty) { + } + // Lock several nfts and fts to mint one nft. + rpc Assemble (AssembleInput) returns (aelf.Hash) { + } + // Disassemble one assembled nft to get locked nfts and fts back. + rpc Disassemble (DisassembleInput) returns (google.protobuf.Empty) { + } + // Modify metadata of one nft. + rpc Recast (RecastInput) returns (google.protobuf.Empty) { + } + + rpc AddMinters (AddMintersInput) returns (google.protobuf.Empty) { + } + rpc RemoveMinters (RemoveMintersInput) returns (google.protobuf.Empty) { + } + + rpc AddNFTType (AddNFTTypeInput) returns (google.protobuf.Empty) { + } + rpc RemoveNFTType (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + rpc GetNFTProtocolInfo (google.protobuf.StringValue) returns (NFTProtocolInfo) { + option (aelf.is_view) = true; + } + rpc GetNFTInfo (GetNFTInfoInput) returns (NFTInfo) { + option (aelf.is_view) = true; + } + rpc GetNFTInfoByTokenHash (aelf.Hash) returns (NFTInfo) { + option (aelf.is_view) = true; + } + rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } + rpc GetBalanceByTokenHash (GetBalanceByTokenHashInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } + rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + rpc GetAllowanceByTokenHash (GetAllowanceByTokenHashInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + rpc GetMinterList (google.protobuf.StringValue) returns (MinterList) { + option (aelf.is_view) = true; + } + rpc CalculateTokenHash (CalculateTokenHashInput) returns (aelf.Hash) { + option (aelf.is_view) = true; + } + rpc GetNFTTypes (google.protobuf.Empty) returns (NFTTypes) { + option (aelf.is_view) = true; + } + rpc GetOperatorList (GetOperatorListInput) returns (AddressList) { + option (aelf.is_view) = true; + } +} + +// Structs +message NFTTypes { + map value = 1; +} + +// Inputs +message CreateInput { + // The type of this nft protocol. + string nft_type = 1; + // The name of this nft protocol. + string protocol_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The address that created the token. + aelf.Address creator = 4; + // A flag indicating if this token is burnable. + bool is_burnable = 5; + // The chain id of the token. + int32 issue_chain_id = 6; + // The metadata of the token. + Metadata metadata = 7; + // Base Uri. + string base_uri = 8; + // Is token id can be reused. + bool is_token_id_reuse = 9; + // Initial minter list (creator will be added automatically) + MinterList minter_list = 10; +} + +message CrossChainCreateInput { + string symbol = 1; +} + +message TransferInput { + aelf.Address to = 1; + string symbol = 2; + int64 token_id = 3; + string memo = 4; + int64 amount = 5; +} + +message TransferFromInput { + aelf.Address from = 1; + aelf.Address to = 2; + string symbol = 3; + int64 token_id = 4; + string memo = 5; + int64 amount = 6; +} + +message ApproveInput { + aelf.Address spender = 1; + string symbol = 2; + int64 token_id = 3; + int64 amount = 4; +} + +message UnApproveInput { + aelf.Address spender = 1; + string symbol = 2; + int64 token_id = 3; + int64 amount = 4; +} + +message ApproveProtocolInput { + aelf.Address operator = 1; + string symbol = 2; + bool approved = 3; +} + +message AddressList { + repeated aelf.Address value = 1; +} + +message GetOperatorListInput { + string symbol = 1; + aelf.Address owner = 2; +} + +message BurnInput { + string symbol = 1; + int64 token_id = 2; + int64 amount = 3; +} + +message AssembleInput { + string symbol = 1; + aelf.Address owner = 2; + string uri = 3; + string alias = 4; + Metadata metadata = 5; + AssembledNfts assembled_nfts = 6; + AssembledFts assembled_fts = 7; + int64 token_id = 8; +} + +message DisassembleInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; +} + +message MinterList { + repeated aelf.Address value = 1; +} + +message MintInput { + string symbol = 1; + aelf.Address owner = 2; + string uri = 3; + string alias = 4; + Metadata metadata = 5; + int64 quantity = 6; + int64 token_id = 7; +} + +message GetBalanceInput { + aelf.Address owner = 1; + string symbol = 2; + int64 token_id = 3; +} + +message GetBalanceByTokenHashInput { + aelf.Address owner = 1; + aelf.Hash token_hash = 2; +} + +message GetBalanceOutput { + aelf.Address owner = 1; + aelf.Hash token_hash = 2; + int64 balance = 3; +} + +message GetAllowanceInput { + string symbol = 1; + int64 token_id = 2; + aelf.Address owner = 3; + aelf.Address spender = 4; +} + +message GetAllowanceByTokenHashInput { + aelf.Hash token_hash = 1; + aelf.Address owner = 2; + aelf.Address spender = 3; +} + +message GetAllowanceOutput { + aelf.Hash token_hash = 1; + int64 allowance = 2; + aelf.Address owner = 3; + aelf.Address spender = 4; +} + +message CalculateTokenHashInput { + string symbol = 1; + int64 token_id = 2; +} + +message NFTProtocolInfo { + // The symbol of the token. + string symbol = 1; + // The minted number of the token. + int64 supply = 2; + // The total number of the token. + int64 total_supply = 3; + // The address that creat the token. + aelf.Address creator = 4; + // Base Uri. + string base_uri = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // The chain to mint this token. + int32 issue_chain_id = 7; + // The metadata of the token. + Metadata metadata = 8; + // NFT Type. + string nft_type = 9; + // Protocol name, aka token name in MultiToken Contract. + string protocol_name = 10; + // Is token id can be reused. + bool is_token_id_reuse = 11; + int64 issued = 12; +} + +message NFTInfo { + // The symbol of the protocol this nft belongs to. + string symbol = 1; + // The name of the protocol this nft belongs to. + string protocol_name = 2; + // Actually is the order of this token. + int64 token_id = 3; + // The address that creat the base token. + aelf.Address creator = 4; + // The addresses that mint this token. + repeated aelf.Address minters = 5; + // The metadata of the token. + Metadata metadata = 6; + // Minted amount. + int64 quantity = 7; + // Token Uri. + string uri = 8; + // Base Uri. + string base_uri = 9; + // Alias + string alias = 10; + // Is burned. + bool is_burned = 11; + // NFT Type + string nft_type = 12; +} + +enum NFTType { + ANY = 0; + ART = 1; + MUSIC = 2; + DOMAIN_NAMES = 3; + VIRTUAL_WORLDS = 4; + TRADING_CARDS = 5; + COLLECTABLES = 6; + SPORTS = 7; + UTILITY = 8; + BADGES = 9; +} + +message Metadata { + map value = 1; +} + +message AddMintersInput { + MinterList minter_list = 1; + string symbol = 2; +} + +message RemoveMintersInput { + MinterList minter_list = 1; + string symbol = 2; +} + +message GetNFTInfoInput { + string symbol = 1; + int64 token_id = 2; +} + +message RecastInput { + string symbol = 1; + int64 token_id = 2; + string uri = 3; + string alias = 4; + Metadata metadata = 5; +} + +message AssembledNfts { + map value = 1; +} + +message AssembledFts { + map value = 1; +} + +message AddNFTTypeInput { + string full_name = 1; + string short_name = 2; +} + +// Events + +message NFTProtocolCreated { + option (aelf.is_event) = true; + // The symbol of this protocol. + string symbol = 1; + // The name of this protocol. + string protocol_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The address that created the token. + aelf.Address creator = 4; + // A flag indicating if this token is burnable. + bool is_burnable = 5; + // The chain id of the token. + int32 issue_chain_id = 6; + // The metadata of the token. + Metadata metadata = 7; + // Base Uri. + string base_uri = 8; + // Is token id can be reused. + bool is_token_id_reuse = 9; + string nft_type = 10; +} + +message NFTMinted { + option (aelf.is_event) = true; + // The symbol of this protocol. + string symbol = 1; + // The name of this protocol. + string protocol_name = 2; + // Actually is the order of this token. + int64 token_id = 3; + // The address that creat the base token. + aelf.Address creator = 4; + // The address that mint this token. + aelf.Address minter = 5; + // The metadata of the token. + Metadata metadata = 6; + // The current owner of this nft. + aelf.Address owner = 7; + // Token Uri. + string uri = 8; + // Base Uri. + string base_uri = 9; + // Alias + string alias = 10; + // NFT Type + string nft_type = 11; + // Quantity + int64 quantity = 12; + int64 total_quantity = 13; + aelf.Hash token_hash = 14; +} + +message Transferred { + option (aelf.is_event) = true; + aelf.Address from = 1 [(aelf.is_indexed) = true]; + aelf.Address to = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 token_id = 4 [(aelf.is_indexed) = true]; + int64 amount = 5; + string memo = 6; +} + +message Approved { + option (aelf.is_event) = true; + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 token_id = 4 [(aelf.is_indexed) = true]; + int64 amount = 5; +} + +message UnApproved { + option (aelf.is_event) = true; + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 token_id = 4 [(aelf.is_indexed) = true]; + int64 current_allowance = 5; +} + +message Burned { + option (aelf.is_event) = true; + aelf.Address burner = 1 [(aelf.is_indexed) = true]; + string symbol = 2 [(aelf.is_indexed) = true]; + int64 token_id = 3 [(aelf.is_indexed) = true]; + int64 amount = 4; +} + +message Recasted { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + int64 token_id = 2 [(aelf.is_indexed) = true]; + Metadata old_metadata = 3 [(aelf.is_indexed) = true]; + Metadata new_metadata = 4 [(aelf.is_indexed) = true]; + string alias = 5 [(aelf.is_indexed) = true]; + string uri = 6 [(aelf.is_indexed) = true]; +} + +message Assembled { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + int64 token_id = 2 [(aelf.is_indexed) = true]; + AssembledNfts assembled_nfts = 3 [(aelf.is_indexed) = true]; + AssembledFts assembled_fts = 4 [(aelf.is_indexed) = true]; +} + +message Disassembled { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + int64 token_id = 2 [(aelf.is_indexed) = true]; + AssembledNfts disassembled_nfts = 3 [(aelf.is_indexed) = true]; + AssembledFts disassembled_fts = 4 [(aelf.is_indexed) = true]; +} + +message NFTTypeAdded { + option (aelf.is_event) = true; + string full_name = 1; + string short_name = 2; +} + +message NFTTypeRemoved { + option (aelf.is_event) = true; + string short_name = 1; +} + +message MinterListAdded { + option (aelf.is_event) = true; + MinterList minter_list = 1; + string symbol = 2; +} + +message MinterListRemoved { + option (aelf.is_event) = true; + MinterList minter_list = 1; + string symbol = 2; +} \ No newline at end of file diff --git a/protobuf/parallel_execution.proto b/protobuf/parallel_execution.proto new file mode 100755 index 00000000..17287788 --- /dev/null +++ b/protobuf/parallel_execution.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Kernel.SmartContract"; + +message TransactionResourceInfo { + repeated aelf.ScopedStatePath write_paths = 1; + repeated aelf.ScopedStatePath read_paths = 2; + ParallelType parallel_type = 3; + aelf.Hash transaction_id = 4; + aelf.Hash contract_hash = 5; + bool is_nonparallel_contract_code = 6; +} + +enum ParallelType { + PARALLELIZABLE = 0; + NON_PARALLELIZABLE = 1; + INVALID_CONTRACT_ADDRESS = 2; +} + +message NonparallelContractCode{ + aelf.Hash code_hash = 1; +} \ No newline at end of file diff --git a/protobuf/parliament_contract.proto b/protobuf/parliament_contract.proto new file mode 100755 index 00000000..9084e946 --- /dev/null +++ b/protobuf/parliament_contract.proto @@ -0,0 +1,90 @@ +syntax = "proto3"; + +package Parliament; + +import "acs3.proto"; +import public "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.Parliament"; + +service ParliamentContract { + + option (aelf.csharp_state) = "AElf.Contracts.Parliament.ParliamentState"; + + // Actions + rpc Initialize(InitializeInput) returns (google.protobuf.Empty) { + } + rpc CreateOrganization (CreateOrganizationInput) returns (aelf.Address) { + } + rpc ApproveMultiProposals(ProposalIdList) returns (google.protobuf.Empty){ + } + rpc CreateOrganizationBySystemContract(CreateOrganizationBySystemContractInput) returns (aelf.Address){ + } + rpc GetOrganization (aelf.Address) returns (Organization) { + option (aelf.is_view) = true; + } + rpc GetDefaultOrganizationAddress (google.protobuf.Empty) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc ValidateAddressIsParliamentMember(aelf.Address) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } + rpc GetProposerWhiteList(google.protobuf.Empty) returns (acs3.ProposerWhiteList){ + option (aelf.is_view) = true; + } + rpc GetNotVotedPendingProposals(ProposalIdList) returns (ProposalIdList){ + option (aelf.is_view) = true; + } + rpc GetNotVotedProposals(ProposalIdList) returns (ProposalIdList){ + option (aelf.is_view) = true; + } + rpc CalculateOrganizationAddress(CreateOrganizationInput) returns (aelf.Address){ + option (aelf.is_view) = true; + } +} + +message CreateOrganizationInput { + acs3.ProposalReleaseThreshold proposal_release_threshold = 1; + bool proposer_authority_required = 2; + bool parliament_member_proposing_allowed = 3; + aelf.Hash creation_token = 4; +} + +message Organization { + bool proposer_authority_required = 1; + aelf.Address organization_address = 2; + aelf.Hash organization_hash = 3; + acs3.ProposalReleaseThreshold proposal_release_threshold = 4; + bool parliament_member_proposing_allowed = 5; + aelf.Hash creation_token = 6; +} + +message ProposalInfo { + aelf.Hash proposal_id = 1; + string contract_method_name = 2; + aelf.Address to_address = 3; + bytes params = 4; + google.protobuf.Timestamp expired_time = 5; + aelf.Address proposer = 6; + aelf.Address organization_address = 7; + repeated aelf.Address approvals = 8; + repeated aelf.Address rejections = 9; + repeated aelf.Address abstentions = 10; + string proposal_description_url = 11; +} + +message InitializeInput{ + aelf.Address privileged_proposer = 1; + bool proposer_authority_required = 2; +} + +message ProposalIdList{ + repeated aelf.Hash proposal_ids = 1; +} + +message CreateOrganizationBySystemContractInput { + CreateOrganizationInput organization_creation_input = 1; + string organization_address_feedback_method = 2; +} \ No newline at end of file diff --git a/protobuf/parliament_contract_impl.proto b/protobuf/parliament_contract_impl.proto new file mode 100755 index 00000000..ddf51b94 --- /dev/null +++ b/protobuf/parliament_contract_impl.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package ParliamentImpl; + +import "acs1.proto"; +import "acs3.proto"; +import "parliament_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Parliament"; + +service ParliamentContractImpl { + + option (aelf.csharp_state) = "AElf.Contracts.Parliament.ParliamentState"; + option (aelf.base) = "acs3.proto"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "parliament_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/peer_service.proto b/protobuf/peer_service.proto new file mode 100755 index 00000000..8e5e45da --- /dev/null +++ b/protobuf/peer_service.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +option csharp_namespace = "AElf.OS.Network.Grpc"; + +import "network_types.proto"; +import "aelf/core.proto"; + +service PeerService { + + rpc Ping (PingRequest) returns (PongReply) {} + rpc CheckHealth (HealthCheckRequest) returns (HealthCheckReply) {} + + rpc RequestBlock (BlockRequest) returns (BlockReply) {} + rpc RequestBlocks (BlocksRequest) returns (BlockList) {} + + rpc BlockBroadcastStream (stream BlockWithTransactions) returns (VoidReply) {} + + rpc TransactionBroadcastStream (stream aelf.Transaction) returns (VoidReply) {} + rpc AnnouncementBroadcastStream (stream BlockAnnouncement) returns (VoidReply) {} + + rpc LibAnnouncementBroadcastStream (stream LibAnnouncement) returns (VoidReply) {} + + rpc GetNodes (NodesRequest) returns (NodeList) {} + + rpc DoHandshake (HandshakeRequest) returns (HandshakeReply) {} + rpc ConfirmHandshake (ConfirmHandshakeRequest) returns (VoidReply) {} + + rpc Disconnect (DisconnectReason) returns (VoidReply) {} +} + +// **** No reply ***** + +message VoidReply { +} + +message PingRequest { +} + +message PongReply { +} + +message HealthCheckRequest { +} + +message HealthCheckReply { +} + diff --git a/protobuf/profit_contract.proto b/protobuf/profit_contract.proto new file mode 100755 index 00000000..e6b8c594 --- /dev/null +++ b/protobuf/profit_contract.proto @@ -0,0 +1,218 @@ +syntax = "proto3"; + +package Profit; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.Profit"; + +service ProfitContract { + option (aelf.csharp_state) = "AElf.Contracts.Profit.ProfitContractState"; + + // Actions + rpc CreateScheme (CreateSchemeInput) returns (aelf.Hash) { + } + rpc AddBeneficiary (AddBeneficiaryInput) returns (google.protobuf.Empty) { + } + rpc RemoveBeneficiary (RemoveBeneficiaryInput) returns (google.protobuf.Empty) { + } + rpc AddBeneficiaries (AddBeneficiariesInput) returns (google.protobuf.Empty) { + } + rpc RemoveBeneficiaries (RemoveBeneficiariesInput) returns (google.protobuf.Empty) { + } + rpc ContributeProfits (ContributeProfitsInput) returns (google.protobuf.Empty) { + } + rpc ClaimProfits (ClaimProfitsInput) returns (google.protobuf.Empty) { + } + rpc DistributeProfits (DistributeProfitsInput) returns (google.protobuf.Empty) { + } + rpc AddSubScheme (AddSubSchemeInput) returns (google.protobuf.Empty) { + } + rpc RemoveSubScheme (RemoveSubSchemeInput) returns (google.protobuf.Empty) { + } + rpc ResetManager (ResetManagerInput) returns (google.protobuf.Empty) { + } + + // Views + rpc GetManagingSchemeIds (GetManagingSchemeIdsInput) returns (CreatedSchemeIds) { + option (aelf.is_view) = true; + } + rpc GetScheme (aelf.Hash) returns (Scheme) { + option (aelf.is_view) = true; + } + rpc GetSchemeAddress (SchemePeriod) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetDistributedProfitsInfo (SchemePeriod) returns (DistributedProfitsInfo) { + option (aelf.is_view) = true; + } + rpc GetProfitDetails (GetProfitDetailsInput) returns (ProfitDetails) { + option (aelf.is_view) = true; + } + rpc GetProfitAmount (GetProfitAmountInput) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetProfitsMap (ClaimProfitsInput) returns (ReceivedProfitsMap) { + option (aelf.is_view) = true; + } +} + +message CreateSchemeInput { + int64 profit_receiving_due_period_count = 1; + bool is_release_all_balance_every_time_by_default = 2; + int32 delay_distribute_period_count = 3; + aelf.Address manager = 4; + bool can_remove_beneficiary_directly = 5; + aelf.Hash token = 6; +} + +message Scheme { + aelf.Address virtual_address = 1; + int64 total_shares = 2; + aelf.Address manager = 3; + int64 current_period = 4; + repeated SchemeBeneficiaryShare sub_schemes = 5; + bool can_remove_beneficiary_directly = 6; + int64 profit_receiving_due_period_count = 7; + bool is_release_all_balance_every_time_by_default = 8; + aelf.Hash scheme_id = 9; + int32 delay_distribute_period_count = 10; + map cached_delay_total_shares = 11;// period -> total shares, max elements count should be delay_distribute_period_count + repeated string received_token_symbols = 12; +} + +message SchemeBeneficiaryShare { + aelf.Hash scheme_id = 1; + int64 shares = 2; +} + +message AddBeneficiaryInput { + aelf.Hash scheme_id = 1; + BeneficiaryShare beneficiary_share = 2; + int64 end_period = 3; +} + +message RemoveBeneficiaryInput { + aelf.Address beneficiary = 1; + aelf.Hash scheme_id = 2; +} + +message AddBeneficiariesInput { + aelf.Hash scheme_id = 1; + repeated BeneficiaryShare beneficiary_shares = 2; + int64 end_period = 3; +} + +message RemoveBeneficiariesInput { + repeated aelf.Address beneficiaries = 1; + aelf.Hash scheme_id = 2; +} + +message BeneficiaryShare { + aelf.Address beneficiary = 1; + int64 shares = 2; +} + +message ClaimProfitsInput { + aelf.Hash scheme_id = 1; + aelf.Address beneficiary = 2; +} + +message DistributeProfitsInput { + aelf.Hash scheme_id = 1; + int64 period = 2; + map amounts_map = 3; +} + +message ProfitDetails { + repeated ProfitDetail details = 1; +} + +message ProfitDetail { + int64 start_period = 1; + int64 end_period = 2; + int64 shares = 3; + int64 last_profit_period = 4; + bool is_weight_removed = 5; +} + +message ContributeProfitsInput { + aelf.Hash scheme_id = 1; + int64 amount = 2; + int64 period = 3; + string symbol = 4; +} + +message AddSubSchemeInput { + aelf.Hash scheme_id = 1; + aelf.Hash sub_scheme_id = 2; + int64 sub_scheme_shares = 3; +} + +message RemoveSubSchemeInput { + aelf.Hash scheme_id = 1; + aelf.Hash sub_scheme_id = 2; + aelf.Address sub_item_creator = 3; +} + +message DistributedProfitsInfo { + int64 total_shares = 1; + map amounts_map = 2; + bool is_released = 3; +} + +message CreatedSchemeIds { + repeated aelf.Hash scheme_ids = 1; +} + +message GetManagingSchemeIdsInput { + aelf.Address manager = 1; +} + +message SchemePeriod { + aelf.Hash scheme_id = 1; + int64 period = 2; +} + +message GetProfitDetailsInput { + aelf.Hash scheme_id = 1; + aelf.Address beneficiary = 2; +} + +message ResetManagerInput { + aelf.Hash scheme_id = 1; + aelf.Address new_manager = 2; +} + +message GetProfitAmountInput { + aelf.Hash scheme_id = 1; + string symbol = 2; + aelf.Address beneficiary = 3; +} + +message ReceivedProfitsMap { + map value = 1; +} + +// Events +message SchemeCreated { + option (aelf.is_event) = true; + aelf.Address virtual_address = 1; + aelf.Address manager = 2; + int64 profit_receiving_due_period_count = 3; + bool is_release_all_balance_every_time_by_default = 4; + aelf.Hash scheme_id = 5; +} + +message ProfitsClaimed { + option (aelf.is_event) = true; + aelf.Address beneficiary = 1; + string symbol = 2; + int64 amount = 3; + int64 period = 4; + int64 claimer_shares = 5; + int64 total_shares = 6; +} \ No newline at end of file diff --git a/protobuf/profit_contract_impl.proto b/protobuf/profit_contract_impl.proto new file mode 100755 index 00000000..7e55742e --- /dev/null +++ b/protobuf/profit_contract_impl.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package ProfitImpl; + +import "acs1.proto"; +import "aelf/core.proto"; +import "profit_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Profit"; + +service ProfitContractImpl { + option (aelf.csharp_state) = "AElf.Contracts.Profit.ProfitContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "profit_contract.proto"; + +} diff --git a/protobuf/random_demo.proto b/protobuf/random_demo.proto new file mode 100755 index 00000000..181ac742 --- /dev/null +++ b/protobuf/random_demo.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; + +option csharp_namespace = "AElf.Contracts.RandomDemoContract"; + +service RandomDemoContract { + + option (aelf.csharp_state) = "AElf.Contracts.RandomDemoContract.RandomDemoContractState"; + + rpc RequestRandomList (RequestRandomParam) returns (RequestRandomOutput) { } + rpc GetRandomList (aelf.Hash) returns (RandomList) { } +} + +message listArrayData{ + sint64 index = 1; + string name = 2; + sint64 number = 3; +} + +message RequestRandomParam { + sint64 Number = 1; + repeated listArrayData List = 2; +} + +message RequestRandomOutput { + aelf.Hash token_hash = 1; +} + +message RandomList { + repeated listArrayData List = 3; +} \ No newline at end of file diff --git a/protobuf/referendum_contract.proto b/protobuf/referendum_contract.proto new file mode 100755 index 00000000..418505a4 --- /dev/null +++ b/protobuf/referendum_contract.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; + +package Referendum; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "aelf/core.proto"; +import "acs3.proto"; + +option csharp_namespace = "AElf.Contracts.Referendum"; + +service ReferendumContract { + + option (aelf.csharp_state) = "AElf.Contracts.Referendum.ReferendumState"; + + // Actions + rpc ReclaimVoteToken (aelf.Hash) returns (google.protobuf.Empty) { + } + rpc CreateOrganization (CreateOrganizationInput) returns (aelf.Address) { + } + rpc CreateOrganizationBySystemContract(CreateOrganizationBySystemContractInput) returns (aelf.Address){ + } + rpc GetOrganization (aelf.Address) returns (Organization) { + option (aelf.is_view) = true; + } + rpc CalculateOrganizationAddress(CreateOrganizationInput) returns (aelf.Address){ + option (aelf.is_view) = true; + } + rpc GetProposalVirtualAddress(aelf.Hash) returns (aelf.Address){} +} + +message CreateOrganizationInput { + string token_symbol = 1; + acs3.ProposalReleaseThreshold proposal_release_threshold = 2; + acs3.ProposerWhiteList proposer_white_list = 3; + aelf.Hash creation_token = 4; +} + +message Organization { + acs3.ProposalReleaseThreshold proposal_release_threshold = 1; + string token_symbol = 2; + aelf.Address organization_address = 3; + aelf.Hash organization_hash = 4; + acs3.ProposerWhiteList proposer_white_list = 5; + aelf.Hash creation_token = 6; +} + +message Receipt { + int64 amount = 1; + string token_symbol = 2; + aelf.Hash lock_id = 3; +} + +message ProposalInfo { + aelf.Hash proposal_id = 1; + string contract_method_name = 2; + aelf.Address to_address = 3; + bytes params = 4; + google.protobuf.Timestamp expired_time = 5; + aelf.Address proposer = 6; + aelf.Address organization_address = 7; + int64 approval_count = 8; + int64 rejection_count = 9; + int64 abstention_count = 10; + string proposal_description_url = 11; +} + +message CreateOrganizationBySystemContractInput { + CreateOrganizationInput organization_creation_input = 1; + string organization_address_feedback_method = 2; +} + +message ReferendumReceiptCreated { + option (aelf.is_event) = true; + aelf.Hash proposal_id = 1; + aelf.Address address = 2; + string symbol = 3; + int64 amount = 4; + string receipt_type = 5; + google.protobuf.Timestamp time = 6; + aelf.Address organization_address=7 [(aelf.is_indexed) = true]; +} \ No newline at end of file diff --git a/protobuf/referendum_contract_impl.proto b/protobuf/referendum_contract_impl.proto new file mode 100755 index 00000000..1c7e2900 --- /dev/null +++ b/protobuf/referendum_contract_impl.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package ReferendumImpl; + +import "acs1.proto"; +import "acs3.proto"; +import "referendum_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Referendum"; + +service ReferendumContractImpl { + + option (aelf.csharp_state) = "AElf.Contracts.Referendum.ReferendumState"; + option (aelf.base) = "acs3.proto"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "referendum_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/test_contract.proto b/protobuf/test_contract.proto new file mode 100755 index 00000000..ecbe89e7 --- /dev/null +++ b/protobuf/test_contract.proto @@ -0,0 +1,41 @@ +/* This files is part of the Hello World smart contract example that is included in Boilerplate. + * It is only the definition of the contract, implementation is located in the "contract" folder + * and tests are located in the "test" folder. + * + * You can use this as a basic template for new contracts. + * + * When building Boilerplate or the contract project located in the "../contract/AElf.Test/" + * protoc (the protobuf compiler) will be invoked and this file will produce a .c.cs file and .g.cs file, in the + * "../contract/AElf.Test/Protobuf/Generated/" folder. + */ + +// the version of the language, use proto3 for contracts +syntax = "proto3"; + +// some core imports for aelf chain types +import "aelf/core.proto"; +import "aelf/options.proto"; + +// import for using the google.protobuf.Empty type. +import "google/protobuf/empty.proto"; + +// the name of the C# namespace in which the contract code will be, +// generated code will also be in this namespace. +option csharp_namespace = "AElf.Test"; + +// the contract definition: a gRPC service definition. +service TestContract { + + // the full name of the C# class that will contain the state (here . format). + option (aelf.csharp_state) = "TestContractState"; + + // an action defined as a gRPC service method. + // this action take a google.protobuf.Empty (placeholder for void) as input + // and returns a custom defined type: HelloReturn. + rpc Hello (google.protobuf.Empty) returns (HelloReturn) { } +} + +// a custom message, used as the return type of the Hello action +message HelloReturn { + string Value = 1; +} \ No newline at end of file diff --git a/protobuf/token_contract.proto b/protobuf/token_contract.proto new file mode 100755 index 00000000..4715ad8c --- /dev/null +++ b/protobuf/token_contract.proto @@ -0,0 +1,782 @@ +/** + * MultiToken contract. + */ +syntax = "proto3"; + +package token; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.MultiToken"; + +service TokenContract { + // Create a new token. + rpc Create (CreateInput) returns (google.protobuf.Empty) { + } + + // Issuing some amount of tokens to an address is the action of increasing that addresses balance + // for the given token. The total amount of issued tokens must not exceed the total supply of the token + // and only the issuer (creator) of the token can issue tokens. + // Issuing tokens effectively increases the circulating supply. + rpc Issue (IssueInput) returns (google.protobuf.Empty) { + } + + // Transferring tokens simply is the action of transferring a given amount of tokens from one address to another. + // The origin or source address is the signer of the transaction. + // The balance of the sender must be higher than the amount that is transferred. + rpc Transfer (TransferInput) returns (google.protobuf.Empty) { + } + + // The TransferFrom action will transfer a specified amount of tokens from one address to another. + // For this operation to succeed the from address needs to have approved (see allowances) enough tokens + // to Sender of this transaction. If successful the amount will be removed from the allowance. + rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) { + } + + // The approve action increases the allowance from the Sender to the Spender address, + // enabling the Spender to call TransferFrom. + rpc Approve (ApproveInput) returns (google.protobuf.Empty) { + } + + // This is the reverse operation for Approve, it will decrease the allowance. + rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { + } + + // This method can be used to lock tokens. + rpc Lock (LockInput) returns (google.protobuf.Empty) { + } + + // This is the reverse operation of locking, it un-locks some previously locked tokens. + rpc Unlock (UnlockInput) returns (google.protobuf.Empty) { + } + + // This action will burn the specified amount of tokens, removing them from the token’s Supply. + rpc Burn (BurnInput) returns (google.protobuf.Empty) { + } + + // Change the issuer of the specified token. Only the original issuer can change it. + rpc ChangeTokenIssuer (ChangeTokenIssuerInput) returns (google.protobuf.Empty) { + } + + // Set the primary token of side chain. + rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) { + } + + // This interface is used for cross-chain transfer. + rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) { + } + + // This method is used to receive cross-chain transfers. + rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) { + } + + // The side chain creates tokens. + rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) { + } + + // When the side chain is started, the side chain is initialized with the parent chain information. + rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) { + } + + // Handle the transaction fees charged by ChargeTransactionFees. + rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) { + } + + // Used to collect transaction fees. + rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) { + } + + // Check the token threshold. + rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) { + } + + // Initialize coefficients of every type of tokens supporting charging fee. + rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + // Processing resource token received. + rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) { + } + + // A transaction resource fee is charged to implement the ACS8 standards. + rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) { + } + + // Verify that the resource token are sufficient. + rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + // Set the list of tokens to pay transaction fees. + rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){ + } + + // Update the coefficient of the transaction fee calculation formula. + rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + + // Update the coefficient of the transaction fee calculation formula. + rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + + // This method is used to initialize the governance organization for some functions, + // including: the coefficient of the user transaction fee calculation formula, + // the coefficient of the contract developer resource fee calculation formula, and the side chain rental fee. + rpc InitializeAuthorizedController (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + rpc ResetExternalInfo (ResetExternalInfoInput) returns (google.protobuf.Empty){ + } + + rpc AddAddressToCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) { + } + rpc RemoveAddressFromCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) { + } + + // Query token information. + rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) { + option (aelf.is_view) = true; + } + + // Query native token information. + rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) { + option (aelf.is_view) = true; + } + + // Query resource token information. + rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) { + option (aelf.is_view) = true; + } + + // Query the balance at the specified address. + rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } + + // Query the account's allowance for other addresses + rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + + // Check whether the token is in the whitelist of an address, + // which can be called TransferFrom to transfer the token under the condition of not being credited. + rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + + // Query the information for a lock. + rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) { + option (aelf.is_view) = true; + } + + // Query the address of receiving token in cross-chain transfer. + rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Query the name of the primary Token. + rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + + // Query the coefficient of the transaction fee calculation formula. + rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + + // Query the coefficient of the transaction fee calculation formula. + rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + + // Query tokens that can pay transaction fees. + rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){ + option (aelf.is_view) = true; + } + + // Query the hash of the last input of ClaimTransactionFees. + rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + + // Query the hash of the last input of DonateResourceToken. + rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc IsInCreateTokenWhiteList (aelf.Address) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetReservedExternalInfoKeyList (google.protobuf.Empty) returns (StringList) { + option (aelf.is_view) = true; + } +} + +message TokenInfo { + // The symbol of the token.f + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The current supply of the token. + int64 supply = 3; + // The total supply of the token. + int64 total_supply = 4; + // The precision of the token. + int32 decimals = 5; + // The address that created the token. + aelf.Address issuer = 6; + // A flag indicating if this token is burnable. + bool is_burnable = 7; + // The chain id of the token. + int32 issue_chain_id = 8; + // The amount of issued tokens. + int64 issued = 9; + // The external information of the token. + ExternalInfo external_info = 10; +} + +message ExternalInfo { + map value = 1; +} + +message CreateInput { + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token + int32 decimals = 4; + // The address that created the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // A whitelist address list used to lock tokens. + repeated aelf.Address lock_white_list = 7; + // The chain id of the token. + int32 issue_chain_id = 8; + // The external information of the token. + ExternalInfo external_info = 9; +} + +message SetPrimaryTokenSymbolInput { + // The symbol of the token. + string symbol = 1; +} + +message IssueInput { + // The token symbol to issue. + string symbol = 1; + // The token amount to issue. + int64 amount = 2; + // The memo. + string memo = 3; + // The target address to issue. + aelf.Address to = 4; +} + +message TransferInput { + // The receiver of the token. + aelf.Address to = 1; + // The token symbol to transfer. + string symbol = 2; + // The amount to to transfer. + int64 amount = 3; + // The memo. + string memo = 4; +} + +message LockInput { + // The one want to lock his token. + aelf.Address address = 1; + // Id of the lock. + aelf.Hash lock_id = 2; + // The symbol of the token to lock. + string symbol = 3; + // a memo. + string usage = 4; + // The amount of tokens to lock. + int64 amount = 5; +} + +message UnlockInput { + // The one want to un-lock his token. + aelf.Address address = 1; + // Id of the lock. + aelf.Hash lock_id = 2; + // The symbol of the token to un-lock. + string symbol = 3; + // a memo. + string usage = 4; + // The amount of tokens to un-lock. + int64 amount = 5; +} + +message TransferFromInput { + // The source address of the token. + aelf.Address from = 1; + // The destination address of the token. + aelf.Address to = 2; + // The symbol of the token to transfer. + string symbol = 3; + // The amount to transfer. + int64 amount = 4; + // The memo. + string memo = 5; +} + +message ApproveInput { + // The address that allowance will be increased. + aelf.Address spender = 1; + // The symbol of token to approve. + string symbol = 2; + // The amount of token to approve. + int64 amount = 3; +} + +message UnApproveInput { + // The address that allowance will be decreased. + aelf.Address spender = 1; + // The symbol of token to un-approve. + string symbol = 2; + // The amount of token to un-approve. + int64 amount = 3; +} + +message BurnInput { + // The symbol of token to burn. + string symbol = 1; + // The amount of token to burn. + int64 amount = 2; +} + +message ChargeResourceTokenInput { + // Collection of charge resource token, Symbol->Amount. + map cost_dic = 1; + // The sender of the transaction. + aelf.Address caller = 2; +} + +message TransactionFeeBill { + // The transaction fee dictionary, Symbol->fee. + map fees_map = 1; +} + +message CheckThresholdInput { + // The sender of the transaction. + aelf.Address sender = 1; + // The threshold to set, Symbol->Threshold. + map symbol_to_threshold = 2; + // Whether to check the allowance. + bool is_check_allowance = 3; +} + +message GetTokenInfoInput { + // The symbol of token. + string symbol = 1; +} + +message GetBalanceInput { + // The symbol of token. + string symbol = 1; + // The target address of the query. + aelf.Address owner = 2; +} + +message GetBalanceOutput { + // The symbol of token. + string symbol = 1; + // The target address of the query. + aelf.Address owner = 2; + // The balance of the owner. + int64 balance = 3; +} + +message GetAllowanceInput { + // The symbol of token. + string symbol = 1; + // The address of the token owner. + aelf.Address owner = 2; + // The address of the spender. + aelf.Address spender = 3; +} + +message GetAllowanceOutput { + // The symbol of token. + string symbol = 1; + // The address of the token owner. + aelf.Address owner = 2; + // The address of the spender. + aelf.Address spender = 3; + // The amount of allowance. + int64 allowance = 4; +} + +message CrossChainTransferInput { + // The receiver of transfer. + aelf.Address to = 1; + // The symbol of token. + string symbol = 2; + // The amount of token to transfer. + int64 amount = 3; + // The memo. + string memo = 4; + // The destination chain id. + int32 to_chain_id = 5; + // The chain id of the token. + int32 issue_chain_id = 6; +} + +message CrossChainReceiveTokenInput { + // The source chain id. + int32 from_chain_id = 1; + // The height of the transfer transaction. + int64 parent_chain_height = 2; + // The raw bytes of the transfer transaction. + bytes transfer_transaction_bytes = 3; + // The merkle path created from the transfer transaction. + aelf.MerklePath merkle_path = 4; +} + +message IsInWhiteListInput { + // The symbol of token. + string symbol = 1; + // The address to check. + aelf.Address address = 2; +} + +message SymbolToPayTxSizeFee{ + // The symbol of token. + string token_symbol = 1; + // The charge weight of primary token. + int32 base_token_weight = 2; + // The new added token charge weight. For example, the charge weight of primary Token is set to 1. + // The newly added token charge weight is set to 10. If the transaction requires 1 unit of primary token, + // the user can also pay for 10 newly added tokens. + int32 added_token_weight = 3; +} + +message SymbolListToPayTxSizeFee{ + // Transaction fee token information. + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1; +} + +message ChargeTransactionFeesInput { + // The method name of transaction. + string method_name = 1; + // The contract address of transaction. + aelf.Address contract_address = 2; + // The amount of transaction size fee. + int64 transaction_size_fee = 3; + // Transaction fee token information. + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4; +} + +message ChargeTransactionFeesOutput { + // Whether the charge was successful. + bool success = 1; + // The charging information. + string charging_information = 2; +} + +message CallbackInfo { + aelf.Address contract_address = 1; + string method_name = 2; +} + +message ExtraTokenListModified { + option (aelf.is_event) = true; + // Transaction fee token information. + SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1; +} + +message GetLockedAmountInput { + // The address of the lock. + aelf.Address address = 1; + // The token symbol. + string symbol = 2; + // The id of the lock. + aelf.Hash lock_id = 3; +} + +message GetLockedAmountOutput { + // The address of the lock. + aelf.Address address = 1; + // The token symbol. + string symbol = 2; + // The id of the lock. + aelf.Hash lock_id = 3; + // The locked amount. + int64 amount = 4; +} + +message TokenInfoList { + // List of token information. + repeated TokenInfo value = 1; +} + +message GetCrossChainTransferTokenContractAddressInput { + // The chain id. + int32 chainId = 1; +} + +message CrossChainCreateTokenInput { + // The chain id of the chain on which the token was created. + int32 from_chain_id = 1; + // The height of the transaction that created the token. + int64 parent_chain_height = 2; + // The transaction that created the token. + bytes transaction_bytes = 3; + // The merkle path created from the transaction that created the transaction. + aelf.MerklePath merkle_path = 4; +} + +message InitializeFromParentChainInput { + // The amount of resource. + map resource_amount = 1; + // The token contract addresses. + map registered_other_token_contract_addresses = 2; + // The creator the side chain. + aelf.Address creator = 3; +} + +message UpdateCoefficientsInput { + // The specify pieces gonna update. + repeated int32 piece_numbers = 1; + // Coefficients of one single type. + CalculateFeeCoefficients coefficients = 2; +} + +enum FeeTypeEnum { + READ = 0; + STORAGE = 1; + WRITE = 2; + TRAFFIC = 3; + TX = 4; +} + +message CalculateFeePieceCoefficients { + // Coefficients of one single piece. + // The first char is its type: liner / power. + // The second char is its piece upper bound. + repeated int32 value = 1; +} + +message CalculateFeeCoefficients { + // The resource fee type, like READ, WRITE, etc. + int32 fee_token_type = 1; + // Coefficients of one single piece. + repeated CalculateFeePieceCoefficients piece_coefficients_list = 2; +} + +message AllCalculateFeeCoefficients { + // The coefficients of fee Calculation. + repeated CalculateFeeCoefficients value = 1; +} + +message TotalTransactionFeesMap +{ + // Token dictionary that charge transaction fee, Symbol->Amount. + map value = 1; + // The hash of the block processing the transaction. + aelf.Hash block_hash = 2; + // The height of the block processing the transaction. + int64 block_height = 3; +} + +message TotalResourceTokensMaps { + // Resource tokens to charge. + repeated ContractTotalResourceTokens value = 1; + // The hash of the block processing the transaction. + aelf.Hash block_hash = 2; + // The height of the block processing the transaction. + int64 block_height = 3; +} + +message ContractTotalResourceTokens { + // The contract address. + aelf.Address contract_address = 1; + // Resource tokens to charge. + TotalResourceTokensMap tokens_map = 2; +} + +message TotalResourceTokensMap +{ + // Resource token dictionary, Symbol->Amount. + map value = 1; +} + +message ChangeTokenIssuerInput +{ + // The token symbol. + string symbol = 1; + // The new token issuer for change. + aelf.Address new_token_Issuer = 2; +} + +message ResetExternalInfoInput { + string symbol = 1; + ExternalInfo external_info = 2; +} + +message StringList { + repeated string value = 1; +} + +message Transferred { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1 [(aelf.is_indexed) = true]; + // The destination address of the transferred token. + aelf.Address to = 2 [(aelf.is_indexed) = true]; + // The symbol of the transferred token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of the transferred token. + int64 amount = 4; + // The memo. + string memo = 5; +} + +message Approved { + option (aelf.is_event) = true; + // The address of the token owner. + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + // The address that allowance be increased. + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + // The symbol of approved token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of approved token. + int64 amount = 4; +} + +message UnApproved { + option (aelf.is_event) = true; + // The address of the token owner. + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + // The address that allowance be decreased. + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + // The symbol of un-approved token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of un-approved token. + int64 amount = 4; +} + +message Burned +{ + option (aelf.is_event) = true; + // The address who wants to burn token. + aelf.Address burner = 1 [(aelf.is_indexed) = true]; + // The symbol of burned token. + string symbol = 2 [(aelf.is_indexed) = true]; + // The amount of burned token. + int64 amount = 3; +} + +message ChainPrimaryTokenSymbolSet { + option (aelf.is_event) = true; + // The symbol of token. + string token_symbol = 1; +} + +message CalculateFeeAlgorithmUpdated { + option (aelf.is_event) = true; + // All calculate fee coefficients after modification. + AllCalculateFeeCoefficients all_type_fee_coefficients = 1; +} + +message RentalCharged { + option (aelf.is_event) = true; + // The symbol of rental fee charged. + string symbol = 1; + // The amount of rental fee charged. + int64 amount = 2; +} + +message RentalAccountBalanceInsufficient { + option (aelf.is_event) = true; + // The symbol of insufficient rental account balance. + string symbol = 1; + // The balance of the account. + int64 amount = 2; +} + +message TokenCreated { + option (aelf.is_event) = true; + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token. + int32 decimals = 4; + // The address that created the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // The chain id of the token. + int32 issue_chain_id = 7; + // The external information of the token. + ExternalInfo external_info = 8; +} + +message Issued { + option (aelf.is_event) = true; + // The symbol of issued token. + string symbol = 1; + // The amount of issued token. + int64 amount = 2; + // The memo. + string memo = 3; + // The issued target address. + aelf.Address to = 4; +} + +message CrossChainTransferred { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1; + // The destination address of the transferred token. + aelf.Address to = 2; + // The symbol of the transferred token. + string symbol = 3; + // The amount of the transferred token. + int64 amount = 4; + // The memo. + string memo = 5; + // The destination chain id. + int32 to_chain_id = 6; + // The chain id of the token. + int32 issue_chain_id = 7; +} + +message CrossChainReceived { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1; + // The destination address of the transferred token. + aelf.Address to = 2; + // The symbol of the received token. + string symbol = 3; + // The amount of the received token. + int64 amount = 4; + // The memo. + string memo = 5; + // The destination chain id. + int32 from_chain_id = 6; + // The chain id of the token. + int32 issue_chain_id = 7; + // The parent chain height of the transfer transaction. + int64 parent_chain_height = 8; +} + +message ExternalInfoChanged { + option (aelf.is_event) = true; + string symbol = 1; + ExternalInfo external_info = 2; +} \ No newline at end of file diff --git a/protobuf/token_contract_impl.proto b/protobuf/token_contract_impl.proto new file mode 100755 index 00000000..b21aaa8a --- /dev/null +++ b/protobuf/token_contract_impl.proto @@ -0,0 +1,240 @@ +/** + * MultiToken contract. + * + * The MultiToken contract is mainly used to manage the user's account and transaction fees related Settings. + * + * Implement AElf Standards ACS1 and ACS2. + */ +syntax = "proto3"; + +package tokenimpl; + +import "aelf/core.proto"; +import "acs1.proto"; +import "acs2.proto"; +import "token_contract.proto"; +// Because implementation uses this proto file. +import "transaction_fee.proto"; +import "authority_info.proto"; + +option csharp_namespace = "AElf.Contracts.MultiToken"; + +service TokenContractImpl { + option (aelf.csharp_state) = "AElf.Contracts.MultiToken.TokenContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "acs2.proto"; + option (aelf.base) = "token_contract.proto"; + + // Transfer resource tokens to designated contract address. + rpc AdvanceResourceToken (AdvanceResourceTokenInput) returns (google.protobuf.Empty) { + } + + // Take token from contract address. + rpc TakeResourceTokenBack (TakeResourceTokenBackInput) returns (google.protobuf.Empty) { + } + + // Register the token contract address for cross chain. + rpc RegisterCrossChainTokenContractAddress (RegisterCrossChainTokenContractAddressInput) returns (google.protobuf.Empty) { + } + + // Set the receiver address of the side chain transaction fee. + rpc SetFeeReceiver (aelf.Address) returns (google.protobuf.Empty) { + } + + // Validates if the token exist. + rpc ValidateTokenInfoExists(ValidateTokenInfoExistsInput) returns (google.protobuf.Empty){ + } + + // Update the rental unit price of the side chain. + rpc UpdateRental (UpdateRentalInput) returns (google.protobuf.Empty) { + } + + // Set the amount of resources fee per minute for the side chain. + rpc UpdateRentedResources (UpdateRentedResourcesInput) returns (google.protobuf.Empty) { + } + + // Transfer Token to the specified contract. + rpc TransferToContract (TransferToContractInput) returns (google.protobuf.Empty) { + } + + // Change the governance organization of side chain rental. + rpc ChangeSideChainRentalController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Change the governance organization for tokens to pay transaction fees. + rpc ChangeSymbolsToPayTXSizeFeeController(AuthorityInfo) returns (google.protobuf.Empty){ + } + + // Change the governance organization for cross-chain token contract address registration. + rpc ChangeCrossChainTokenContractRegistrationController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Change the governance organization of the coefficient of the user transaction fee calculation formula. + rpc ChangeUserFeeController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Change the governance organization of the coefficient of the developer's transaction resource fee calculation formula. + rpc ChangeDeveloperController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Get the address of fee receiver. + rpc GetFeeReceiver (google.protobuf.Empty) returns (aelf.Address){ + option (aelf.is_view) = true; + } + + // Query the amount of resources usage currently. + rpc GetResourceUsage (google.protobuf.Empty) returns (ResourceUsage) { + option (aelf.is_view) = true; + } + + // Query the governance organization for tokens to pay transaction fees. + rpc GetSymbolsToPayTXSizeFeeController(google.protobuf.Empty) returns (AuthorityInfo){ + option (aelf.is_view) = true; + } + + // Query the governance organization of the + rpc GetCrossChainTokenContractRegistrationController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } + + // Query the governance organization that calculates the formula coefficient + // for the transaction cost the user sends the contract. + rpc GetUserFeeController(google.protobuf.Empty) returns (UserFeeController){ + option (aelf.is_view) = true; + } + + // Query the governing organization of the formula coefficients for calculating developer contract transaction fee. + rpc GetDeveloperFeeController (google.protobuf.Empty) returns (DeveloperFeeController) { + option (aelf.is_view) = true; + } + + // Query the organization that governs the side chain rental fee. + rpc GetSideChainRentalControllerCreateInfo (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } + + // Compute the virtual address for locking. + rpc GetVirtualAddressForLocking (GetVirtualAddressForLockingInput) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Query how much resource tokens should be paid currently. + rpc GetOwningRental (google.protobuf.Empty) returns (OwningRental) { + option (aelf.is_view) = true; + } + + // Query the unit price of the side chain resource cost, resource cost = unit price * quantity, + // the quantity can be queried through GetResourceUsage. + rpc GetOwningRentalUnitValue (google.protobuf.Empty) returns (OwningRentalUnitValue) { + option (aelf.is_view) = true; + } +} + +message AdvanceResourceTokenInput { + // The contract address to transfer. + aelf.Address contract_address = 1; + // The resource token symbol to transfer. + string resource_token_symbol = 2; + // The amount of resource token to transfer. + int64 amount = 3; +} + +message TakeResourceTokenBackInput { + // The contract address to take back. + aelf.Address contract_address = 1; + // The resource token symbol to take back. + string resource_token_symbol = 2; + // The amount of resource token to take back. + int64 amount = 3; +} + +message RegisterCrossChainTokenContractAddressInput{ + // The source chain id. + int32 from_chain_id = 1; + // The parent chain height of the transaction. + int64 parent_chain_height = 2; + // The raw bytes of the transfer transaction. + bytes transaction_bytes = 3; + // The merkle path created from the transaction. + aelf.MerklePath merkle_path = 4; + // The token contract address. + aelf.Address token_contract_address = 5; +} + +message ValidateTokenInfoExistsInput{ + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token. + int32 decimals = 4; + // The address that created the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // The chain id of the token. + int32 issue_chain_id = 7; + // The external information of the token. + map external_info = 8; +} + +message UpdateRentalInput { + // The unit price of resource tokens, symbol -> unit price. + map rental = 1; +} + +message UpdateRentedResourcesInput { + // Amount of resource tokens consumed per minute, symbol -> resource consumption. + map resource_amount = 1; +} + +message ResourceUsage { + // The amount of resource tokens usage, symbol -> amount. + map value = 1; +} + +message GetVirtualAddressForLockingInput { + // The address of the lock. + aelf.Address address = 1; + // The id of the lock. + aelf.Hash lock_id = 2; +} + +message OwningRental { + // The amount of resource tokens owed, symbol -> amount. + map resource_amount = 1; +} + +message OwningRentalUnitValue { + // Resource unit price, symbol -> unit price. + map resource_unit_value = 1; +} + +message TransferToContractInput { + // The symbol of token. + string symbol = 1; + // The amount of token. + int64 amount = 2; + // The memo. + string memo = 3; +} + +message UserFeeController{ + // The association that governs the organization. + AuthorityInfo root_controller = 1; + // The parliament organization of members. + AuthorityInfo parliament_controller = 2; + // The referendum organization of members. + AuthorityInfo referendum_controller = 3; +} + +message DeveloperFeeController { + // The association that governs the organization. + AuthorityInfo root_controller = 1; + // The parliament organization of members. + AuthorityInfo parliament_controller = 2; + // The developer organization of members. + AuthorityInfo developer_controller = 3; +} diff --git a/protobuf/token_converter_contract.proto b/protobuf/token_converter_contract.proto new file mode 100755 index 00000000..45151398 --- /dev/null +++ b/protobuf/token_converter_contract.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package TokenConverter; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; +import "authority_info.proto"; + +option csharp_namespace = "AElf.Contracts.TokenConverter"; + +service TokenConverterContract { + + option (aelf.csharp_state) = "AElf.Contracts.TokenConverter.TokenConverterContractState"; + + // Actions + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc SetConnector (Connector) returns (google.protobuf.Empty) { + } + rpc Buy (BuyInput) returns (google.protobuf.Empty) { + } + rpc Sell (SellInput) returns (google.protobuf.Empty) { + } + rpc SetFeeRate (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + rpc UpdateConnector(Connector) returns (google.protobuf.Empty){ + } + rpc AddPairConnector(PairConnectorParam) returns (google.protobuf.Empty){ + } + rpc EnableConnector (ToBeConnectedTokenInfo) returns (google.protobuf.Empty) { + } + rpc ChangeConnectorController (AuthorityInfo) returns (google.protobuf.Empty) { + } + // Views + rpc GetFeeReceiverAddress (google.protobuf.Empty) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetPairConnector (TokenSymbol) returns (PairConnector) { + option (aelf.is_view) = true; + } + rpc GetFeeRate (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + rpc GetBaseTokenSymbol (google.protobuf.Empty) returns (TokenSymbol) { + option (aelf.is_view) = true; + } + rpc GetNeededDeposit(ToBeConnectedTokenInfo) returns (DepositInfo) { + option (aelf.is_view) = true; + } + rpc GetDepositConnectorBalance(google.protobuf.StringValue) returns (google.protobuf.Int64Value){ + option (aelf.is_view) = true; + } + rpc GetControllerForManageConnector (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } + rpc IsSymbolAbleToSell (google.protobuf.StringValue) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} + +message Connector { + string symbol = 1; + int64 virtual_balance = 2; + string weight = 3; + bool is_virtual_balance_enabled = 4; // true if virtual balance is enabled, false if not + bool is_purchase_enabled = 5; + string related_symbol = 6; + bool is_deposit_account = 7; +} + +message TokenSymbol { + string symbol = 1; +} + +message InitializeInput { + string base_token_symbol = 1; + string fee_rate = 2; + repeated Connector connectors = 3; +} + +message BuyInput { + string symbol = 1; + int64 amount = 2; + int64 pay_limit = 3; // No buy if paying more than this, 0 if no limit +} + +message SellInput { + string symbol = 1; + int64 amount = 2; + int64 receive_limit = 3; // No sell if receiving less than this, 0 if no limit +} + +message GetExchangeRateInput { + string from_symbol = 1; + string to_symbol = 2; +} + +message SellWithInlineActionInput { + string symbol = 1; + int64 amount = 2; + int64 receive_limit = 3; // No sell if receiving less than this, 0 if no limit + aelf.Address contract_address = 4; + string method_name = 5; + bytes params = 6; +} + +// Events +message TokenBought { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + int64 bought_amount = 2; + int64 base_amount = 3; + int64 fee_amount =4; +} + +message TokenSold { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + int64 sold_amount = 2; + int64 base_amount = 3; + int64 fee_amount =4; +} +message PairConnectorParam { + string resource_connector_symbol = 1; + string resource_weight = 2; + int64 native_virtual_balance = 3; + string native_weight = 4; +} + +message ToBeConnectedTokenInfo{ + string token_symbol = 1; + int64 amount_to_token_convert = 2; +} +message DepositInfo{ + int64 need_amount = 1; + int64 amount_out_of_token_convert = 2; +} +message PairConnector{ + Connector resource_connector = 1; + Connector deposit_connector = 2; +} \ No newline at end of file diff --git a/protobuf/token_converter_contract_impl.proto b/protobuf/token_converter_contract_impl.proto new file mode 100755 index 00000000..e73cd5c4 --- /dev/null +++ b/protobuf/token_converter_contract_impl.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package TokenConverterImpl; + +import "acs1.proto"; +import "token_converter_contract.proto"; + +option csharp_namespace = "AElf.Contracts.TokenConverter"; + +service TokenConverterContractImpl { + + option (aelf.csharp_state) = "AElf.Contracts.TokenConverter.TokenConverterContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "token_converter_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/token_holder_contract.proto b/protobuf/token_holder_contract.proto new file mode 100755 index 00000000..d2e9fd60 --- /dev/null +++ b/protobuf/token_holder_contract.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +package TokenHolder; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.TokenHolder"; + +service TokenHolderContract { + + option (aelf.csharp_state) = "AElf.Contracts.TokenHolder.TokenHolderContractState"; + + // Actions + rpc CreateScheme (CreateTokenHolderProfitSchemeInput) returns (google.protobuf.Empty) { + } + rpc AddBeneficiary (AddTokenHolderBeneficiaryInput) returns (google.protobuf.Empty) { + } + rpc RemoveBeneficiary (RemoveTokenHolderBeneficiaryInput) returns (google.protobuf.Empty) { + } + rpc ContributeProfits (ContributeProfitsInput) returns (google.protobuf.Empty) { + } + rpc DistributeProfits (DistributeProfitsInput) returns (google.protobuf.Empty) { + } + rpc RegisterForProfits (RegisterForProfitsInput) returns (google.protobuf.Empty) { + } + rpc Withdraw (aelf.Address) returns (google.protobuf.Empty) { + } + rpc ClaimProfits (ClaimProfitsInput) returns (google.protobuf.Empty) { + } + // Views + rpc GetScheme (aelf.Address) returns (TokenHolderProfitScheme) { + option (aelf.is_view) = true; + } + rpc GetProfitsMap (ClaimProfitsInput) returns (ReceivedProfitsMap) { + option (aelf.is_view) = true; + } +} + +message CreateTokenHolderProfitSchemeInput { + string symbol = 1; + int64 minimum_lock_minutes = 2; + map auto_distribute_threshold = 3; +} + +message AddTokenHolderBeneficiaryInput { + aelf.Address beneficiary = 1; + int64 shares = 2; +} + +message RemoveTokenHolderBeneficiaryInput { + aelf.Address beneficiary = 1; + int64 amount = 2; +} + +message ContributeProfitsInput { + aelf.Address scheme_manager = 1; + int64 amount = 2; + string symbol = 3; +} + +message DistributeProfitsInput { + aelf.Address scheme_manager = 1; + map amounts_map = 2; +} + +message RegisterForProfitsInput { + aelf.Address scheme_manager = 1; + int64 amount = 2; +} + +message ClaimProfitsInput { + aelf.Address scheme_manager = 1; + aelf.Address beneficiary = 2; +} + +message TokenHolderProfitScheme { + string symbol = 1; + aelf.Hash scheme_id = 2; + int64 period = 3; + int64 minimum_lock_minutes = 4; + map auto_distribute_threshold = 5; +} + +message ReceivedProfitsMap { + map value = 1; +} \ No newline at end of file diff --git a/protobuf/token_holder_contract_impl.proto b/protobuf/token_holder_contract_impl.proto new file mode 100755 index 00000000..f18cd9bb --- /dev/null +++ b/protobuf/token_holder_contract_impl.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package TokenHolderImpl; + +import "acs1.proto"; +import "token_holder_contract.proto"; + +option csharp_namespace = "AElf.Contracts.TokenHolder"; + +service TokenHolderContractImpl { + + option (aelf.csharp_state) = "AElf.Contracts.TokenHolder.TokenHolderContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "token_holder_contract.proto"; + +} \ No newline at end of file diff --git a/protobuf/transaction_fee.proto b/protobuf/transaction_fee.proto new file mode 100755 index 00000000..ed4ed8bf --- /dev/null +++ b/protobuf/transaction_fee.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package aelf; + +import "aelf/core.proto"; +import "aelf/options.proto"; + +option csharp_namespace = "AElf.Contracts.MultiToken"; + +service TransactionFee { +} + +// Messages + +message TransactionSizeFeeSymbols +{ + repeated TransactionSizeFeeSymbol transaction_size_fee_symbol_list = 1; +} + +message TransactionSizeFeeSymbol +{ + string token_symbol = 1; + int32 base_token_weight = 2; + int32 added_token_weight = 3; +} + +// Events + +message TransactionFeeCharged { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; +} + +message ResourceTokenCharged { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; + aelf.Address contract_address = 3; +} + +message ResourceTokenOwned { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; + aelf.Address contract_address = 3; +} \ No newline at end of file diff --git a/protobuf/treasury_contract.proto b/protobuf/treasury_contract.proto new file mode 100755 index 00000000..7cba0fde --- /dev/null +++ b/protobuf/treasury_contract.proto @@ -0,0 +1,105 @@ +syntax = "proto3"; + +package Treasury; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; +import "authority_info.proto"; + +option csharp_namespace = "AElf.Contracts.Treasury"; + +service TreasuryContract { + option (aelf.csharp_state) = "AElf.Contracts.Treasury.TreasuryContractState"; + + // Action + rpc InitialTreasuryContract (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc InitialMiningRewardProfitItem (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc DonateAll (DonateAllInput) returns (google.protobuf.Empty) { + } + rpc SetDividendPoolWeightSetting (DividendPoolWeightSetting) returns (google.protobuf.Empty){ + } + rpc SetMinerRewardWeightSetting (MinerRewardWeightSetting) returns (google.protobuf.Empty){ + } + rpc UpdateMiningReward (google.protobuf.Int64Value) returns (google.protobuf.Empty){ + } + rpc ChangeTreasuryController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // View + rpc GetWelfareRewardAmountSample (GetWelfareRewardAmountSampleInput) returns (GetWelfareRewardAmountSampleOutput) { + option (aelf.is_view) = true; + } + rpc GetTreasurySchemeId (google.protobuf.Empty) returns (aelf.Hash) { + option (aelf.is_view) = true; + } + rpc GetDividendPoolWeightProportion (google.protobuf.Empty) returns (DividendPoolWeightProportion) { + option (aelf.is_view) = true; + } + rpc GetMinerRewardWeightProportion (google.protobuf.Empty) returns (MinerRewardWeightProportion) { + option (aelf.is_view) = true; + } + rpc GetTreasuryController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } +} + +message RegisterInput { + string token_symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + string connector_weight = 5; +} + +message GetWelfareRewardAmountSampleInput { + repeated int64 value = 1; +} + +message GetWelfareRewardAmountSampleOutput { + repeated int64 value = 1; +} + +message DonateAllInput { + string symbol = 1; +} + +message ReleaseMiningRewardInput { + int64 mined_blocks_count = 1; +} + +message MinerReElectionInformation { + map continual_appointment_times = 1; +} + +message DividendPoolWeightSetting { + int32 citizen_welfare_weight = 1; + int32 backup_subsidy_weight = 2; + int32 miner_reward_weight = 3; +} + +message MinerRewardWeightSetting { + int32 basic_miner_reward_weight = 1; + int32 votes_weight_reward_weight = 2; + int32 re_election_reward_weight = 3; +} + +message SchemeProportionInfo { + aelf.Hash scheme_id = 1; + int32 proportion = 2; +} + +message DividendPoolWeightProportion { + SchemeProportionInfo citizen_welfare_proportion_info = 1; + SchemeProportionInfo backup_subsidy_proportion_info = 2; + SchemeProportionInfo miner_reward_proportion_info = 3; +} + +message MinerRewardWeightProportion { + SchemeProportionInfo basic_miner_reward_proportion_info = 1; + SchemeProportionInfo votes_weight_reward_proportion_info = 2; + SchemeProportionInfo re_election_reward_proportion_info = 3; +} \ No newline at end of file diff --git a/protobuf/treasury_contract_impl.proto b/protobuf/treasury_contract_impl.proto new file mode 100755 index 00000000..8c8606bf --- /dev/null +++ b/protobuf/treasury_contract_impl.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package TreasuryImpl; + +import "aelf/core.proto"; +import "acs1.proto"; +import "acs10.proto"; +import "treasury_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Treasury"; + +service TreasuryContractImpl { + option (aelf.csharp_state) = "AElf.Contracts.Treasury.TreasuryContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "acs10.proto"; + option (aelf.base) = "treasury_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/vote_contract.proto b/protobuf/vote_contract.proto new file mode 100755 index 00000000..736e5690 --- /dev/null +++ b/protobuf/vote_contract.proto @@ -0,0 +1,202 @@ +syntax = "proto3"; + +package Vote; + +import "aelf/core.proto"; +import "google/protobuf/timestamp.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.Vote"; + +service VoteContract { + option (aelf.csharp_state) = "AElf.Contracts.Vote.VoteContractState"; + + // Action + rpc Register (VotingRegisterInput) returns (google.protobuf.Empty) { + } + rpc Vote (VoteInput) returns (google.protobuf.Empty) { + } + rpc Withdraw (WithdrawInput) returns (google.protobuf.Empty) { + } + rpc TakeSnapshot (TakeSnapshotInput) returns (google.protobuf.Empty) { + } + rpc AddOption (AddOptionInput) returns (google.protobuf.Empty) { + } + rpc RemoveOption (RemoveOptionInput) returns (google.protobuf.Empty) { + } + rpc AddOptions (AddOptionsInput) returns (google.protobuf.Empty) { + } + rpc RemoveOptions (RemoveOptionsInput) returns (google.protobuf.Empty) { + } + + // View + rpc GetVotingItem (GetVotingItemInput) returns (VotingItem) { + option (aelf.is_view) = true; + } + rpc GetVotingResult (GetVotingResultInput) returns (VotingResult) { + option (aelf.is_view) = true; + } + rpc GetLatestVotingResult (aelf.Hash) returns (VotingResult) { + option (aelf.is_view) = true; + } + rpc GetVotingRecord (aelf.Hash) returns (VotingRecord) { + option (aelf.is_view) = true; + } + rpc GetVotingRecords (GetVotingRecordsInput) returns (VotingRecords) { + option (aelf.is_view) = true; + } + rpc GetVotedItems (aelf.Address) returns (VotedItems) { + option (aelf.is_view) = true; + } + rpc GetVotingIds (GetVotingIdsInput) returns (VotedIds) { + option (aelf.is_view) = true; + } +} + +// Messages +message VotingRegisterInput { + google.protobuf.Timestamp start_timestamp = 1; + google.protobuf.Timestamp end_timestamp = 2; + string accepted_currency = 3; + bool is_lock_token = 4; + int64 total_snapshot_number = 5; + repeated string options = 6; +} + +message VotingItem { + aelf.Hash voting_item_id = 1; + string accepted_currency = 2; + bool is_lock_token = 3; + int64 current_snapshot_number = 4; + int64 total_snapshot_number = 5; + repeated string options = 6; + google.protobuf.Timestamp register_timestamp = 7; + google.protobuf.Timestamp start_timestamp = 8; + google.protobuf.Timestamp end_timestamp = 9; + google.protobuf.Timestamp current_snapshot_start_timestamp = 10; + aelf.Address sponsor = 11; +} + +message VoteInput { + aelf.Hash voting_item_id = 1; + aelf.Address voter = 2; + int64 amount = 3; + string option = 4; + aelf.Hash vote_id = 5; + bool is_change_target = 6; +} + +message WithdrawInput { + aelf.Hash vote_id = 1; +} + +message GetVotingResultInput { + aelf.Hash voting_item_id = 1; + int64 snapshot_number = 2; +} + +message VotingResult { + aelf.Hash voting_item_id = 1; + map results = 2; // option -> amount + int64 snapshot_number = 3; + int64 voters_count = 4; + google.protobuf.Timestamp snapshot_start_timestamp = 5; + google.protobuf.Timestamp snapshot_end_timestamp = 6; + int64 votes_amount = 7; +} + +message TakeSnapshotInput { + aelf.Hash voting_item_id = 1; + int64 snapshot_number = 2; +} + +message VotingRecord { + aelf.Hash voting_item_id = 1; + aelf.Address voter = 2; + int64 snapshot_number = 3; + int64 amount = 4; + google.protobuf.Timestamp withdraw_timestamp = 5; + google.protobuf.Timestamp vote_timestamp = 6; + bool is_withdrawn = 7; + string option = 8; + bool is_change_target = 9; +} + +message AddOptionInput { + aelf.Hash voting_item_id = 1; + string option = 2; +} + +message RemoveOptionInput { + aelf.Hash voting_item_id = 1; + string option = 2; +} + +message AddOptionsInput { + aelf.Hash voting_item_id = 1; + repeated string options = 2; +} + +message RemoveOptionsInput { + aelf.Hash voting_item_id = 1; + repeated string options = 2; +} + +message VotedItems { + map voted_item_vote_ids = 1; +} + +message VotedIds { + repeated aelf.Hash active_votes = 1; + repeated aelf.Hash withdrawn_votes = 2; +} + +message GetVotingIdsInput { + aelf.Address voter = 1; + aelf.Hash voting_item_id = 2; +} + +message GetVotingItemInput { + aelf.Hash voting_item_id = 1; +} + +message GetVotingRecordsInput { + repeated aelf.Hash ids = 1; +} + +message VotingRecords { + repeated VotingRecord records = 1; +} + +// Events +message Voted { + option (aelf.is_event) = true; + aelf.Hash voting_item_id = 1; + aelf.Address voter = 2; + int64 snapshot_number = 3; + int64 amount = 4; + google.protobuf.Timestamp vote_timestamp = 5; + string option = 6; + aelf.Hash vote_id = 7; +} + +message Withdrawn { + option (aelf.is_event) = true; + aelf.Hash vote_id = 1; +} + +message VotingItemRegistered { + option (aelf.is_event) = true; + aelf.Hash voting_item_id = 1; + string accepted_currency = 2; + bool is_lock_token = 3; + int64 current_snapshot_number = 4; + int64 total_snapshot_number = 5; + google.protobuf.Timestamp register_timestamp = 6; + google.protobuf.Timestamp start_timestamp = 7; + google.protobuf.Timestamp end_timestamp = 8; + google.protobuf.Timestamp current_snapshot_start_timestamp = 9; + aelf.Address sponsor = 10; +} \ No newline at end of file diff --git a/protobuf/vote_contract_impl.proto b/protobuf/vote_contract_impl.proto new file mode 100755 index 00000000..b6aa6ce9 --- /dev/null +++ b/protobuf/vote_contract_impl.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package VoteImp; + +import "acs1.proto"; +import "vote_contract.proto"; + +option csharp_namespace = "AElf.Contracts.Vote"; + +service VoteContractImpl { + option (aelf.csharp_state) = "AElf.Contracts.Vote.VoteContractState"; + option (aelf.base) = "acs1.proto"; + option (aelf.base) = "vote_contract.proto"; +} \ No newline at end of file diff --git a/protobuf/whitelist_contract.proto b/protobuf/whitelist_contract.proto new file mode 100644 index 00000000..011e6cc4 --- /dev/null +++ b/protobuf/whitelist_contract.proto @@ -0,0 +1,608 @@ +syntax = "proto3"; + +package whitelist; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "acs1.proto"; + +option csharp_namespace = "AElf.Contracts.Whitelist"; + +service WhitelistContract { + option (aelf.csharp_state) = "AElf.Contracts.Whitelist.WhitelistContractState"; + option (aelf.base) = "acs1.proto"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + //For Managers. + //Create whitelist. + rpc CreateWhitelist (CreateWhitelistInput) returns (aelf.Hash){ + } + + //Add tag info or extraInfo. + rpc AddExtraInfo (AddExtraInfoInput) returns (aelf.Hash){ + } + + //Add multiple addresses to an existing whitelist. + rpc AddAddressInfoListToWhitelist (AddAddressInfoListToWhitelistInput) returns (google.protobuf.Empty){ + } + + //Remove multiple addresses from an existing whitelist. + rpc RemoveAddressInfoListFromWhitelist (RemoveAddressInfoListFromWhitelistInput) returns (google.protobuf.Empty){ + } + + //Remove address list from whitelist.(Only input addressList) + rpc RemoveInfoFromWhitelist(RemoveInfoFromWhitelistInput) returns (google.protobuf.Empty){ + } + + //Remove tag info. + rpc RemoveTagInfo (RemoveTagInfoInput) returns (google.protobuf.Empty){ + } + + //Disable whitelist according to the whitelist_id. + rpc DisableWhitelist (aelf.Hash) returns (google.protobuf.Empty){ + } + + //Re-enable whitelist according to the whitelist_id. + rpc EnableWhitelist (aelf.Hash) returns (google.protobuf.Empty){ + } + + //Update state: Whether the whitelist is allowed to be cloned. + rpc ChangeWhitelistCloneable(ChangeWhitelistCloneableInput) returns (google.protobuf.Empty){ + } + + //Update whitelist extraInfo according to the whitelist_id and extraInfo. + rpc UpdateExtraInfo (UpdateExtraInfoInput) returns (google.protobuf.Empty){ + } + + //Transfer Manager. + rpc TransferManager (TransferManagerInput) returns (google.protobuf.Empty){ + } + + //Add manager. + rpc AddManagers (AddManagersInput) returns (google.protobuf.Empty){ + } + + //Remove manager. + rpc RemoveManagers (RemoveManagersInput) returns (google.protobuf.Empty){ + } + + //Reset whitelist according to the whitelist_id and project_id. + rpc ResetWhitelist (ResetWhitelistInput) returns (google.protobuf.Empty){ + } + + + //For Subscribers. + //Subscribe whitelist. + rpc SubscribeWhitelist (SubscribeWhitelistInput) returns (aelf.Hash){ + } + + // Cancel subscribe according to the subscribe_id. + rpc UnsubscribeWhitelist (aelf.Hash) returns (google.protobuf.Empty){ + } + + //After used,address and extra info will be added into the consumedList. + rpc ConsumeWhitelist (ConsumeWhitelistInput) returns (google.protobuf.Empty){ + } + + //Clone whitelist. + rpc CloneWhitelist (CloneWhitelistInput) returns (aelf.Hash){ + } + + //Add subscribe whitelist manager. + rpc AddSubscribeManagers (AddSubscribeManagersInput) returns (google.protobuf.Empty){ + } + + //Remove subscribe whitelist manager. + rpc RemoveSubscribeManagers (RemoveSubscribeManagersInput) returns (google.protobuf.Empty){ + } + + + //Views. + //Get whitelist_id list according to the manager. + rpc GetWhitelistByManager (aelf.Address) returns (WhitelistIdList){ + option (aelf.is_view) = true; + } + + //Get existing whitelist according to the whitelist_id. + rpc GetWhitelist (aelf.Hash) returns (WhitelistInfo){ + option (aelf.is_view) = true; + } + + //Get whitelist detail extraInfo according to the whitelist_id. + rpc GetWhitelistDetail (aelf.Hash) returns(ExtraInfoList){ + option (aelf.is_view) = true; + } + + //Get whitelist id by project_id. + rpc GetWhitelistByProject(aelf.Hash) returns (WhitelistIdList){ + option (aelf.is_view) = true; + } + + //Get extraInfo according to the tag_id. + rpc GetExtraInfoByTag (GetExtraInfoByTagInput) returns (ExtraInfo){ + option (aelf.is_view) = true; + } + + //Get tag info according to the tag_info_id. + rpc GetTagInfoByHash (aelf.Hash) returns (TagInfo){ + option (aelf.is_view) = true; + } + + //Get tagInfoId list according to the whitelist_id and project_id. + rpc GetExtraInfoIdList(GetExtraInfoIdListInput) returns (HashList){ + option (aelf.is_view) = true; + } + + rpc GetTagInfoListByWhitelist(GetTagInfoListByWhitelistInput) returns (TagInfoList){ + option (aelf.is_view) = true; + } + + //Get tagId according to the whitelist_id and address. + rpc GetTagIdByAddress(GetTagIdByAddressInput) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + + //Get TagInfo according to the address and whitelist_id. + rpc GetExtraInfoByAddress(GetExtraInfoByAddressInput) returns (TagInfo){ + option (aelf.is_view) = true; + } + + //Whether the address exists in the whitelist according to the whitelist_id and address. + rpc GetAddressFromWhitelist(GetAddressFromWhitelistInput) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } + + //Whether the extraInfo (address+TagInfoId) exists in the whitelist according to the whitelist_id and address. + rpc GetExtraInfoFromWhitelist(GetExtraInfoFromWhitelistInput) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } + + //Whether the tagInfo exists in the whitelist according to the whitelist_id,project_id,tagInfo(tagName,info). + rpc GetTagInfoFromWhitelist(GetTagInfoFromWhitelistInput) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } + + rpc GetWhitelistIdList(GetWhitelistIdListInput) returns (WhitelistIdList){ + option (aelf.is_view) = true; + } + + //Get manager list according to the whitelist_id. + rpc GetManagerList(aelf.Hash) returns (AddressList){ + option (aelf.is_view) = true; + } + + //Get subscribe manager list according to the subscribe_id. + rpc GetSubscribeManagerList(aelf.Hash) returns (AddressList){ + option (aelf.is_view) = true; + } + + //Whether manager exist in whitelist. + rpc GetManagerExistFromWhitelist(GetManagerExistFromWhitelistInput) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } + + //Get subscribe whitelist info according to the subscribe_id. + rpc GetSubscribeWhitelist (aelf.Hash) returns (SubscribeWhitelistInfo){ + option (aelf.is_view) = true; + } + + //Get subscribe_id list according to the manager. + rpc GetSubscribeIdByManager (aelf.Address) returns (HashList){ + option (aelf.is_view) = true; + } + + //Get consumed list according to the subscribe_id. + rpc GetConsumedList (aelf.Hash) returns (ConsumedList){ + option (aelf.is_view) = true; + } + + //After consumed,get available whitelist according to the subscribe_id. + rpc GetAvailableWhitelist (aelf.Hash) returns (ExtraInfoIdList){ + option (aelf.is_view) = true; + } + + //Whether the extraInfo exist in the available whitelist. + rpc GetFromAvailableWhitelist (GetFromAvailableWhitelistInput) returns (google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } + + +} + +//Structs. + +message WhitelistInfo { + //The whitelist id. + aelf.Hash whitelist_id = 1; + //The project id. + aelf.Hash project_id = 2; + //The list of address and extra info in this whitelist. + ExtraInfoIdList extra_info_id_list = 3; + //Whether the whitelist is available. + bool is_available = 4; + //Whether the whiteList can be cloned. + bool is_cloneable = 5; + string remark = 6; + aelf.Hash clone_from = 7; + aelf.Address creator = 8; + AddressList manager = 9; + StrategyType strategy_type = 10; +} + +//Pricing strategy +message PriceTag{ + string symbol = 1; + int64 amount = 2; +} + +enum StrategyType{ + Basic = 0; + Price = 1; + Customize = 2; +} + +message ExtraInfoIdList { + repeated ExtraInfoId value = 1; +} + +message ExtraInfoId { + AddressList address_list = 1; + aelf.Hash id = 2; +} + +message ExtraInfoList { + repeated ExtraInfo value = 1; +} + +message ExtraInfo { + AddressList address_list = 1; + TagInfo info = 2; +} + +message TagInfo { + string tag_name = 1; + bytes info = 2; +} + +message TagInfoList{ + repeated TagInfo value = 1; +} + +message WhitelistIdList{ + repeated aelf.Hash whitelist_id = 1; +} + +message SubscribeWhitelistInfo { + //The subscribe id. + aelf.Hash subscribe_id = 1; + //The project id. + aelf.Hash project_id = 2; + //The whitelist id. + aelf.Hash whitelist_id = 3; + aelf.Address subscriber = 4; + //Manager list. + AddressList manager_list = 5; +} + +message ConsumedList { + //The subscribe id. + aelf.Hash subscribe_id = 1; + //The whitelist id. + aelf.Hash whitelist_id = 2; + //The consumed address and extra info list in this whitelist. + ExtraInfoIdList extra_info_id_list = 3; +} + +message AddressList { + repeated aelf.Address value = 1; +} + +message HashList{ + repeated aelf.Hash value = 1; +} + +//Inputs. + +//message InitializeInput{ +// +//} + +message CreateWhitelistInput { + ExtraInfoList extra_info_list = 1; + bool is_cloneable = 2; + string remark = 3; + aelf.Address creator = 4; + AddressList manager_list = 5; + aelf.Hash project_id = 6; + StrategyType strategy_type = 7; +} + +message AddAddressInfoListToWhitelistInput { + aelf.Hash whitelist_id = 1; + ExtraInfoIdList extra_info_id_list = 2; +} + +message RemoveAddressInfoListFromWhitelistInput{ + aelf.Hash whitelist_id = 1; + ExtraInfoIdList extra_info_id_list = 2; +} + +message RemoveInfoFromWhitelistInput{ + aelf.Hash whitelist_id = 1; + AddressList address_list = 2; +} + +message ChangeWhitelistCloneableInput{ + aelf.Hash whitelist_id = 1; + bool is_cloneable = 2; +} + +message AddExtraInfoInput{ + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; + TagInfo tag_info = 3; + AddressList address_list = 4; +} + +message RemoveTagInfoInput{ + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; + aelf.Hash tag_id = 3; +} + +message UpdateExtraInfoInput{ + aelf.Hash whitelist_id = 1; + ExtraInfoId extra_info_list = 2; +} + +message SubscribeWhitelistInput{ + //The project id. + aelf.Hash project_id = 1; + //The whitelist id. + aelf.Hash whitelist_id = 2; + //Subscriber. + aelf.Address subscriber = 3; + //Manager list. + AddressList manager_list = 4; +} + +message ConsumeWhitelistInput{ + aelf.Hash subscribe_id = 1; + aelf.Hash whitelist_id = 2; + ExtraInfoId extra_info_id = 3; +} + +message CloneWhitelistInput{ + aelf.Hash whitelist_id = 1; + aelf.Address creator = 2; + AddressList manager_list = 3; + aelf.Hash project_id = 4; +} + +message TransferManagerInput{ + aelf.Hash whitelist_id = 1; + aelf.Address manager = 2; +} + +message GetFromAvailableWhitelistInput{ + aelf.Hash subscribe_id = 1; + ExtraInfoId extra_info_id = 2; +} + +message AddManagersInput{ + aelf.Hash whitelist_id = 1; + AddressList manager_list = 2; +} + +message AddSubscribeManagersInput{ + aelf.Hash subscribe_id = 1; + AddressList manager_list = 2; +} + +message RemoveManagersInput{ + aelf.Hash whitelist_id = 1; + AddressList manager_list = 2; +} + +message RemoveSubscribeManagersInput{ + aelf.Hash subscribe_id = 1; + AddressList manager_list = 2; +} + +message GetTagIdByAddressInput{ + aelf.Hash whitelist_id = 1; + aelf.Address address = 2; +} + +message GetExtraInfoByAddressInput{ + aelf.Hash whitelist_id = 1; + aelf.Address address = 2; +} + +message GetExtraInfoIdListInput{ + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; +} + +message GetTagInfoListByWhitelistInput{ + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; +} + +message GetExtraInfoByTagInput{ + aelf.Hash whitelist_id = 1; + aelf.Hash tag_info_id = 2; +} + +message GetAddressFromWhitelistInput{ + aelf.Hash whitelist_id = 1; + aelf.Address address = 2; +} + +message GetExtraInfoFromWhitelistInput{ + aelf.Hash whitelist_id = 1; + ExtraInfoId extra_info_id = 2; +} + +message GetTagInfoFromWhitelistInput{ + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; + TagInfo tag_info = 3; +} + +message GetWhitelistIdListInput{ + aelf.Address address = 1; + WhitelistIdList whitelist_id_list = 2; +} + +message GetManagerExistFromWhitelistInput{ + aelf.Hash whitelist_id = 1; + aelf.Address manager = 2; +} + +message ResetWhitelistInput{ + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; +} + +//Events. + +message WhitelistCreated { + option (aelf.is_event) = true; + //The whitelist id. + aelf.Hash whitelist_id = 1; + //The list of address and extra info in this whitelist. + ExtraInfoIdList extra_info_id_list = 2; + //Whether the whitelist is available. + bool is_available = 3; + bool is_cloneable = 4; + string remark = 5; + aelf.Hash clone_from = 6; + aelf.Address creator = 7; + AddressList manager = 8; + aelf.Hash project_id = 9; + StrategyType strategy_type = 10; + +} + +message WhitelistSubscribed { + option (aelf.is_event) = true; + //The subscribe id. + aelf.Hash subscribe_id = 1; + //The project id. + aelf.Hash project_id = 2; + //The whitelist id. + aelf.Hash whitelist_id = 3; + //Subscriber. + aelf.Address subscriber = 4; + //Manager list. + AddressList manager_list = 5; +} + +message WhitelistUnsubscribed{ + option (aelf.is_event) = true; + aelf.Hash subscribe_id = 1; + aelf.Hash project_id = 2; + aelf.Hash whitelist_id = 3; +} + +message WhitelistAddressInfoAdded { + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + ExtraInfoIdList extra_info_id_list = 2; +} + +message WhitelistAddressInfoRemoved { + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + ExtraInfoIdList extra_info_id_list = 2; +} + +message WhitelistDisabled { + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + bool is_available = 2; +} + +message WhitelistReenable{ + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + bool is_available = 2; +} + +message ConsumedListAdded { + option (aelf.is_event) = true; + aelf.Hash subscribe_id = 1; + aelf.Hash whitelist_id = 2; + ExtraInfoIdList extra_info_id_list = 3; +} + +message IsCloneableChanged{ + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + bool is_cloneable = 2; +} + +message TagInfoAdded{ + option (aelf.is_event) = true; + aelf.Hash tag_info_id = 1; + TagInfo tag_info = 2; + aelf.Hash project_id = 3; + aelf.Hash whitelist_id = 4; +} + +message TagInfoRemoved{ + option (aelf.is_event) = true; + aelf.Hash tag_info_id = 1; + TagInfo tag_info = 2; + aelf.Hash project_id = 3; + aelf.Hash whitelist_id = 4; +} + +message ExtraInfoUpdated { + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + ExtraInfoId extra_info_id_before = 2; + ExtraInfoId extra_info_id_after = 3; +} + +message ManagerTransferred{ + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + aelf.Address transfer_from = 2; + aelf.Address transfer_to = 3; +} + +message ManagerAdded{ + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + AddressList manager_list = 2; +} + +message ManagerRemoved{ + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + AddressList manager_list = 2; +} + +message WhitelistReset{ + option (aelf.is_event) = true; + aelf.Hash whitelist_id = 1; + aelf.Hash project_id = 2; +} + +message SubscribeManagerAdded{ + option (aelf.is_event) = true; + aelf.Hash subscribe_id = 1; + AddressList manager_list = 2; +} + +message SubscribeManagerRemoved{ + option (aelf.is_event) = true; + aelf.Hash subscribe_id = 1; + AddressList manager_list = 2; +} \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..d9ba348d --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +dotnet restore -s "https://nuget.cdn.azure.cn/v3/index.json" -s "https://api.nuget.org/v3/index.json" -v quiet "AElf.sln" +dotnet build /clp:ErrorsOnly /p:GeneratePackageOnBuild=false -v quiet "AElf.sln" + +if [[ $? -ne 0 ]] ; then + echo "Build failed." + exit 1 +fi diff --git a/scripts/deploy_docker.sh b/scripts/deploy_docker.sh new file mode 100755 index 00000000..0fea2b5e --- /dev/null +++ b/scripts/deploy_docker.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -ev + +TAG=$1 +DOCKER_USERNAME=$2 +DOCKER_PASSWORD=$3 + +# publish docker +# AElf node +dotnet publish AElf.sln /clp:ErrorsOnly -c Release -o ~/aelf/ + +docker build -t aelf/node:${TAG} ~/aelf/. +docker tag aelf/node:${TAG} aelf/node:latest +docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" +docker push aelf/node:${TAG} +docker push aelf/node:latest diff --git a/scripts/deploy_myget.sh b/scripts/deploy_myget.sh new file mode 100755 index 00000000..6f0f7242 --- /dev/null +++ b/scripts/deploy_myget.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -ev + +TAG=$1 +MYGET_API_KEY=$2 +VERSION=`echo ${TAG} | cut -b 2-` + +src_path=src/ +contract_path=contract/ +build_output=/tmp/aelf-build + +if [[ -d ${build_output} ]]; then + rm -rf ${build_output} +fi + +dotnet restore AElf.sln + +for path in ${src_path} ${contract_path} ; +do + cd ${path} + echo '---- build '${path} + + for name in `ls -lh | grep ^d | grep AElf | grep -v Tests| awk '{print $NF}'`; + do + if [[ -f ${name}/${name}.csproj ]] && [[ 1 -eq $(grep -c "GeneratePackageOnBuild" ${name}/${name}.csproj) ]];then + echo ${name}/${name}.csproj + dotnet build /clp:ErrorsOnly ${name}/${name}.csproj --configuration Release -P:Version=${VERSION} -P:Authors=AElf -o ${build_output} + + echo `ls ${build_output}/${name}.${VERSION}.nupkg` + dotnet nuget push ${build_output}/${name}.${VERSION}.nupkg -k ${MYGET_API_KEY} -s https://www.myget.org/F/aelf-project/api/v3/index.json + fi + done + cd ../ +done diff --git a/scripts/deploy_myget_daily.sh b/scripts/deploy_myget_daily.sh new file mode 100755 index 00000000..295e6e19 --- /dev/null +++ b/scripts/deploy_myget_daily.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -ev + +VERSION_PREFIX=$1 +MYGET_API_KEY=$2 + +# days since 1970-1-1 as build version +BUILD_VERSION=`expr $(date +%s) / 86400` +VERSION=${VERSION_PREFIX}-${BUILD_VERSION} + +src_path=src/ +contract_path=contract/ +build_output=/tmp/aelf-build + +if [[ -d ${build_output} ]]; then + rm -rf ${build_output} +fi + +dotnet restore AElf.sln + +for path in ${src_path} ${contract_path} ; +do + cd ${path} + echo '---- build '${path} + + for name in `ls -lh | grep ^d | grep AElf | grep -v Tests| awk '{print $NF}'`; + do + if [[ -f ${name}/${name}.csproj ]] && [[ 1 -eq $(grep -c "GeneratePackageOnBuild" ${name}/${name}.csproj) ]];then + echo ${name}/${name}.csproj + dotnet build /clp:ErrorsOnly ${name}/${name}.csproj --configuration Release -P:Version=${VERSION} -P:Authors=AElf -o ${build_output} + + echo `ls ${build_output}/${name}.${VERSION}.nupkg` + dotnet nuget push ${build_output}/${name}.${VERSION}.nupkg -k ${MYGET_API_KEY} -s https://www.myget.org/F/aelf-project-dev/api/v3/index.json + fi + done + cd ../ +done diff --git a/scripts/deploy_nuget.sh b/scripts/deploy_nuget.sh new file mode 100755 index 00000000..290c6710 --- /dev/null +++ b/scripts/deploy_nuget.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -ev + +TAG=$1 +NUGET_API_KEY=$2 +VERSION=`echo ${TAG} | cut -b 2-` + +src_path=src +contract_path=contract +build_output=/tmp/aelf-build + +if [[ -d ${build_output} ]]; then + rm -rf ${build_output} +fi + +dotnet restore AElf.sln + +for path in ${src_path} ${contract_path} ; +do + cd ${path} + echo '---- build '${path} + + for name in `ls -lh | grep ^d | grep AElf | grep -v Tests | awk '{print $NF}'`; + do + if [[ -f ${name}/${name}.csproj ]] && [[ 1 -eq $(grep -c "GeneratePackageOnBuild" ${name}/${name}.csproj) ]];then + echo ${name}/${name}.csproj + dotnet build ${name}/${name}.csproj /clp:ErrorsOnly --configuration Release -P:Version=${VERSION} -P:Authors=AElf -o ${build_output} + + echo `ls ${build_output}/${name}.${VERSION}.nupkg` + dotnet nuget push ${build_output}/${name}.${VERSION}.nupkg -k ${NUGET_API_KEY} -s https://api.nuget.org/v3/index.json + fi + done + cd ../ +done diff --git a/scripts/download_binary.bat b/scripts/download_binary.bat new file mode 100755 index 00000000..02382a3b --- /dev/null +++ b/scripts/download_binary.bat @@ -0,0 +1,16 @@ +SET scriptdir=%~dp0 + +SET version=v1.0.3 +SET filename=contract_csharp_plugin-%version%-win32.zip + +SET url=https://github.com/AElfProject/contract-plugin/releases/download/%version%/%filename% +SET file=%scriptdir%%filename% + +if not exist "%scriptdir%contract_csharp_plugin.exe" ( + if not exist "%file%" ( + echo "download contract plugin from: %url%" + powershell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%url%' -OutFile '%file%'" + ) + echo "unzip file: %file%" + unzip %scriptdir%%filename% -d %scriptdir% +) diff --git a/scripts/download_binary.sh b/scripts/download_binary.sh new file mode 100755 index 00000000..49b8fdbe --- /dev/null +++ b/scripts/download_binary.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +scriptdir=`dirname "$0"` + +d="Darwin" +l="Linux" +if [[ "$(uname -s | grep ${d})" != "" ]]; then + osn="macosx" +elif [[ "$(uname -s | grep ${l})" != "" ]]; then + osn="linux" +else + osn="windows" +fi + +plugin="${scriptdir}/contract_csharp_plugin" + +version="v1.0.3" + +if [[ ! -f ${plugin} ]]; then + if [[ ${osn} == "macosx" ]]; then + suffix="osx" + elif [[ ${osn} == "linux" ]]; then + suffix="linux" + fi + filename=contract_csharp_plugin-${version}-${suffix}.zip + # Make sure you grab the latest version + if [[ ! -f ${filename} ]]; then + echo "download contract plugin ${filename} from github" + curl -OL https://github.com/AElfProject/contract-plugin/releases/download/${version}/${filename} + fi + + # Unzip + unzip -o ${filename} -d "${scriptdir}" +fi diff --git a/scripts/generate_contract_base.bat b/scripts/generate_contract_base.bat new file mode 100755 index 00000000..57ca1664 --- /dev/null +++ b/scripts/generate_contract_base.bat @@ -0,0 +1,11 @@ +SET scriptdir=%~dp0 + +call "%scriptdir%download_binary.bat" + +protoc --proto_path=../../protobuf ^ +--csharp_out=%2:./Protobuf\Generated ^ +--csharp_opt=file_extension=.g.cs ^ +--contract_opt=%2,nocontract ^ +--contract_out=./Protobuf/Generated ^ +--plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ +%1 diff --git a/scripts/generate_contract_base.sh b/scripts/generate_contract_base.sh new file mode 100755 index 00000000..28a1550d --- /dev/null +++ b/scripts/generate_contract_base.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +scriptdir=`dirname "$0"` + +bash "${scriptdir}/download_binary.sh" + +plugin="${scriptdir}/contract_csharp_plugin" + +solutiondir=`dirname ${scriptdir}` + +protoc --proto_path=${solutiondir}/protobuf \ +--csharp_out="$2":./Protobuf/Generated \ +--csharp_opt=file_extension=.g.cs \ +--contract_opt="$2",nocontract \ +--contract_out=./Protobuf/Generated \ +--plugin=protoc-gen-contract=${plugin} \ +$1 diff --git a/scripts/generate_contract_code.bat b/scripts/generate_contract_code.bat new file mode 100755 index 00000000..3b95aad7 --- /dev/null +++ b/scripts/generate_contract_code.bat @@ -0,0 +1,10 @@ +SET scriptdir=%~dp0 + +call "%scriptdir%download_binary.bat" + +protoc --proto_path=../../protobuf ^ +--csharp_out=./Protobuf\Generated ^ +--csharp_opt=file_extension=.g.cs ^ +--contract_out=./Protobuf/Generated ^ +--plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ +%* diff --git a/scripts/generate_contract_code.sh b/scripts/generate_contract_code.sh new file mode 100755 index 00000000..60093c9a --- /dev/null +++ b/scripts/generate_contract_code.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +scriptdir=`dirname "$0"` + +bash "${scriptdir}/download_binary.sh" + +plugin="${scriptdir}/contract_csharp_plugin" + +solutiondir=`dirname ${scriptdir}` + +protoc --proto_path=${solutiondir}/protobuf \ +--csharp_out=./Protobuf/Generated \ +--csharp_opt=file_extension=.g.cs \ +--contract_out=./Protobuf/Generated \ +--plugin=protoc-gen-contract=${plugin} \ +$@ diff --git a/scripts/generate_contract_reference.bat b/scripts/generate_contract_reference.bat new file mode 100755 index 00000000..97fee83d --- /dev/null +++ b/scripts/generate_contract_reference.bat @@ -0,0 +1,11 @@ +SET scriptdir=%~dp0 + +call "%scriptdir%download_binary.bat" + +protoc --proto_path=../../protobuf ^ +--csharp_out=internal_access:./Protobuf\Generated ^ +--csharp_opt=file_extension=.g.cs ^ +--contract_opt=reference ^ +--contract_out=internal_access:./Protobuf/Generated ^ +--plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ +%* diff --git a/scripts/generate_contract_reference.sh b/scripts/generate_contract_reference.sh new file mode 100755 index 00000000..47f8027e --- /dev/null +++ b/scripts/generate_contract_reference.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +scriptdir=`dirname "$0"` + +bash "${scriptdir}/download_binary.sh" + +plugin="${scriptdir}/contract_csharp_plugin" + +solutiondir=`dirname ${scriptdir}` + +protoc --proto_path=${solutiondir}/protobuf \ +--csharp_out=internal_access:./Protobuf/Generated \ +--csharp_opt=file_extension=.g.cs \ +--contract_opt=reference \ +--contract_out=internal_access:./Protobuf/Generated \ +--plugin=protoc-gen-contract="${plugin}" \ +$@ diff --git a/scripts/generate_contract_stub.bat b/scripts/generate_contract_stub.bat new file mode 100755 index 00000000..9e1120e8 --- /dev/null +++ b/scripts/generate_contract_stub.bat @@ -0,0 +1,12 @@ +SET scriptdir=%~dp0 + +call "%scriptdir%download_binary.bat" + +protoc --proto_path=../../protobuf ^ +--csharp_out=internal_access:./Protobuf\Generated ^ +--csharp_opt=file_extension=.g.cs ^ +--contract_opt=stub ^ +--contract_opt=internal_access ^ +--contract_out=./Protobuf/Generated ^ +--plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ +%* diff --git a/scripts/generate_contract_stub.sh b/scripts/generate_contract_stub.sh new file mode 100755 index 00000000..ac303fab --- /dev/null +++ b/scripts/generate_contract_stub.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +scriptdir=`dirname "$0"` + +bash "${scriptdir}/download_binary.sh" + +plugin="${scriptdir}/contract_csharp_plugin" + +destdir=./Protobuf/Generated + +[ -d ${destdir} ] || mkdir -p ${destdir} + +solutiondir=`dirname ${scriptdir}` + +protoc --proto_path=${solutiondir}/protobuf \ +--csharp_out=internal_access:${destdir} \ +--csharp_opt=file_extension=.g.cs \ +--contract_opt=stub \ +--contract_opt=internal_access \ +--contract_out=${destdir} \ +--plugin=protoc-gen-contract="${plugin}" \ +$@ diff --git a/scripts/generate_event_only.bat b/scripts/generate_event_only.bat new file mode 100755 index 00000000..31dd3e1d --- /dev/null +++ b/scripts/generate_event_only.bat @@ -0,0 +1,11 @@ +SET scriptdir=%~dp0 + +call "%scriptdir%download_binary.bat" + +protoc --proto_path=../../protobuf ^ +--csharp_out=internal_access:./Protobuf\Generated ^ +--csharp_opt=file_extension=.g.cs ^ +--contract_opt=nocontract ^ +--contract_out=internal_access:./Protobuf/Generated ^ +--plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ +%* diff --git a/scripts/generate_event_only.sh b/scripts/generate_event_only.sh new file mode 100755 index 00000000..db7d22d0 --- /dev/null +++ b/scripts/generate_event_only.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +scriptdir=`dirname "$0"` + +bash "${scriptdir}/download_binary.sh" + +plugin="${scriptdir}/contract_csharp_plugin" + +solutiondir=`dirname ${scriptdir}` + +protoc --proto_path=${solutiondir}/protobuf \ +--csharp_out=internal_access:./Protobuf/Generated \ +--csharp_opt=file_extension=.g.cs \ +--contract_opt=nocontract \ +--contract_out=internal_access:./Protobuf/Generated \ +--plugin=protoc-gen-contract=${plugin} \ +$@ diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100755 index 00000000..f200223f --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,5 @@ +wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-win64.zip -OutFile C:\protoc-3.11.4-win64.zip; +Expand-Archive -Path C:\protoc-3.11.4-win64.zip -DestinationPath C:\protoc ; +cp -r C:\protoc\include\ C:\WINDOWS\; +cp C:\protoc\bin\protoc.exe C:\WINDOWS\; +protoc --version; diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 00000000..941fd8e0 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +d="Darwin" +l="Linux" +if [[ "$(uname -s | grep ${d})" != "" ]]; then + osn="macosx" +elif [[ "$(uname -s | grep ${l})" != "" ]]; then + osn="linux" +else + osn="windows" +fi + +echo "Install protobuf on ${osn}" + +if [[ ${osn} == "macosx" ]]; then + curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-osx-x86_64.zip + unzip protoc-3.11.4-osx-x86_64.zip -d protoc3 + sudo mv protoc3/bin/* /usr/local/bin/ + sudo mv protoc3/include/* /usr/local/include + sudo chown ${USER} /usr/local/bin/protoc + sudo chown -R ${USER} /usr/local/include/google +elif [[ ${osn} == "linux" ]]; then + # Make sure you grab the latest version + curl -OL https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip + + # Unzip + unzip protoc-3.11.4-linux-x86_64.zip -d protoc3 + + # Move protoc to /usr/local/bin/ + sudo mv protoc3/bin/* /usr/local/bin/ + + # Move protoc3/include to /usr/local/include/ + sudo mv protoc3/include/* /usr/local/include/ + + # Optional: change owner + sudo chown ${USER} /usr/local/bin/protoc + sudo chown -R ${USER} /usr/local/include/google +fi \ No newline at end of file diff --git a/scripts/install_choco.ps1 b/scripts/install_choco.ps1 new file mode 100755 index 00000000..a2aa6de4 --- /dev/null +++ b/scripts/install_choco.ps1 @@ -0,0 +1,4 @@ +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +choco upgrade protoc -y +choco upgrade unzip -y \ No newline at end of file diff --git a/scripts/install_dependency.ps1 b/scripts/install_dependency.ps1 new file mode 100755 index 00000000..a2aa6de4 --- /dev/null +++ b/scripts/install_dependency.ps1 @@ -0,0 +1,4 @@ +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +choco upgrade protoc -y +choco upgrade unzip -y \ No newline at end of file diff --git a/scripts/install_protobuf.sh b/scripts/install_protobuf.sh new file mode 100755 index 00000000..0d6400db --- /dev/null +++ b/scripts/install_protobuf.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +d="Darwin" +l="Linux" +if [[ "$(uname -s | grep ${d})" != "" ]]; then + osn="macosx" +elif [[ "$(uname -s | grep ${l})" != "" ]]; then + osn="linux" +else + osn="windows" +fi + +echo "Install protobuf on ${osn}" + +if [[ ${osn} == "macosx" ]]; then + curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-osx-x86_64.zip + unzip protoc-3.11.4-osx-x86_64.zip -d protoc3 + sudo mv protoc3/bin/* /usr/local/bin/ + sudo mv protoc3/include/* /usr/local/include + sudo chown ${USER} /usr/local/bin/protoc + sudo chown -R ${USER} /usr/local/include/google +elif [[ ${osn} == "linux" ]]; then + # Make sure you grab the latest version + curl -OL https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip + + # Unzip + unzip protoc-3.11.4-linux-x86_64.zip -d protoc3 + + # Move protoc to /usr/local/bin/ + sudo mv protoc3/bin/* /usr/local/bin/ + + # Move protoc3/include to /usr/local/include/ + sudo mv protoc3/include/* /usr/local/include/ + + # Optional: change owner + sudo chown ${USER} /usr/local/bin/protoc + sudo chown -R ${USER} /usr/local/include/google +fi diff --git a/scripts/template_script/gen_template.sh b/scripts/template_script/gen_template.sh new file mode 100755 index 00000000..ea0b67c2 --- /dev/null +++ b/scripts/template_script/gen_template.sh @@ -0,0 +1,17 @@ +#!/bin/bash +CONTRACT_NAME=$1 +echo $CONTRACT_NAME +echo "${0%/*}" + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +echo "Current script directory: $DIR" + +parentdir="$(dirname "$DIR")" +chain_folder_path="$(dirname "$parentdir")" +echo $chain_folder_path + +sed "s|{{ContractName}}|$CONTRACT_NAME|g" $chain_folder_path/scripts/template_script/template.proto > $chain_folder_path/scripts/template_script/temp.proto +cp $chain_folder_path/scripts/template_script/temp.proto $chain_folder_path/protobuf/$CONTRACT_NAME.proto +rm $chain_folder_path/scripts/template_script/temp.proto + + diff --git a/scripts/template_script/template.proto b/scripts/template_script/template.proto new file mode 100755 index 00000000..4d240957 --- /dev/null +++ b/scripts/template_script/template.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +import "aelf/options.proto"; + +// some common options +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.{{ContractName}}"; + +service {{ContractName}}Contract { + option (aelf.csharp_state) = "AElf.Contracts.{{ContractName}}.{{ContractName}}ContractState"; + + // actions + + // views +} + +// Define some types \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 00000000..79224159 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +cd test/ + +for i in *Tests ; do + echo "" + echo "### Executing Tests for $i:" + + time dotnet test "$i" --no-build /p:CollectCoverage=true /p:CoverletOutputFormat='json%2copencover' \ + /p:CoverletOutput="../results/coverage" /p:MergeWith="../results/coverage.json" \ + /p:Exclude="[coverlet.*.tests?]*%2c[xunit.*]*%2c[AElf.Kernel.Consensus.Scheduler.*]*%2c[AElf.Database]AElf.Database.RedisProtocol.*%2c[AElf.Contracts.Authorization]*%2c[AElf.Test.Helpers]*%2c[*]*Exception%2c[*.Tests]*%2c[AElf.Contracts.TestContract.BasicFunctionWithParallel]*%2c[AElf.Contracts.GenesisUpdate]*" \ + /p:ExcludeByFile="../../src/AElf.Runtime.CSharp.Core/Metadata/*.cs%2c../../src/AElf.Kernel.SmartContract/Metadata/*.cs%2c../../src/AElf.Database/RedisDatabase.cs%2c../../test/*.TestBase/*.cs" \ + > /tmp/test.log + + if [[ $? -ne 0 ]] ; then + echo "Test Run Failed." + cat /tmp/test.log + exit 1 + fi + + echo "Test Run Successful." + +done diff --git a/scripts/upload_coverage.sh b/scripts/upload_coverage.sh new file mode 100755 index 00000000..082982d9 --- /dev/null +++ b/scripts/upload_coverage.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +curl -s https://codecov.io/bash > codecov +chmod +x codecov +./codecov -f "../test/results/coverage.opencover.xml" -t $1 diff --git a/src/AElf.Boilerplate.ContractPatcher/AElf.Boilerplate.ContractPatcher.csproj b/src/AElf.Boilerplate.ContractPatcher/AElf.Boilerplate.ContractPatcher.csproj new file mode 100755 index 00000000..67ca1240 --- /dev/null +++ b/src/AElf.Boilerplate.ContractPatcher/AElf.Boilerplate.ContractPatcher.csproj @@ -0,0 +1,32 @@ + + + + Exe + net6.0 + Exe + Smart contract deployer. + + + TRACE;UNIT_TEST + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AElf.Boilerplate.ContractPatcher/ContractPatcherModule.cs b/src/AElf.Boilerplate.ContractPatcher/ContractPatcherModule.cs new file mode 100755 index 00000000..40224823 --- /dev/null +++ b/src/AElf.Boilerplate.ContractPatcher/ContractPatcherModule.cs @@ -0,0 +1,12 @@ +using AElf.CSharp.CodeOps; +using AElf.Modularity; +using Volo.Abp.Modularity; + +namespace AElf.Boilerplate.ContractPatcher +{ + [DependsOn(typeof(CSharpCodeOpsAElfModule))] + public class ContractPatcherModule : AElfModule + { + + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.ContractPatcher/Program.cs b/src/AElf.Boilerplate.ContractPatcher/Program.cs new file mode 100755 index 00000000..259fca5e --- /dev/null +++ b/src/AElf.Boilerplate.ContractPatcher/Program.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.IO; +using AElf.CSharp.CodeOps; +using AElf.Kernel.CodeCheck.Infrastructure; +using CommandLine; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; + +namespace AElf.Boilerplate.ContractPatcher +{ + class Program + { + class Options + { + [Option('s', "skipaudit", Default = false, HelpText = "Skip performing code check on contract code.")] + public bool SkipAudit { get; set; } + + [Option('w', "overwrite", Default = false, + HelpText = "Overwrite contract's DLL instead of saving with .patched extension.")] + public bool Overwrite { get; set; } + + [Option('t', "treatsystem", Default = false, HelpText = "Treat as system contract when patching.")] + public bool IsSystemContract { get; set; } + + [Option('p', "path", Required = true, HelpText = "The path of the contract's DLL.")] + public string ContractDllPath { get; set; } + } + + static void Main(string[] args) + { + Parser.Default.ParseArguments(args) + .WithParsed(Run) + .WithNotParsed(Error); + } + + private static void Error(IEnumerable errors) + { + Console.WriteLine("error: Problem parsing input parameters to run contract deployer."); + } + + private static void Run(Options o) + { + string saveAsPath; + + if (!File.Exists(o.ContractDllPath)) + { + Console.WriteLine($"error: Contract DLL cannot be found in specified path {o.ContractDllPath}"); + return; + } + + if (o.Overwrite) + { + saveAsPath = o.ContractDllPath; + Console.WriteLine($"[CONTRACT-PATCHER] Overwriting {saveAsPath}"); + } + else + { + saveAsPath = o.ContractDllPath + ".patched"; + Console.WriteLine($"[CONTRACT-PATCHER] Saving as {saveAsPath}"); + } + + using var application = AbpApplicationFactory.Create(); + application.Initialize(); + var contractPatcher = application.ServiceProvider.GetRequiredService(); + var patchedCode = contractPatcher.Patch(File.ReadAllBytes(o.ContractDllPath), o.IsSystemContract); + + if (!o.SkipAudit) + { + try + { + var auditor = application.ServiceProvider.GetRequiredService(); + auditor.Audit(patchedCode, null, o.IsSystemContract); + } + catch (CSharpCodeCheckException ex) + { + foreach (var finding in ex.Findings) + { + // Print error in parsable format so that it can be shown in IDE + Console.WriteLine($"error: {finding.ToString()}"); + } + } + } + + File.WriteAllBytes(saveAsPath, patchedCode); + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.MainChain/AElf.Boilerplate.MainChain.csproj b/src/AElf.Boilerplate.MainChain/AElf.Boilerplate.MainChain.csproj new file mode 100755 index 00000000..8bfb6479 --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/AElf.Boilerplate.MainChain.csproj @@ -0,0 +1,162 @@ + + + net6.0 + AElf.Boilerplate.MainChain + + + + + + + + + + + + + 0436 + + + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + + + Protobuf\Proto\authority_info.proto + + + + + Protobuf\Proto\acs3.proto + + + Protobuf\Proto\acs1.proto + + + Protobuf\Proto\acs4.proto + + + Protobuf\Proto\acs5.proto + + + Protobuf\Proto\acs6.proto + + + Protobuf\Proto\acs7.proto + + + Protobuf\Proto\acs10.proto + + + Protobuf\Proto\aedpos_contract.proto + + + Protobuf\Proto\aedpos_contract_impl.proto + + + Protobuf\Proto\cross_chain_contract.proto + + + Protobuf\Proto\profit_contract.proto + + + Protobuf\Proto\election_contract.proto + + + Protobuf\Proto\parliament_contract.proto + + + Protobuf\Proto\token_contract.proto + + + Protobuf\Proto\vote_contract.proto + + + Protobuf\Proto\treasury_contract.proto + + + Protobuf\Proto\token_converter_contract.proto + + + Protobuf\Proto\economic_contract.proto + + + Protobuf\Proto\referendum_contract.proto + + + Protobuf\Proto\association_contract.proto + + + Protobuf\Proto\configuration_contract.proto + + + diff --git a/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/AEDPoSContractInitializationDataProvider.cs b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/AEDPoSContractInitializationDataProvider.cs new file mode 100755 index 00000000..412d412c --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/AEDPoSContractInitializationDataProvider.cs @@ -0,0 +1,30 @@ +using AElf.Kernel.Consensus.AEDPoS; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace AElf.Boilerplate.MainChain +{ + // ReSharper disable once InconsistentNaming + public class AEDPoSContractInitializationDataProvider : IAEDPoSContractInitializationDataProvider, + ITransientDependency + { + private readonly ConsensusOptions _consensusOptions; + + public AEDPoSContractInitializationDataProvider(IOptionsSnapshot consensusOptions) + { + _consensusOptions = consensusOptions.Value; + } + + public AEDPoSContractInitializationData GetContractInitializationData() + { + return new AEDPoSContractInitializationData + { + MiningInterval = _consensusOptions.MiningInterval, + PeriodSeconds = _consensusOptions.PeriodSeconds, + StartTimestamp = _consensusOptions.StartTimestamp, + InitialMinerList = _consensusOptions.InitialMinerList, + MinerIncreaseInterval = _consensusOptions.MinerIncreaseInterval + }; + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/CrossChainContractInitializationDataProvider.cs b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/CrossChainContractInitializationDataProvider.cs new file mode 100755 index 00000000..82e36e05 --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/CrossChainContractInitializationDataProvider.cs @@ -0,0 +1,17 @@ +using AElf.CrossChain; +using Volo.Abp.DependencyInjection; + +namespace AElf.Boilerplate.MainChain +{ + public class CrossChainContractInitializationDataProvider : ICrossChainContractInitializationDataProvider, + ITransientDependency + { + public CrossChainContractInitializationData GetContractInitializationData() + { + return new CrossChainContractInitializationData + { + IsPrivilegePreserved = true + }; + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/MainChainGenesisSmartContractDtoProvider.cs b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/MainChainGenesisSmartContractDtoProvider.cs new file mode 100755 index 00000000..4b3ea9ee --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/MainChainGenesisSmartContractDtoProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using AElf.Blockchains.BasicBaseChain; +using AElf.ContractDeployer; +using AElf.Kernel.SmartContract; +using AElf.Kernel.SmartContract.Application; +using Microsoft.Extensions.Options; + +namespace AElf.Boilerplate.MainChain +{ + public class MainChainGenesisSmartContractDtoProvider : GenesisSmartContractDtoProviderBase + { + private readonly ContractOptions _contractOptions; + + public MainChainGenesisSmartContractDtoProvider(IContractDeploymentListProvider contractDeploymentListProvider, + IServiceContainer contractInitializationProviders, + IOptionsSnapshot contractOptions) + : base(contractDeploymentListProvider, contractInitializationProviders) + { + _contractOptions = contractOptions.Value; + } + + protected override IReadOnlyDictionary GetContractCodes() + { + return ContractsDeployer.GetContractCodes(_contractOptions + .GenesisContractDir); + } + } +} diff --git a/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/ParliamentContractInitializationDataProvider.cs b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/ParliamentContractInitializationDataProvider.cs new file mode 100755 index 00000000..a4a190e3 --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/ParliamentContractInitializationDataProvider.cs @@ -0,0 +1,15 @@ +using AElf.GovernmentSystem; +using AElf.Kernel.Proposal; +using Volo.Abp.DependencyInjection; + +namespace AElf.Boilerplate.MainChain +{ + public class ParliamentContractInitializationDataProvider : IParliamentContractInitializationDataProvider, + ITransientDependency + { + public ParliamentContractInitializationData GetContractInitializationData() + { + return new ParliamentContractInitializationData(); + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/TokenContractInitializationDataProvider.cs b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/TokenContractInitializationDataProvider.cs new file mode 100755 index 00000000..751c976c --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/ContractInitDataProviders/TokenContractInitializationDataProvider.cs @@ -0,0 +1,12 @@ +using AElf.Kernel.Token; + +namespace AElf.Boilerplate.MainChain +{ + public class TokenContractInitializationDataProvider : ITokenContractInitializationDataProvider + { + public TokenContractInitializationData GetContractInitializationData() + { + return null; + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.MainChain/MainChainAElfModule.cs b/src/AElf.Boilerplate.MainChain/MainChainAElfModule.cs new file mode 100755 index 00000000..b6359418 --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/MainChainAElfModule.cs @@ -0,0 +1,41 @@ +using AElf.Blockchains.BasicBaseChain; +using AElf.Boilerplate.SystemTransactionGenerator; +using AElf.Database; +using AElf.Kernel.Infrastructure; +using AElf.Kernel.SmartContract; +using AElf.Kernel.SmartContract.Application; +using AElf.Modularity; +using AElf.OS.Node.Application; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Modularity; + +namespace AElf.Boilerplate.MainChain +{ + [DependsOn( + typeof(BasicBaseChainAElfModule), + typeof(SystemTransactionGeneratorModule) + )] + public class MainChainAElfModule : AElfModule + { + public ILogger Logger { get; set; } + + public MainChainAElfModule() + { + Logger = NullLogger.Instance; + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var services = context.Services; + services.AddTransient(); + services.AddTransient(); + + services.AddKeyValueDbContext(p => p.UseInMemoryDatabase()); + services.AddKeyValueDbContext(p => p.UseInMemoryDatabase()); + + Configure(o => o.ContractDeploymentAuthorityRequired = false); + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.MainChain/MainChainContractDeploymentListProvider.cs b/src/AElf.Boilerplate.MainChain/MainChainContractDeploymentListProvider.cs new file mode 100755 index 00000000..8ab710f4 --- /dev/null +++ b/src/AElf.Boilerplate.MainChain/MainChainContractDeploymentListProvider.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using AElf.CrossChain; +using AElf.EconomicSystem; +using AElf.GovernmentSystem; +using AElf.Kernel.Configuration; +using AElf.Kernel.Consensus; +using AElf.Kernel.Proposal; +using AElf.Kernel.SmartContract.Application; +using AElf.Kernel.Token; +using AElf.Types; + +namespace AElf.Boilerplate.MainChain +{ + public class MainChainContractDeploymentListProvider : IContractDeploymentListProvider + { + public List GetDeployContractNameList() + { + return new List + { + VoteSmartContractAddressNameProvider.Name, + ProfitSmartContractAddressNameProvider.Name, + ElectionSmartContractAddressNameProvider.Name, + TreasurySmartContractAddressNameProvider.Name, + ParliamentSmartContractAddressNameProvider.Name, + AssociationSmartContractAddressNameProvider.Name, + ReferendumSmartContractAddressNameProvider.Name, + TokenSmartContractAddressNameProvider.Name, + CrossChainSmartContractAddressNameProvider.Name, + ConfigurationSmartContractAddressNameProvider.Name, + ConsensusSmartContractAddressNameProvider.Name, + TokenConverterSmartContractAddressNameProvider.Name, + TokenHolderSmartContractAddressNameProvider.Name, + EconomicSmartContractAddressNameProvider.Name + }; + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.SystemTransactionGenerator/AElf.Boilerplate.SystemTransactionGenerator.csproj b/src/AElf.Boilerplate.SystemTransactionGenerator/AElf.Boilerplate.SystemTransactionGenerator.csproj new file mode 100755 index 00000000..be31216b --- /dev/null +++ b/src/AElf.Boilerplate.SystemTransactionGenerator/AElf.Boilerplate.SystemTransactionGenerator.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + AElf.Boilerplate.SystemTransactionGenerator + + + + + + + + + + \ No newline at end of file diff --git a/src/AElf.Boilerplate.SystemTransactionGenerator/SystemTransactionGeneratorModule.cs b/src/AElf.Boilerplate.SystemTransactionGenerator/SystemTransactionGeneratorModule.cs new file mode 100755 index 00000000..8a1801c6 --- /dev/null +++ b/src/AElf.Boilerplate.SystemTransactionGenerator/SystemTransactionGeneratorModule.cs @@ -0,0 +1,14 @@ +using AElf.Modularity; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace AElf.Boilerplate.SystemTransactionGenerator +{ + public class SystemTransactionGeneratorModule : AElfModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.SystemTransactionGenerator/TransactionGeneratingService.cs b/src/AElf.Boilerplate.SystemTransactionGenerator/TransactionGeneratingService.cs new file mode 100755 index 00000000..4587d9a6 --- /dev/null +++ b/src/AElf.Boilerplate.SystemTransactionGenerator/TransactionGeneratingService.cs @@ -0,0 +1,60 @@ +using System.Linq; +using System.Threading.Tasks; +using AElf.Kernel; +using AElf.Kernel.Account.Application; +using AElf.Kernel.Blockchain.Application; +using AElf.Kernel.Infrastructure; +using AElf.Kernel.SmartContract.Application; +using AElf.Types; +using Google.Protobuf; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace AElf.Boilerplate.SystemTransactionGenerator +{ + public interface ITransactionGeneratingService + { + Task GenerateTransactionAsync(Hash contractName, string methodName, ByteString param); + } + + public class TransactionGeneratingService : ITransactionGeneratingService + { + private readonly IAccountService _accountService; + private readonly ISmartContractAddressService _smartContractAddressService; + private readonly IBlockchainService _blockchainService; + + public ILogger Logger { get; set; } + + public TransactionGeneratingService(IAccountService accountService, + ISmartContractAddressService smartContractAddressService, IBlockchainService blockchainService) + { + _accountService = accountService; + _smartContractAddressService = smartContractAddressService; + _blockchainService = blockchainService; + + Logger = NullLogger.Instance; + } + + public async Task GenerateTransactionAsync(Hash contractName, string methodName, ByteString param) + { + var pubkey = await _accountService.GetPublicKeyAsync(); + var chain = await _blockchainService.GetChainAsync(); + var address = await _smartContractAddressService.GetAddressByContractNameAsync(new ChainContext + { + BlockHash = chain.BestChainHash, + BlockHeight = chain.BestChainHeight + }, contractName.ToStorageKey()); + var transaction = new Transaction + { + From = Address.FromPublicKey(pubkey), + To = address, + MethodName = methodName, + Params = param, + RefBlockNumber = chain.BestChainHeight, + RefBlockPrefix = ByteString.CopyFrom(chain.BestChainHash.Value.Take(4).ToArray()) + }; + //Logger.LogDebug($"[Boilerplate]Generated test tx: {transaction}. tx id: {transaction.GetHash()}"); + return transaction; + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.TestBase/AElf.Boilerplate.TestBase.csproj b/src/AElf.Boilerplate.TestBase/AElf.Boilerplate.TestBase.csproj new file mode 100755 index 00000000..a53b8b5e --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/AElf.Boilerplate.TestBase.csproj @@ -0,0 +1,133 @@ + + + + net6.0 + AElf.Boilerplate.TestBase + false + + + + 0436 + + + + + + + + + + + + + + + + + + + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + false + Contract + PreserveNewest + + + + + Protobuf\Proto\authority_info.proto + + + + + Protobuf\Proto\acs0.proto + + + Protobuf\Proto\acs1.proto + + + Protobuf\Proto\acs3.proto + + + Protobuf\Proto\acs4.proto + + + Protobuf\Proto\token_contract.proto + + + Protobuf\Proto\parliament_contract.proto + + + Protobuf\Proto\aedpos_contract.proto + + + Protobuf\Proto\profit_contract.proto + + + + diff --git a/src/AElf.Boilerplate.TestBase/DAppContractTestBase.cs b/src/AElf.Boilerplate.TestBase/DAppContractTestBase.cs new file mode 100755 index 00000000..7da58e48 --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/DAppContractTestBase.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AElf.ContractDeployer; +using AElf.Contracts.Genesis; +using AElf.ContractTestBase.ContractTestKit; +using AElf.Cryptography.ECDSA; +using AElf.Kernel; +using AElf.Kernel.Blockchain.Application; +using AElf.Kernel.Infrastructure; +using AElf.Kernel.SmartContract.Application; +using AElf.Standards.ACS0; +using AElf.Types; +using Google.Protobuf; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace AElf.Boilerplate.TestBase +{ + public class DAppContractTestBase : ContractTestBase where TModule : AbpModule + { + public Address DAppContractAddress => GetAddress(DAppSmartContractAddressNameProvider.StringName); + + public Address GetAddress(string contractStringName) + { + var addressService = Application.ServiceProvider.GetRequiredService(); + var blockchainService = Application.ServiceProvider.GetRequiredService(); + var chain = AsyncHelper.RunSync(blockchainService.GetChainAsync); + var address = AsyncHelper.RunSync(() => addressService.GetSmartContractAddressAsync(new ChainContext + { + BlockHash = chain.BestChainHash, + BlockHeight = chain.BestChainHeight + }, contractStringName)).SmartContractAddress.Address; + return address; + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.TestBase/DAppContractTestDeploymentListProvider.cs b/src/AElf.Boilerplate.TestBase/DAppContractTestDeploymentListProvider.cs new file mode 100755 index 00000000..63ff5450 --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/DAppContractTestDeploymentListProvider.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using AElf.Boilerplate.TestBase.SmartContractNameProviders; +using AElf.ContractTestBase; +using AElf.Kernel.SmartContract.Application; +using AElf.Types; + +namespace AElf.Boilerplate.TestBase +{ + public class SideChainDAppContractTestDeploymentListProvider : SideChainContractDeploymentListProvider, IContractDeploymentListProvider + { + public List GetDeployContractNameList() + { + var list = base.GetDeployContractNameList(); + //list.Add(DAppSmartContractAddressNameProvider.Name); + list.Add(ForestSmartContractAddressNameProvider.Name); + list.Add(WhitelistSmartContractAddressNameProvider.Name); + list.Add(NFTSmartContractAddressNameProvider.Name); + return list; + } + } + + public class MainChainDAppContractTestDeploymentListProvider : MainChainContractDeploymentListProvider, IContractDeploymentListProvider + { + public List GetDeployContractNameList() + { + var list = base.GetDeployContractNameList(); + //list.Add(DAppSmartContractAddressNameProvider.Name); + list.Add(ForestSmartContractAddressNameProvider.Name); + list.Add(WhitelistSmartContractAddressNameProvider.Name); + list.Add(NFTSmartContractAddressNameProvider.Name); + return list; + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.TestBase/DAppContractTestModule.cs b/src/AElf.Boilerplate.TestBase/DAppContractTestModule.cs new file mode 100755 index 00000000..1d1220ec --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/DAppContractTestModule.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.IO; +using AElf.ContractDeployer; +using AElf.ContractTestBase; +using AElf.ContractTestBase.ContractTestKit; +using AElf.Kernel.Consensus.AEDPoS; +using AElf.Kernel.Miner.Application; +using AElf.Kernel.SmartContract.Application; +using AElf.OS.Node.Application; +using AElf.Runtime.CSharp; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace AElf.Boilerplate.TestBase +{ + [DependsOn( + typeof(SideChainContractTestModule) + )] + public class SideChainDAppContractTestModule : SideChainContractTestModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + base.ConfigureServices(context); + Configure(o => + { + o.SdkDir = Path.GetDirectoryName(typeof(SideChainContractTestModule).Assembly.Location); + }); + context.Services.AddSingleton(); + context.Services.AddSingleton(); + context.Services.AddSingleton(); + context.Services.AddSingleton(); + + Configure(options => + { + options.MiningInterval = 4000; + options.InitialMinerList = new List {SampleAccount.Accounts[0].KeyPair.PublicKey.ToHex()}; + }); + + context.Services.RemoveAll(); + } + + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + var contractCodeProvider = context.ServiceProvider.GetService(); + contractCodeProvider.Codes = ContractsDeployer.GetContractCodes(); + } + } + + [DependsOn( + typeof(MainChainContractTestModule) + )] + public class MainChainDAppContractTestModule : MainChainContractTestModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + base.ConfigureServices(context); + Configure(o => + { + o.SdkDir = Path.GetDirectoryName(typeof(MainChainDAppContractTestModule).Assembly.Location); + }); + context.Services.AddSingleton(); + context.Services.AddSingleton(); + context.Services.AddSingleton(); + context.Services.AddSingleton(); + + Configure(options => + { + options.MiningInterval = 4000; + options.InitialMinerList = new List {SampleAccount.Accounts[0].KeyPair.PublicKey.ToHex()}; + }); + + context.Services.RemoveAll(); + } + + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + var contractCodeProvider = context.ServiceProvider.GetService(); + contractCodeProvider.Codes = ContractsDeployer.GetContractCodes(); + } + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.TestBase/DAppSmartContractAddressNameProvider.cs b/src/AElf.Boilerplate.TestBase/DAppSmartContractAddressNameProvider.cs new file mode 100755 index 00000000..02a4681d --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/DAppSmartContractAddressNameProvider.cs @@ -0,0 +1,14 @@ +using AElf.Kernel.Infrastructure; +using AElf.Types; + +namespace AElf.Boilerplate.TestBase +{ + public class DAppSmartContractAddressNameProvider + { + public static readonly Hash Name = HashHelper.ComputeFrom("AElf.ContractNames.Test"); + + public static readonly string StringName = Name.ToStorageKey(); + public Hash ContractName => Name; + public string ContractStringName => StringName; + } +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/ForestSmartContractAddressNameProvider.cs b/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/ForestSmartContractAddressNameProvider.cs new file mode 100644 index 00000000..d8766128 --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/ForestSmartContractAddressNameProvider.cs @@ -0,0 +1,13 @@ +using AElf.Kernel.Infrastructure; +using AElf.Types; + +namespace AElf.Boilerplate.TestBase.SmartContractNameProviders; + +public class ForestSmartContractAddressNameProvider +{ + public static readonly Hash Name = HashHelper.ComputeFrom("AElf.ContractNames.Forest"); + + public static readonly string StringName = Name.ToStorageKey(); + public Hash ContractName => Name; + public string ContractStringName => StringName; +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/NFTSmartContractAddressNameProvider.cs b/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/NFTSmartContractAddressNameProvider.cs new file mode 100644 index 00000000..1675a882 --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/NFTSmartContractAddressNameProvider.cs @@ -0,0 +1,13 @@ +using AElf.Kernel.Infrastructure; +using AElf.Types; + +namespace AElf.Boilerplate.TestBase.SmartContractNameProviders; + +public class NFTSmartContractAddressNameProvider +{ + public static readonly Hash Name = HashHelper.ComputeFrom("AElf.ContractNames.NFTContract"); + + public static readonly string StringName = Name.ToStorageKey(); + public Hash ContractName => Name; + public string ContractStringName => StringName; +} \ No newline at end of file diff --git a/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/WhitelistSmartContractAddressNameProvider.cs b/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/WhitelistSmartContractAddressNameProvider.cs new file mode 100644 index 00000000..6343216d --- /dev/null +++ b/src/AElf.Boilerplate.TestBase/SmartContractNameProviders/WhitelistSmartContractAddressNameProvider.cs @@ -0,0 +1,13 @@ +using AElf.Kernel.Infrastructure; +using AElf.Types; + +namespace AElf.Boilerplate.TestBase.SmartContractNameProviders; + +public class WhitelistSmartContractAddressNameProvider +{ + public static readonly Hash Name = HashHelper.ComputeFrom("AElf.ContractNames.Whitelist"); + + public static readonly string StringName = Name.ToStorageKey(); + public Hash ContractName => Name; + public string ContractStringName => StringName; +} \ No newline at end of file diff --git a/test/Forest.Tests/ContractInitializationProvider/DAppContractTestDeploymentListProvider.cs b/test/Forest.Tests/ContractInitializationProvider/DAppContractTestDeploymentListProvider.cs new file mode 100644 index 00000000..de7c071f --- /dev/null +++ b/test/Forest.Tests/ContractInitializationProvider/DAppContractTestDeploymentListProvider.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using AElf.Boilerplate.TestBase.SmartContractNameProviders; +using AElf.ContractTestBase; +using AElf.Kernel.SmartContract.Application; +using AElf.Types; + +namespace AElf.Contracts.Bridge; + +public class MainChainDAppContractTestDeploymentListProvider : MainChainContractDeploymentListProvider, + IContractDeploymentListProvider +{ + public new List GetDeployContractNameList() + { + var list = base.GetDeployContractNameList(); + list.Add(ForestSmartContractAddressNameProvider.Name); + list.Add(NFTSmartContractAddressNameProvider.Name); + list.Add(WhitelistSmartContractAddressNameProvider.Name); + return list; + } +} \ No newline at end of file diff --git a/test/Forest.Tests/ContractInitializationProvider/ForestContractInitializationProvider.cs b/test/Forest.Tests/ContractInitializationProvider/ForestContractInitializationProvider.cs new file mode 100755 index 00000000..b99bfd0a --- /dev/null +++ b/test/Forest.Tests/ContractInitializationProvider/ForestContractInitializationProvider.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using AElf.Boilerplate.TestBase.SmartContractNameProviders; +using AElf.Kernel.SmartContract.Application; +using AElf.Types; +using Volo.Abp.DependencyInjection; + +namespace AElf.Contracts.Forest; + +public class ForestContractInitializationProvider : IContractInitializationProvider, ISingletonDependency +{ + public List GetInitializeMethodList(byte[] contractCode) + { + return new List(); + } + + public Hash SystemSmartContractName => ForestSmartContractAddressNameProvider.Name; + public string ContractCodeName => "Forest"; +} \ No newline at end of file diff --git a/test/Forest.Tests/ContractInitializationProvider/NFTContractInitializationProvider.cs b/test/Forest.Tests/ContractInitializationProvider/NFTContractInitializationProvider.cs new file mode 100644 index 00000000..530c1676 --- /dev/null +++ b/test/Forest.Tests/ContractInitializationProvider/NFTContractInitializationProvider.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using AElf.Boilerplate.TestBase.SmartContractNameProviders; +using AElf.Kernel.SmartContract.Application; +using AElf.Types; +using Volo.Abp.DependencyInjection; + +namespace AElf.Contracts.Forest; + +public class NFTContractInitializationProvider : IContractInitializationProvider, ISingletonDependency +{ + public List GetInitializeMethodList(byte[] contractCode) + { + return new List(); + } + + public Hash SystemSmartContractName => NFTSmartContractAddressNameProvider.Name; + public string ContractCodeName => "AElf.Contracts.NFT"; +} \ No newline at end of file diff --git a/test/Forest.Tests/ContractInitializationProvider/WhitelistContractInitializationProvider.cs b/test/Forest.Tests/ContractInitializationProvider/WhitelistContractInitializationProvider.cs new file mode 100644 index 00000000..173cc682 --- /dev/null +++ b/test/Forest.Tests/ContractInitializationProvider/WhitelistContractInitializationProvider.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using AElf.Boilerplate.TestBase.SmartContractNameProviders; +using AElf.Kernel.SmartContract.Application; +using AElf.Types; +using Volo.Abp.DependencyInjection; + +namespace AElf.Contracts.Forest; + +public class WhitelistContractInitializationProvider : IContractInitializationProvider, ISingletonDependency +{ + public List GetInitializeMethodList(byte[] contractCode) + { + return new List(); + } + + public Hash SystemSmartContractName => WhitelistSmartContractAddressNameProvider.Name; + public string ContractCodeName => "AElf.Contracts.Whitelist"; +} \ No newline at end of file diff --git a/test/Forest.Tests/Forest.Tests.csproj b/test/Forest.Tests/Forest.Tests.csproj new file mode 100755 index 00000000..c30f799a --- /dev/null +++ b/test/Forest.Tests/Forest.Tests.csproj @@ -0,0 +1,101 @@ + + + + net6.0 + Forest + false + + + + 0436 + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + Protobuf\Proto\authority_info.proto + + + Protobuf\Proto\transaction_fee.proto + + + + + Protobuf\Proto\forest_contract.proto + + + Protobuf\Proto\token_contract.proto + + + Protobuf\Proto\token_contract_impl.proto + + + Protobuf\Proto\acs3.proto + + + Protobuf\Proto\election_contract.proto + + + Protobuf\Proto\election_contract_impl.proto + + + Protobuf\Proto\aedpos_contract_impl.proto + + + Protobuf\Proto\parliament_contract.proto + + + Protobuf\Proto\parliament_contract_impl.proto + + + Protobuf\Proto\nft_contract.proto + + + Protobuf\Proto\whitelist_contract.proto + + + Protobuf\Proto\acs0.proto + + + Protobuf\Proto\acs1.proto + + + Protobuf\Proto\acs2.proto + + + Protobuf\Proto\base\acs10.proto + + + + + + + + + + + \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTestBase.cs b/test/Forest.Tests/ForestContractTestBase.cs new file mode 100755 index 00000000..a69ce520 --- /dev/null +++ b/test/Forest.Tests/ForestContractTestBase.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AElf.Boilerplate.TestBase; +using AElf.Boilerplate.TestBase.SmartContractNameProviders; +using AElf.Contracts.Consensus.AEDPoS; +using AElf.Contracts.Election; +using AElf.Contracts.MultiToken; +using AElf.Contracts.NFT; +using AElf.Contracts.Parliament; +using AElf.Contracts.Whitelist; +using AElf.Cryptography.ECDSA; +using AElf.CSharp.Core.Extension; +using AElf.GovernmentSystem; +using AElf.Standards.ACS3; +using AElf.Types; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using Volo.Abp.Threading; +using TimestampHelper = AElf.Kernel.TimestampHelper; + +namespace Forest +{ + public class ForestContractTestBase : DAppContractTestBase + { + protected ECKeyPair DefaultKeyPair => Accounts[0].KeyPair; + protected Address DefaultAddress => Accounts[0].Address; + + protected ECKeyPair MinterKeyPair => Accounts[1].KeyPair; + protected Address MinterAddress => Accounts[1].Address; + + protected ECKeyPair User1KeyPair => Accounts[10].KeyPair; + protected ECKeyPair User2KeyPair => Accounts[11].KeyPair; + protected ECKeyPair User3KeyPair => Accounts[14].KeyPair; + protected Address User1Address => Accounts[10].Address; + protected Address User2Address => Accounts[11].Address; + protected Address User3Address => Accounts[14].Address; + protected Address User4Address => Accounts[15].Address; + protected Address User5Address => Accounts[16].Address; + protected Address User6Address => Accounts[17].Address; + + protected ECKeyPair MarketServiceFeeReceiverKeyPair => Accounts[12].KeyPair; + protected Address MarketServiceFeeReceiverAddress => Accounts[12].Address; + + protected List InitialCoreDataCenterKeyPairs => + Accounts.Take(InitialCoreDataCenterCount).Select(a => a.KeyPair).ToList(); + + + // You can get address of any contract via GetAddress method, for example: + // internal Address DAppContractAddress => GetAddress(DAppSmartContractAddressNameProvider.StringName); + internal ParliamentContractImplContainer.ParliamentContractImplStub ParliamentContractStub; + + internal ElectionContractImplContainer.ElectionContractImplStub ElectionContractStub; + internal AEDPoSContractImplContainer.AEDPoSContractImplStub ConsensusContractStub; + + internal TokenContractImplContainer.TokenContractImplStub TokenContractStub; + internal TokenContractImplContainer.TokenContractImplStub UserTokenContractStub; + internal TokenContractImplContainer.TokenContractImplStub NFTBuyerTokenContractStub; + internal TokenContractImplContainer.TokenContractImplStub NFTBuyer2TokenContractStub; + + internal NFTContractContainer.NFTContractStub NFTContractStub { get; set; } + internal ForestContractContainer.ForestContractStub ForestContractStub { get; set; } + internal WhitelistContractContainer.WhitelistContractStub WhitelistContractStub { get; set; } + + internal ForestContractContainer.ForestContractStub SellerForestContractStub { get; set; } + + internal ForestContractContainer.ForestContractStub Seller2ForestContractStub { get; set; } + internal ForestContractContainer.ForestContractStub BuyerForestContractStub { get; set; } + internal ForestContractContainer.ForestContractStub Buyer2ForestContractStub { get; set; } + internal ForestContractContainer.ForestContractStub Buyer3ForestContractStub { get; set; } + internal ForestContractContainer.ForestContractStub CreatorForestContractStub { get; set; } + internal ForestContractContainer.ForestContractStub AdminForestContractStub { get; set; } + + internal NFTContractContainer.NFTContractStub MinterNFTContractStub { get; set; } + internal NFTContractContainer.NFTContractStub NFT2ContractStub { get; set; } + + + internal Address NFTContractAddress => GetAddress(NFTSmartContractAddressNameProvider.StringName); + internal Address ForestContractAddress => GetAddress(ForestSmartContractAddressNameProvider.StringName); + internal Address WhitelistContractAddress => GetAddress(WhitelistSmartContractAddressNameProvider.StringName); + + internal Address ElectionContractAddress => GetAddress(ElectionSmartContractAddressNameProvider.StringName); + + public ForestContractTestBase() + { + TokenContractStub = + GetTester(TokenContractAddress, DefaultKeyPair); + UserTokenContractStub = + GetTester(TokenContractAddress, User1KeyPair); + NFTBuyerTokenContractStub = + GetTester(TokenContractAddress, User2KeyPair); + NFTBuyer2TokenContractStub = + GetTester(TokenContractAddress, User3KeyPair); + MinterNFTContractStub = GetTester(NFTContractAddress, MinterKeyPair); + + AdminForestContractStub =GetForestContractStub(DefaultKeyPair); + ForestContractStub = GetForestContractStub(DefaultKeyPair); + SellerForestContractStub = GetForestContractStub(DefaultKeyPair); + Seller2ForestContractStub = GetForestContractStub(User2KeyPair); + BuyerForestContractStub = GetForestContractStub(User2KeyPair); + Buyer2ForestContractStub = GetForestContractStub(User3KeyPair); + Buyer3ForestContractStub = GetForestContractStub(DefaultKeyPair); + CreatorForestContractStub = GetForestContractStub(DefaultKeyPair); + NFTContractStub = GetNFTContractStub(DefaultKeyPair); + NFT2ContractStub = GetTester(NFTContractAddress, User2KeyPair); + WhitelistContractStub = GetWhitelistContractStub(DefaultKeyPair); + ParliamentContractStub = GetTester( + ParliamentContractAddress, DefaultKeyPair); + ElectionContractStub = GetTester( + ElectionContractAddress, DefaultKeyPair); + ConsensusContractStub = GetTester( + ConsensusContractAddress, DefaultKeyPair); + AsyncHelper.RunSync(SetNFTContractAddress); + + } + + internal ParliamentContractImplContainer.ParliamentContractImplStub GetParliamentContractTester( + ECKeyPair keyPair) + { + return GetTester(ParliamentContractAddress, + keyPair); + } + + internal ForestContractContainer.ForestContractStub GetForestContractStub(ECKeyPair senderKeyPair) + { + return GetTester(ForestContractAddress, senderKeyPair); + } + internal NFTContractContainer.NFTContractStub GetNFTContractStub(ECKeyPair senderKeyPair) + { + return GetTester(NFTContractAddress, senderKeyPair); + } + internal WhitelistContractContainer.WhitelistContractStub GetWhitelistContractStub(ECKeyPair senderKeyPair) + { + return GetTester(WhitelistContractAddress, senderKeyPair); + } + + private async Task SetNFTContractAddress() + { + var defaultParliament = await ParliamentContractStub.GetDefaultOrganizationAddress.CallAsync(new Empty()); + var proposalId = await CreateProposalAsync(TokenContractAddress, + defaultParliament, nameof(TokenContractStub.AddAddressToCreateTokenWhiteList), + NFTContractAddress); + await ApproveWithMinersAsync(proposalId); + await ParliamentContractStub.Release.SendAsync(proposalId); + } + private async Task CreateProposalAsync(Address contractAddress, Address organizationAddress, + string methodName, IMessage input) + { + var proposal = new CreateProposalInput + { + OrganizationAddress = organizationAddress, + ContractMethodName = methodName, + ExpiredTime = TimestampHelper.GetUtcNow().AddHours(1), + Params = input.ToByteString(), + ToAddress = contractAddress + }; + + var createResult = await ParliamentContractStub.CreateProposal.SendAsync(proposal); + var proposalId = createResult.Output; + + return proposalId; + } + + private async Task ApproveWithMinersAsync(Hash proposalId) + { + var miner = GetParliamentContractTester(DefaultKeyPair); + await miner.Approve.SendAsync(proposalId); + // foreach (var bp in InitialCoreDataCenterKeyPairs) + // { + // var tester = GetParliamentContractTester(bp); + // await tester.Approve.SendAsync(proposalId); + // } + } + } + +} \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTestModule.cs b/test/Forest.Tests/ForestContractTestModule.cs new file mode 100755 index 00000000..e6953bb3 --- /dev/null +++ b/test/Forest.Tests/ForestContractTestModule.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.IO; +using AElf.Boilerplate.TestBase; +using AElf.Contracts.Forest; +using AElf.Contracts.Whitelist; +using AElf.Contracts.NFT; +using AElf.ContractTestBase; +using AElf.ContractTestBase.ContractTestKit; +using AElf.Kernel.SmartContract.Application; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace Forest +{ + [DependsOn(typeof(MainChainDAppContractTestModule))] + public class ForestContractTestModule : MainChainDAppContractTestModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddSingleton(); + context.Services + .AddSingleton(); + } + + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + var contractCodeProvider = context.ServiceProvider.GetService() ?? + new ContractCodeProvider(); + var contractCodes = new Dictionary(contractCodeProvider.Codes) + { + { + new ForestContractInitializationProvider().ContractCodeName, + File.ReadAllBytes(typeof(ForestContract).Assembly.Location) + }, + { + new WhitelistContractInitializationProvider().ContractCodeName, + File.ReadAllBytes(typeof(WhitelistContract).Assembly.Location) + }, + { + new NFTContractInitializationProvider().ContractCodeName, + File.ReadAllBytes(typeof(NFTContract).Assembly.Location) + } + }; + contractCodeProvider.Codes = contractCodes; + } + } +} \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTests.cs b/test/Forest.Tests/ForestContractTests.cs new file mode 100755 index 00000000..0de3397e --- /dev/null +++ b/test/Forest.Tests/ForestContractTests.cs @@ -0,0 +1,1525 @@ +using System.Linq; +using System.Threading.Tasks; +using AElf; +using AElf.Contracts.Forest; +using AElf.Contracts.MultiToken; +using AElf.Contracts.NFT; +using AElf.Contracts.Whitelist; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; +using ApproveInput = AElf.Contracts.MultiToken.ApproveInput; +using CreateInput = AElf.Contracts.NFT.CreateInput; +using GetBalanceInput = AElf.Contracts.MultiToken.GetBalanceInput; +using TransferInput = AElf.Contracts.MultiToken.TransferInput; + +namespace Forest +{ + public partial class ForestContractTests : ForestContractTestBase + { + private const long InitialELFAmount = 1_00000000_00000000; + + private const long DefaultSenderInitialELFAmount = 780000000_00000000; + + [Fact] + public async Task CreateArtistsTest() + { + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Art", + NftType = NFTType.Art.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = false + }); + var symbol = executionResult.Output.Value; + + var nftProtocolInfo = await NFTContractStub.GetNFTProtocolInfo.CallAsync(new StringValue {Value = symbol}); + nftProtocolInfo.TotalSupply.ShouldBe(1000); + + return symbol; + } + + private async Task InitializeForestContract() + { + await AdminForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + + await AdminForestContractStub.SetWhitelistContract.SendAsync(WhitelistContractAddress); + } + + private async Task MintBadgeTest() + { + await InitializeForestContract(); + var createWhitelistInput = new CreateWhitelistInput + { + ProjectId = HashHelper.ComputeFrom("Badge Test"), + Creator = ForestContractAddress, + ExtraInfoList = new ExtraInfoList() + { + Value = + { + new ExtraInfo() + { + AddressList = new AElf.Contracts.Whitelist.AddressList + { + Value = {DefaultAddress} + } + } + } + }, + IsCloneable = true, + StrategyType = StrategyType.Basic, + Remark = "Badge Test" + }; + var createWhitelistResult = await WhitelistContractStub.CreateWhitelist.SendAsync(createWhitelistInput); + var whitelistId = createWhitelistResult.Output; + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + BaseUri = BaseUri, + Creator = DefaultAddress, + IsBurnable = true, + Metadata = new Metadata + { + Value = + { + {"Description", "Stands for the human race."} + } + }, + NftType = NFTType.Badges.ToString(), + ProtocolName = "Badge", + IsTokenIdReuse = true, + MinterList = new MinterList + { + Value = {ForestContractAddress} + }, + TotalSupply = 1_000_000_000 // One billion + }); + var symbol = executionResult.Output.Value; + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "badge", + Metadata = new Metadata + { + Value = + { + {"Special Property", "A Value"}, + {"aelf_badge_whitelist", whitelistId.ToHex()} + } + }, + Owner = DefaultAddress, + Uri = $"{BaseUri}foo" + }); + await AdminForestContractStub.MintBadge.SendAsync(new MintBadgeInput() + { + Symbol = symbol, + TokenId = 233 + }); + return symbol; + } + + [Fact(Skip = "Badge not found")] + public async Task ListWithFixedPriceTest() + { + + var symbol = await MintBadgeTest(); + + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = DefaultAddress, + }); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 1, + IsWhitelistAvailable = false + }); + + var listedNftInfo = (await SellerForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + })).Value.First(); + listedNftInfo.Price.Symbol.ShouldBe("ELF"); + listedNftInfo.Price.Amount.ShouldBe(100_00000000); + listedNftInfo.Quantity.ShouldBe(1); + listedNftInfo.ListType.ShouldBe(ListType.FixedPrice); + listedNftInfo.Duration.StartTime.ShouldNotBeNull(); + listedNftInfo.Duration.DurationHours.ShouldBe(24); + + { + var executionResult = await SellerForestContractStub.ListWithFixedPrice.SendWithExceptionAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 1 + }); + executionResult.TransactionResult.Error.ShouldContain("Check sender NFT balance failed."); + } + + return symbol; + } + + [Fact] + public async Task ListWithFixedPriceTest_WithWhitelist() + { + await InitializeForestContract(); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 1, + IsWhitelistAvailable = false + }); + + return symbol; + } + + [Fact(Skip = "Badge not found.")] + public async Task DealWithFixedPriceTest() + { + var symbol = await ListWithFixedPriceTest(); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 100_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + // Because of 10/10000 service fee. + balance.Balance.ShouldBe(InitialELFAmount + 100_00000000 - 100_00000000 / 1000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(0); + } + } + + [Fact(Skip = "Badge not found.")] + public async Task MakeOfferToFixedPrice() + { + var symbol = await ListWithFixedPriceTest(); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + }, + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 99_00000000 + }, + }); + + var offerAddressList = await BuyerForestContractStub.GetOfferAddressList.CallAsync( + new GetAddressListInput + { + Symbol = symbol, + TokenId = 233 + }); + offerAddressList.Value.ShouldContain(User2Address); + + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(2); + offerList.Value.First().Quantity.ShouldBe(2); + offerList.Value.Last().Quantity.ShouldBe(1); + + return symbol; + } + + [Fact(Skip = "Badge not found.")] + public async Task DealToOfferWhenFixedPrice() + { + var symbol = await MakeOfferToFixedPrice(); + + // Set royalty. + await CreatorForestContractStub.SetRoyalty.SendAsync(new SetRoyaltyInput + { + Symbol = symbol, + TokenId = 233, + Royalty = 10, + RoyaltyFeeReceiver = MarketServiceFeeReceiverAddress + }); + + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233 + }); + offerList.Value.Count.ShouldBe(2); + + var offer = offerList.Value.First(); + var executionResult = await SellerForestContractStub.Deal.SendWithExceptionAsync(new DealInput + { + Symbol = symbol, + TokenId = 233, + OfferFrom = offer.From, + Quantity = 1, + Price = offer.Price + }); + executionResult.TransactionResult.Error.ShouldContain("Need to delist"); + + await SellerForestContractStub.Delist.SendAsync(new DelistInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Quantity = 1 + }); + + var executionResult1 = await SellerForestContractStub.Deal.SendAsync(new DealInput + { + Symbol = symbol, + TokenId = 233, + OfferFrom = offer.From, + Quantity = 1, + Price = offer.Price + }); + var log = OfferChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(OfferChanged)).NonIndexed); + log.Quantity.ShouldBe(1); + log.Price.Amount.ShouldBe(90_00000000); + offer.From.ShouldBe(User2Address); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + // Because of 10/10000 service fee. + balance.Balance.ShouldBe(InitialELFAmount + 90_00000000 - 90_00000000 / 1000 - 90_00000000 / 1000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(0); + } + } + + [Fact(Skip = "Badge not found.")] + public async Task DealToOfferWhenNotListed() + { + + var symbol = await MintBadgeTest(); + + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = DefaultAddress, + }); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 1, + Spender = ForestContractAddress + }); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 1000_00000000 + }, + }); + + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233 + }); + offerList.Value.Count.ShouldBe(1); + + var offer = offerList.Value.First(); + await SellerForestContractStub.Deal.SendAsync(new DealInput + { + Symbol = symbol, + TokenId = 233, + OfferFrom = offer.From, + Quantity = 1, + Price = offer.Price + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 1000_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + // Because of 10/10000 service fee. + balance.Balance.ShouldBe(InitialELFAmount + 1000_00000000 - 1000_00000000 / 1000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(0); + } + } + + [Fact] + public async Task TokenWhiteListTest() + { + await AdminForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + + await AdminForestContractStub.SetGlobalTokenWhiteList.SendAsync(new StringList + { + Value = {"USDT", "EAN"} + }); + + var globalTokenWhiteList = await AdminForestContractStub.GetGlobalTokenWhiteList.CallAsync(new Empty()); + globalTokenWhiteList.Value.Count.ShouldBe(3); + globalTokenWhiteList.Value.ShouldContain("EAN"); + globalTokenWhiteList.Value.ShouldContain("ELF"); + globalTokenWhiteList.Value.ShouldContain("USDT"); + + var symbol = await CreateArtistsTest(); + + await CreatorForestContractStub.SetTokenWhiteList.SendAsync(new SetTokenWhiteListInput + { + Symbol = symbol, + TokenWhiteList = new StringList + { + Value = {"TEST"} + } + }); + + { + var tokenWhiteList = + await CreatorForestContractStub.GetTokenWhiteList.CallAsync(new StringValue {Value = symbol}); + tokenWhiteList.Value.Count.ShouldBe(4); + tokenWhiteList.Value.ShouldContain("ELF"); + tokenWhiteList.Value.ShouldContain("TEST"); + } + + await AdminForestContractStub.SetGlobalTokenWhiteList.SendAsync(new StringList + { + Value = {"USDT", "EAN", "NEW"} + }); + + { + var tokenWhiteList = + await CreatorForestContractStub.GetTokenWhiteList.CallAsync(new StringValue {Value = symbol}); + tokenWhiteList.Value.Count.ShouldBe(5); + tokenWhiteList.Value.ShouldContain("NEW"); + tokenWhiteList.Value.ShouldContain("TEST"); + } + + await AdminForestContractStub.SetGlobalTokenWhiteList.SendAsync(new StringList + { + Value = {"ELF"} + }); + + { + var tokenWhiteList = + await CreatorForestContractStub.GetTokenWhiteList.CallAsync(new StringValue {Value = symbol}); + tokenWhiteList.Value.Count.ShouldBe(2); + tokenWhiteList.Value.ShouldContain("ELF"); + tokenWhiteList.Value.ShouldContain("TEST"); + } + } + + [Fact] + public async Task MakeOfferListedFixedPrice_Normal() + { + await InitializeForestContract(); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 5, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 7, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 300_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 8, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 4, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + }); + var log = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed); + log.TokenId.ShouldBe(233); + var executionResult2 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + var log2 = ListedNFTRemoved.Parser.ParseFrom(executionResult2.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTRemoved)).NonIndexed); + log2.Price.Amount.ShouldBe(100_00000000); + var log1 = ListedNFTChanged.Parser.ParseFrom(executionResult2.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed); + log1.Quantity.ShouldBe(6); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(6); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 400_00000000 - 100_00000000 - 200_00000000); + } + var executionResult3 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + }); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(6); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 500_00000000 - 200_00000000); + } + } + + [Fact] + public async Task MakeOffer() + { + await InitializeForestContract(); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 5, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 7, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 300_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 8, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 21, + Price = new Price + { + Symbol = "ELF", + Amount = 300_00000000 + }, + }); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(20); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 100_00000000 * 5 - 200_00000000 * 7 - 300_00000000 * 8); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + Address = User2Address, + TokenId = 233 + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value[0].Quantity.ShouldBe(1); + } + } + + [Fact] + public async Task MakeOffer_Merge_Normal() + { + await InitializeForestContract(); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 5, + IsWhitelistAvailable = true, + Whitelists = new WhitelistInfoList + { + Whitelists = + { + new WhitelistInfo + { + AddressList = new AddressList + { + Value = {User3Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsMergeToPreviousListedInfo = true + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 7, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = true + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 7, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + }); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(7); + } + { + var info = await SellerForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + info.Value.Count.ShouldBe(1); + info.Value[0].Quantity.ShouldBe(5); + } + { + var log = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed); + log.Quantity.ShouldBe(5); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 100_00000000 * 7); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + Address = User2Address, + TokenId = 233 + }); + offerList.Value.Count.ShouldBe(0); + } + } + + [Fact] + public async Task MakeOffer_Merge_Whitelist() + { + await InitializeForestContract(); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 5, + IsWhitelistAvailable = true, + Whitelists = new WhitelistInfoList + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsMergeToPreviousListedInfo = true + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 7, + IsWhitelistAvailable = true, + IsMergeToPreviousListedInfo = true + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 7, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + }); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(7); + } + { + var info = await SellerForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + info.Value.Count.ShouldBe(1); + info.Value[0].Quantity.ShouldBe(5); + } + { + var log1 = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed); + log1.Quantity.ShouldBe(11); + var log = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .Last(l => l.Name == nameof(ListedNFTChanged)).NonIndexed); + log.Quantity.ShouldBe(5); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 100_00000000 * 6 - 90_00000000); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + Address = User2Address, + TokenId = 233 + }); + offerList.Value.Count.ShouldBe(0); + } + } + + [Fact] + public async Task MakeOffer_ServiceFee() + { + await AdminForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + + await AdminForestContractStub.SetWhitelistContract.SendAsync(WhitelistContractAddress); + await AdminForestContractStub.SetServiceFee.SendAsync(new SetServiceFeeInput + { + ServiceFeeRate = 10, + ServiceFeeReceiver = DefaultAddress + }); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + await NFT2ContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 5, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + { + var balance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + balance.Balance.ShouldBe(20); + } + + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + }); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(2); + + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 2, + Spender = ForestContractAddress + }); + } + { + var balance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + balance.Balance.ShouldBe(18); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 200_00000000); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + balance.Balance.ShouldBe(DefaultSenderInitialELFAmount + 200_00000000); + } + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + { + await Seller2ForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 2, + IsWhitelistAvailable = false, + IsMergeToPreviousListedInfo = false + }); + } + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + { + await Buyer3ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = User2Address, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new AElf.Contracts.NFT.GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(19); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + balance.Balance.ShouldBe(DefaultSenderInitialELFAmount + 200_00000000 + 200_00000000 / 1000 - 200_00000000); + } + } + } +} \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTests_Auction.cs b/test/Forest.Tests/ForestContractTests_Auction.cs new file mode 100644 index 00000000..f00e5c6f --- /dev/null +++ b/test/Forest.Tests/ForestContractTests_Auction.cs @@ -0,0 +1,491 @@ +using System.Linq; +using System.Threading.Tasks; +using AElf.Contracts.Forest; +using AElf.Contracts.MultiToken; +using AElf.Contracts.NFT; +using Shouldly; +using Xunit; +using ApproveInput = AElf.Contracts.NFT.ApproveInput; +using GetBalanceInput = AElf.Contracts.NFT.GetBalanceInput; +using TransferInput = AElf.Contracts.MultiToken.TransferInput; + +namespace Forest; + +public partial class ForestContractTests +{ + [Fact] + public async Task ListWithEnglishAuctionTest() + { + await AdminForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + + var symbol = await CreateArtistsTest(); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Alias = "Gift2" + }); + + await NFTContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = symbol, + TokenId = 2, + Amount = 1, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithEnglishAuction.SendAsync(new ListWithEnglishAuctionInput + { + Symbol = symbol, + TokenId = 2, + Duration = new ListDuration + { + DurationHours = 100 + }, + PurchaseSymbol = "ELF", + StartingPrice = 100_00000000, + EarnestMoney = 10_00000000 + }); + + var auctionInfo = await SellerForestContractStub.GetEnglishAuctionInfo.CallAsync( + new GetEnglishAuctionInfoInput + { + Symbol = symbol, + TokenId = 2 + }); + auctionInfo.Owner.ShouldBe(DefaultAddress); + auctionInfo.PurchaseSymbol.ShouldBe("ELF"); + auctionInfo.StartingPrice.ShouldBe(100_00000000); + auctionInfo.Duration.DurationHours.ShouldBe(100); + + return symbol; + } + + [Fact] + public async Task PlaceBidForEnglishAuctionTest() + { + var symbol = await ListWithEnglishAuctionTest(); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 2 + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value.First().From.ShouldBe(User2Address); + offerList.Value.First().Price.Amount.ShouldBe(90_00000000); + } + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await NFTBuyer2TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + } + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 2 + }); + offerList.Value.Count.ShouldBe(1); + } + + { + var bidList = await BuyerForestContractStub.GetBidList.CallAsync(new GetBidListInput + { + Symbol = symbol, + TokenId = 2 + }); + bidList.Value.Count.ShouldBe(1); + } + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 109_00000000 + } + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 2 + }); + offerList.Value.Count.ShouldBe(2); + } + + { + var bidList = await BuyerForestContractStub.GetBidList.CallAsync(new GetBidListInput + { + Symbol = symbol, + TokenId = 2 + }); + bidList.Value.Count.ShouldBe(1); + } + + return symbol; + } + + [Fact] + public async Task DealToEnglishAuctionTest() + { + var symbol = await PlaceBidForEnglishAuctionTest(); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await Buyer2ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 105_00000000 + } + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 10_00000000); + } + + await SellerForestContractStub.Deal.SendAsync(new DealInput + { + Symbol = symbol, + TokenId = 2, + OfferFrom = User2Address, + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + }, + Quantity = 1 + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 110_00000000); + } + + { + var balance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 2, + Owner = User2Address + }); + balance.Balance.ShouldBe(1); + } + + { + var balance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 2, + Owner = DefaultAddress + }); + balance.Balance.ShouldBe(0); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User3Address + }); + balance.Balance.ShouldBe(InitialELFAmount); + } + } + + [Fact] + public async Task ListWithDutchAuctionTest() + { + await AdminForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + + var symbol = await CreateArtistsTest(); + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Alias = "Gift2" + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = symbol, + TokenId = 2, + Amount = 1, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithDutchAuction.SendAsync(new ListWithDutchAuctionInput + { + Symbol = symbol, + TokenId = 2, + Duration = new ListDuration + { + DurationHours = 100 + }, + PurchaseSymbol = "ELF", + StartingPrice = 100_00000000, + EndingPrice = 50_00000000 + }); + + var auctionInfo = await SellerForestContractStub.GetDutchAuctionInfo.CallAsync( + new GetDutchAuctionInfoInput + { + Symbol = symbol, + TokenId = 2 + }); + auctionInfo.Owner.ShouldBe(DefaultAddress); + auctionInfo.PurchaseSymbol.ShouldBe("ELF"); + auctionInfo.StartingPrice.ShouldBe(100_00000000); + auctionInfo.EndingPrice.ShouldBe(50_00000000); + auctionInfo.Duration.DurationHours.ShouldBe(100); + + return symbol; + } + + [Fact] + public async Task PlaceBidForDutchAuctionTest() + { + var symbol = await ListWithDutchAuctionTest(); + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 49_00000000 + }, + Quantity = 1 + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 2 + }); + offerList.Value.Count.ShouldBe(1); + } + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Quantity = 1 + }); + + { + var balance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 2, + Owner = User2Address + }); + balance.Balance.ShouldBe(1); + } + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 2 + }); + offerList.Value.Count.ShouldBe(1); + } + + { + var balance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 2, + Owner = DefaultAddress + }); + balance.Balance.ShouldBe(0); + } + } + + [Fact] + public async Task DelistEnglishAuctionNFTTest() + { + var symbol = await ListWithEnglishAuctionTest(); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await NFTBuyer2TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + } + }); + + await Buyer2ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 300_00000000 + } + }); + + await TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Spender = ForestContractAddress, + Amount = 1_00000000 + }); + + await SellerForestContractStub.Delist.SendAsync(new DelistInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + } + }); + + var listedNftList = (await SellerForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = symbol, + TokenId = 2, + Owner = DefaultAddress + })); + listedNftList.Value.Count.ShouldBe(0); + } + +} \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTests_Customize.cs b/test/Forest.Tests/ForestContractTests_Customize.cs new file mode 100644 index 00000000..ba00605d --- /dev/null +++ b/test/Forest.Tests/ForestContractTests_Customize.cs @@ -0,0 +1,349 @@ +using System.Threading.Tasks; +using AElf.Contracts.NFT; +using AElf.CSharp.Core.Extension; +using AElf.Kernel; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; +using ApproveInput = AElf.Contracts.MultiToken.ApproveInput; +using GetBalanceInput = AElf.Contracts.NFT.GetBalanceInput; + +namespace Forest; + +public partial class ForestContractTests +{ + [Fact] + public async Task SetCustomizeInfoTest() + { + await InitializeForestContract(); + + var symbol = await CreateArtistsTest(); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new AElf.Contracts.MultiToken.TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address + }); + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await CreatorForestContractStub.SetCustomizeInfo.SendAsync(new CustomizeInfo + { + Symbol = symbol, + DepositRate = 2000, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + StakingAmount = 10_00000000, + WhiteListHours = 10, + WorkHours = 7 + }); + + var customizeInfo = + await CreatorForestContractStub.GetCustomizeInfo.CallAsync(new StringValue {Value = symbol}); + customizeInfo.Symbol.ShouldBe(symbol); + return symbol; + } + + [Fact] + public async Task RequestNewNFTTest() + { + var symbol = await SetCustomizeInfoTest(); + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + ExpireTime = TimestampHelper.GetUtcNow().AddMinutes(30) + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 40_00000000); + } + + var requestInfo = await BuyerForestContractStub.GetRequestInfo.CallAsync(new GetRequestInfoInput + { + Symbol = symbol, + TokenId = 2 + }); + requestInfo.DepositRate.ShouldBe(2000); + requestInfo.IsConfirmed.ShouldBeFalse(); + return symbol; + } + + [Fact] + public async Task ConfirmRequestTest() + { + var symbol = await RequestNewNFTTest(); + await CreatorForestContractStub.HandleRequest.SendAsync(new HandleRequestInput + { + Symbol = symbol, + TokenId = 2, + IsConfirm = true, + Requester = User2Address + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + // Received 50% deposit, but need to sub 10_00000000 staking tokens and 2000000 service fees. + balance.Balance.ShouldBe(DefaultSenderInitialELFAmount + 20_00000000 - 10_00000000 - 2000000); + } + + var requestInfo = await BuyerForestContractStub.GetRequestInfo.CallAsync(new GetRequestInfoInput + { + Symbol = symbol, + TokenId = 2 + }); + requestInfo.IsConfirmed.ShouldBeTrue(); + return symbol; + } + + [Fact] + public async Task ListForRequestedNFTTest() + { + var symbol = await ConfirmRequestTest(); + + // Need to mint this token id first. + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + TokenId = 2, + Quantity = 1, + Alias = "Gift" + }); + await NFTContractStub.Approve.SendAsync(new AElf.Contracts.NFT.ApproveInput + { + Symbol = symbol, + TokenId = 2, + Amount = 1, + Spender = ForestContractAddress + }); + + { + var executionResult = await CreatorForestContractStub.ListWithEnglishAuction.SendWithExceptionAsync( + new ListWithEnglishAuctionInput + { + Symbol = symbol, + TokenId = 2, + StartingPrice = 100_00000000, + PurchaseSymbol = "ELF", + Duration = new ListDuration + { + StartTime = TimestampHelper.GetUtcNow(), + PublicTime = TimestampHelper.GetUtcNow(), + DurationHours = 7 + } + }); + executionResult.TransactionResult.Error.ShouldContain( + "This NFT cannot be listed with auction for now."); + } + + { + var executionResult = await CreatorForestContractStub.ListWithDutchAuction.SendWithExceptionAsync( + new ListWithDutchAuctionInput + { + Symbol = symbol, + TokenId = 2, + StartingPrice = 100_00000000, + EndingPrice = 50_00000000, + PurchaseSymbol = "ELF", + Duration = new ListDuration + { + StartTime = TimestampHelper.GetUtcNow(), + PublicTime = TimestampHelper.GetUtcNow(), + DurationHours = 7 + } + }); + executionResult.TransactionResult.Error.ShouldContain( + "This NFT cannot be listed with auction for now."); + } + + var listInput = new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + Duration = new ListDuration + { + StartTime = TimestampHelper.GetUtcNow(), + PublicTime = TimestampHelper.GetUtcNow().AddHours(11), + DurationHours = 100 + }, + Quantity = 1, + IsWhitelistAvailable = true + }; + + { + var executionResult = + await CreatorForestContractStub.ListWithFixedPrice.SendWithExceptionAsync(listInput); + executionResult.TransactionResult.Error.ShouldContain("Incorrect white list address price list."); + } + + listInput.Price.Amount = 100_00000000; + listInput.Whitelists = new WhitelistInfoList() + { + Whitelists = { new WhitelistInfo() + { + AddressList = new AddressList(){Value = { User2Address }}, + PriceTag = new PriceTagInfo + { + TagName = "2ELF", + Price = new Price() + { + Symbol = "ELF", + Amount = 200_00000000 + } + } + }} + }; + + { + var executionResult = + await CreatorForestContractStub.ListWithFixedPrice.SendWithExceptionAsync(listInput); + executionResult.TransactionResult.Error.ShouldContain("too low"); + } + + listInput.Price.Amount = 200_00000000; + await CreatorForestContractStub.ListWithFixedPrice.SendAsync(listInput); + + var requestInfo = await CreatorForestContractStub.GetRequestInfo.CallAsync(new GetRequestInfoInput + { + Symbol = symbol, + TokenId = 2 + }); + requestInfo.ListTime.ShouldNotBeNull(); + + var whiteListId = (await CreatorForestContractStub.GetWhitelistId.CallAsync( + new GetWhitelistIdInput() + { + Symbol = symbol, + TokenId = 2, + Owner = DefaultAddress + })).WhitelistId; + var whitelistIds = await WhitelistContractStub.GetWhitelistByManager.CallAsync(ForestContractAddress); + whitelistIds.WhitelistId.Count.ShouldBe(1); + whitelistIds.WhitelistId[0].ShouldBe(whiteListId); + var whitelist = await WhitelistContractStub.GetWhitelistDetail.CallAsync(whiteListId); + whitelist.Value.Count.ShouldBe(1); + whitelist.Value[0].AddressList.Value[0].ShouldBe(User2Address); + var price = new Price(); + price.MergeFrom(whitelist.Value[0].Info.Info); + price.Symbol.ShouldBe("ELF"); + price.Amount.ShouldBe(160_00000000); + return symbol; + } + + [Fact] + public async Task BuyRequestedNFTTest() + { + var symbol = await ListForRequestedNFTTest(); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 40_00000000); + } + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 2, + OfferTo = DefaultAddress, + Price = new Price + { + Symbol = "ELF", + Amount = 160_00000000 + }, + Quantity = 1 + }); + + var requestInfo = await CreatorForestContractStub.GetRequestInfo.CallAsync(new GetRequestInfoInput + { + Symbol = symbol, + TokenId = 2 + }); + // Removed after dealing. + requestInfo.Price.ShouldBeNull(); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 200_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + // NFT price 200 ELF - Staking 10 ELF - 0.2 Service Fee ELF. + balance.Balance.ShouldBe(DefaultSenderInitialELFAmount + 200_00000000 - 10_00000000 - 20000000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 2, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 2, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(0); + } + } +} \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTests_Whitelist.cs b/test/Forest.Tests/ForestContractTests_Whitelist.cs new file mode 100644 index 00000000..84d6e7b6 --- /dev/null +++ b/test/Forest.Tests/ForestContractTests_Whitelist.cs @@ -0,0 +1,2336 @@ +using System.Linq; +using System.Threading.Tasks; +using AElf; +using AElf.Contracts.MultiToken; +using AElf.Contracts.NFT; +using AElf.Contracts.Whitelist; +using AElf.CSharp.Core.Extension; +using AElf.Kernel; +using AElf.Types; +using Google.Protobuf; +using Shouldly; +using Xunit; +using ApproveInput = AElf.Contracts.NFT.ApproveInput; +using CreateInput = AElf.Contracts.NFT.CreateInput; +using GetBalanceInput = AElf.Contracts.NFT.GetBalanceInput; +using TransferInput = AElf.Contracts.MultiToken.TransferInput; + +namespace Forest; + +public partial class ForestContractTests +{ + private const string BaseUri = "ipfs://aelf/"; + + [Fact(Skip = "Badge not found")] + public async Task DealToFixedPriceBuyerInWhiteList() + { + var symbol = await MintBadgeTest(); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 1, + Whitelists = new WhitelistInfoList + { + Whitelists = + { + new WhitelistInfo + { + AddressList = new AddressList {Value = {User2Address}}, + PriceTag = new PriceTagInfo + { + TagName = "10_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 10_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + { + var whiteListId = (await CreatorForestContractStub.GetWhitelistId.CallAsync( + new GetWhitelistIdInput() + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + })).WhitelistId; + var whitelistIds = + await WhitelistContractStub.GetWhitelistByManager.CallAsync(ForestContractAddress); + whitelistIds.WhitelistId.Count.ShouldBe(1); + whitelistIds.WhitelistId[0].ShouldBe(whiteListId); + + var whitelist = await WhitelistContractStub.GetWhitelistDetail.CallAsync(whiteListId); + whitelist.Value.Count.ShouldBe(1); + whitelist.Value[0].AddressList.Value[0].ShouldBe(User2Address); + + var price = new Price(); + price.MergeFrom(whitelist.Value[0].Info.Info); + price.Symbol.ShouldBe("ELF"); + price.Amount.ShouldBe(10_00000000); + } + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 10_00000000 + }, + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(1); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 10_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + // Because of 10/10000 service fee. + balance.Balance.ShouldBe(DefaultSenderInitialELFAmount + 10_00000000 - 10_00000000 / 1000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(0); + } + } + + [Fact(Skip = "Badge not found.")] + public async Task DealToFixedPriceBuyerInWhiteList_HigherPrice() + { + + var symbol = await MintBadgeTest(); + + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = DefaultAddress, + }); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 1, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 1, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList() {Value = {User2Address}}, + PriceTag = new PriceTagInfo + { + TagName = "110_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + }, + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(2); + offerList.Value[0].Quantity.ShouldBe(1); + offerList.Value[0].Price.Amount.ShouldBe(100_00000000); + offerList.Value[1].Quantity.ShouldBe(2); + offerList.Value[1].Price.Amount.ShouldBe(110_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 100_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = DefaultAddress + }); + // Because of 10/10000 service fee. + balance.Balance.ShouldBe(InitialELFAmount + 100_00000000 - 100_00000000 / 1000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(0); + } + } + + [Fact] + public async Task DealToFixedPriceBuyerInWhiteList_Complicated() + { + await InitializeForestContract(); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 10, + TokenId = 233 + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 10, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays(1) + }, + Quantity = 10, + Whitelists = new WhitelistInfoList + { + Whitelists = + { + new WhitelistInfo + { + AddressList = new AddressList {Value = {User2Address}}, + PriceTag = new PriceTagInfo + { + TagName = "90 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + + var whitelistId = (await SellerForestContractStub.GetWhitelistId.CallAsync(new GetWhitelistIdInput() + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + })).WhitelistId; + var whitelistPrice = await WhitelistContractStub.GetExtraInfoByAddress.CallAsync( + new GetExtraInfoByAddressInput + { + Address = User2Address, + WhitelistId = whitelistId + }); + whitelistPrice.TagName.ShouldBe("90 ELF"); + + { + var whitelistInfo = await WhitelistContractStub.GetWhitelist.CallAsync(whitelistId); + whitelistInfo.ExtraInfoIdList.Value.Single().AddressList.Value.Count.ShouldBe(1); + } + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 101_00000000 + }, + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value.First().Price.Amount.ShouldBe(101_00000000); + } + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + }); + nftBalance.Balance.ShouldBe(9); + } + + { + var whitelistInfo = await WhitelistContractStub.GetWhitelist.CallAsync(whitelistId); + whitelistInfo.ExtraInfoIdList.Value.Single().AddressList.Value.Count.ShouldBe(0); + } + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 3, + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + } + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(2); + offerList.Value.First().Price.Amount.ShouldBe(101_00000000); + offerList.Value.First().Quantity.ShouldBe(1); + offerList.Value.Last().Price.Amount.ShouldBe(110_00000000); + offerList.Value.Last().Quantity.ShouldBe(3); + } + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 201_00000000 + }, + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(3); + } + + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(4); + offerList.Value.Last().Quantity.ShouldBe(1); + offerList.Value.Last().Price.Amount.ShouldBe(200_00000000); + } + } + + private async Task Initialize() + { + await InitializeForestContract(); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 20, + Spender = ForestContractAddress + }); + return symbol; + } + + [Fact] + public async Task ListWithFixedPriceWhitelist() + { + var symbol = await Initialize(); + var executionResult1 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + var whitelistId = (await SellerForestContractStub.GetWhitelistId.CallAsync(new GetWhitelistIdInput() + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + })).WhitelistId; + var log = FixedPriceNFTListed.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(FixedPriceNFTListed)).NonIndexed); + log.WhitelistId.Value.ShouldBe(whitelistId); + log.Quantity.ShouldBe(10); + { + var ifExist1 = await WhitelistContractStub.GetAddressFromWhitelist.CallAsync( + new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = User2Address + }); + ifExist1.Value.ShouldBe(true); + } + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 101_00000000 + }, + }); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000); + } + + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + + + var executionResult2 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList() + { + Value = + { + User3Address, User4Address + } + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + }, + new WhitelistInfo() + { + AddressList = new AddressList() {Value = {User5Address}}, + PriceTag = new PriceTagInfo + { + TagName = "10_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 10_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + var log1 = FixedPriceNFTListed.Parser.ParseFrom(executionResult2.TransactionResult.Logs + .First(l => l.Name == nameof(FixedPriceNFTListed)).NonIndexed); + log1.Quantity.ShouldBe(10); + + var projectId = HashHelper.ComputeFrom($"{symbol}{233}{DefaultAddress}"); + var tagInfoId = HashHelper.ComputeFrom($"{whitelistId}{projectId}90_00000000 ELF"); + var ifExist = await WhitelistContractStub.GetAddressFromWhitelist.CallAsync( + new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = User2Address + }); + ifExist.Value.ShouldBe(false); + + var extraInfoList = await WhitelistContractStub.GetWhitelist.CallAsync(whitelistId); + extraInfoList.ExtraInfoIdList.Value.Count.ShouldBe(2); + + var tagIdList = await WhitelistContractStub.GetExtraInfoIdList.CallAsync(new GetExtraInfoIdListInput() + { + ProjectId = projectId, + WhitelistId = whitelistId + }); + tagIdList.Value.Count.ShouldBe(2); + tagIdList.Value[0].ShouldBe(tagInfoId); + + var tagInfo = await WhitelistContractStub.GetExtraInfoByTag.CallAsync(new GetExtraInfoByTagInput() + { + WhitelistId = whitelistId, + TagInfoId = tagInfoId + }); + tagInfo.AddressList.Value.Count.ShouldBe(2); + + var whitelistPrice = await WhitelistContractStub.GetExtraInfoByAddress.CallAsync( + new GetExtraInfoByAddressInput() + { + Address = User3Address, + WhitelistId = whitelistId + }); + whitelistPrice.TagName.ShouldBe("90_00000000 ELF"); + } + + [Fact] + public async Task ListWithFixedPriceWhitelist_whitelistFirstNull() + { + var symbol = await Initialize(); + var executionResult1 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + IsMergeToPreviousListedInfo = true, + IsWhitelistAvailable = true + }); + var whitelistId = (await SellerForestContractStub.GetWhitelistId.CallAsync(new GetWhitelistIdInput() + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + })).WhitelistId; + var log = FixedPriceNFTListed.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(FixedPriceNFTListed)).NonIndexed).WhitelistId; + log.Value.ShouldBe(whitelistId); + var extraInfo = await WhitelistContractStub.GetWhitelistDetail.CallAsync(whitelistId); + extraInfo.Value.Count.ShouldBe(0); + { + var ifExist1 = await WhitelistContractStub.GetAddressFromWhitelist.CallAsync( + new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = User2Address + }); + ifExist1.Value.ShouldBe(false); + } + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo + { + AddressList = new AddressList + { + Value = + { + User3Address, User4Address + } + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + }, + new WhitelistInfo + { + AddressList = new AddressList() {Value = {User5Address}}, + PriceTag = new PriceTagInfo + { + TagName = "10_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 10_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + + var projectId = HashHelper.ComputeFrom($"{symbol}{233}{DefaultAddress}"); + var tagInfoId = HashHelper.ComputeFrom($"{whitelistId}{projectId}90_00000000 ELF"); + var ifExist = await WhitelistContractStub.GetAddressFromWhitelist.CallAsync( + new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = User3Address + }); + ifExist.Value.ShouldBe(true); + + var extraInfoList = await WhitelistContractStub.GetWhitelist.CallAsync(whitelistId); + extraInfoList.ExtraInfoIdList.Value.Count.ShouldBe(2); + + var tagIdList = await WhitelistContractStub.GetExtraInfoIdList.CallAsync(new GetExtraInfoIdListInput() + { + ProjectId = projectId, + WhitelistId = whitelistId + }); + tagIdList.Value.Count.ShouldBe(2); + tagIdList.Value[0].ShouldBe(tagInfoId); + + var tagInfo = await WhitelistContractStub.GetExtraInfoByTag.CallAsync(new GetExtraInfoByTagInput() + { + WhitelistId = whitelistId, + TagInfoId = tagInfoId + }); + tagInfo.AddressList.Value.Count.ShouldBe(2); + + var whitelistPrice = await WhitelistContractStub.GetExtraInfoByAddress.CallAsync( + new GetExtraInfoByAddressInput() + { + Address = User3Address, + WhitelistId = whitelistId + }); + whitelistPrice.TagName.ShouldBe("90_00000000 ELF"); + } + + [Fact] + public async Task ListWithFixedPriceWhitelist_NoWhitelistContract() + { + await AdminForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + + await NFTContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 20, + Spender = ForestContractAddress + }); + + var executionResult1 = await SellerForestContractStub.ListWithFixedPrice.SendWithExceptionAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + IsMergeToPreviousListedInfo = true, + IsWhitelistAvailable = true + }); + executionResult1.TransactionResult.Error.ShouldContain("Whitelist Contract not initialized."); + } + + [Fact] + public async Task MakeOffer_Whitelist_Affordable() + { + var symbol = await Initialize(); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + var executionResult1 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 5, + Whitelists = new WhitelistInfoList + { + Whitelists = + { + new WhitelistInfo + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + var executionResult2 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + //PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User3Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "110_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + var whitelistId = (await SellerForestContractStub.GetWhitelistId.CallAsync(new GetWhitelistIdInput() + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + })).WhitelistId; + var log = FixedPriceNFTListed.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(FixedPriceNFTListed)).NonIndexed).WhitelistId; + log.Value.ShouldBe(whitelistId); + { + var ifExist1 = await WhitelistContractStub.GetAddressFromWhitelist.CallAsync( + new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = User2Address + }); + ifExist1.Value.ShouldBe(true); + } + { + var ifExist2 = await WhitelistContractStub.GetAddressFromWhitelist.CallAsync( + new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = User3Address + }); + ifExist2.Value.ShouldBe(true); + } + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + await NFTBuyer2TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await Buyer2ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 3, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000 - 200_00000000); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User3Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 110_00000000 - 200_00000000 - 200_00000000); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(2); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User3Address + }); + nftBalance.Balance.ShouldBe(3); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(0); + } + } + + [Fact] + public async Task MakeOfferTest_WithWhitelist_ToOfferList() + { + var symbol = await Initialize(); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 5, + IsWhitelistAvailable = true, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsMergeToPreviousListedInfo = false + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + }); + await NFTBuyer2TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult2 = await Buyer2ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 5, + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + } + }); + var quantity = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed).Quantity; + quantity.ShouldBe(4); + var log = ListedNFTRemoved.Parser.ParseFrom(executionResult2.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTRemoved)).NonIndexed); + log.Price.Amount.ShouldBe(100_00000000); + log.Symbol.ShouldBe(symbol); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User3Address + }); + nftBalance.Balance.ShouldBe(4); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User3Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 400_00000000); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value[0].Price.Amount.ShouldBe(90_00000000); + } + { + var offerList = await Buyer2ForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User3Address + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value[0].Price.Amount.ShouldBe(110_00000000); + } + } + + [Fact] + public async Task MakeOffer_NotSetPublicTime() + { + var symbol = await Initialize(); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + + var executionResult1 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + //PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 5, + Whitelists = new WhitelistInfoList + { + Whitelists = + { + new WhitelistInfo + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + var executionResult2 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + //PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + Whitelists = new WhitelistInfoList + { + Whitelists = + { + new WhitelistInfo + { + AddressList = new AddressList + { + Value = {User3Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "110_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + }); + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + }, + }); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000 - 100_00000000); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(2); + } + await NFTBuyer2TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await Buyer2ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 3, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User3Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 110_00000000 - 100_00000000 - 100_00000000); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User3Address + }); + nftBalance.Balance.ShouldBe(3); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value[0].Quantity.ShouldBe(2); + } + } + + [Fact] + public async Task MakeOfferTest_NotInWhitelist() + { + { + var symbol = await Initialize(); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 10, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User3Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 101_00000000 + }, + }); + var quantity = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed).Quantity; + quantity.ShouldBe(8); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 200_00000000); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(2); + } + await NFTBuyer2TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult2 = await Buyer2ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + }, + }); + var quantity1 = ListedNFTChanged.Parser.ParseFrom(executionResult2.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed).Quantity; + quantity1.ShouldBe(7); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User3Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000); + } + } + } + + [Fact] + public async Task MakeOfferTest_WithPublicTime() + { + var symbol = await Initialize(); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 5, + IsWhitelistAvailable = true, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsMergeToPreviousListedInfo = false + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 2, + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + }); + await NFTBuyer2TokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult2 = await Buyer2ForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + //OfferTo = DefaultAddress, + Quantity = 5, + Price = new Price + { + Symbol = "ELF", + Amount = 110_00000000 + } + }); + var quantity = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed).Quantity; + quantity.ShouldBe(4); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User3Address + }); + nftBalance.Balance.ShouldBe(0); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User3Address + }); + balance.Balance.ShouldBe(InitialELFAmount); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value[0].Price.Amount.ShouldBe(90_00000000); + } + { + var offerList = await Buyer2ForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User3Address + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value[0].Quantity.ShouldBe(5); + offerList.Value[0].Price.Amount.ShouldBe(110_00000000); + } + { + var offerTo = OfferAdded.Parser.ParseFrom(executionResult2.TransactionResult.Logs + .First(l => l.Name == nameof(OfferAdded)).NonIndexed).OfferTo; + offerTo.ShouldBe(DefaultAddress); + } + } + + [Fact] + public async Task CreateBadgeTest_new() + { + await BuyerForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + await BuyerForestContractStub.SetWhitelistContract.SendAsync(WhitelistContractAddress); + var createWhitelistInput = new CreateWhitelistInput + { + ProjectId = HashHelper.ComputeFrom("Badge Test"), + Creator = ForestContractAddress, + ExtraInfoList = new ExtraInfoList() + { + Value = + { + new ExtraInfo() + { + AddressList = new AElf.Contracts.Whitelist.AddressList + { + Value = {User1Address, User2Address} + } + } + } + }, + IsCloneable = true, + StrategyType = StrategyType.Basic, + Remark = "Badge Test" + }; + var createWhitelistResult = await WhitelistContractStub.CreateWhitelist.SendAsync(createWhitelistInput); + var whitelistId = createWhitelistResult.Output; + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + BaseUri = BaseUri, + Creator = DefaultAddress, + IsBurnable = true, + Metadata = new Metadata + { + Value = + { + {"Description", "Stands for the human race."} + } + }, + NftType = NFTType.Badges.ToString(), + ProtocolName = "Badge", + IsTokenIdReuse = true, + MinterList = new MinterList + { + Value = {ForestContractAddress} + }, + TotalSupply = 1_000_000_000 // One billion + }); + var symbol = executionResult.Output.Value; + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "badge", + Metadata = new Metadata + { + Value = + { + {"Special Property", "A Value"}, + {"aelf_badge_whitelist", whitelistId.ToHex()} + } + }, + Owner = DefaultAddress, + Uri = $"{BaseUri}foo" + }); + await BuyerForestContractStub.MintBadge.SendAsync(new MintBadgeInput() + { + Symbol = symbol, + TokenId = 1 + }); + var exception = await Buyer2ForestContractStub.MintBadge.SendWithExceptionAsync(new MintBadgeInput() + { + Symbol = symbol, + TokenId = 1 + }); + exception.TransactionResult.Error.ShouldContain("No permission."); + { + var ifExist = await WhitelistContractStub.GetAddressFromWhitelist.CallAsync( + new GetAddressFromWhitelistInput() + { + WhitelistId = whitelistId, + Address = User2Address + }); + ifExist.Value.ShouldBe(false); + } + + var userBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 1, + Owner = User2Address + }); + userBalance.Balance.ShouldBe(1); + return symbol; + } + + [Fact] + public async Task CreateBadgeTest_new_whitelistIdNull() + { + await BuyerForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + await BuyerForestContractStub.SetWhitelistContract.SendAsync(WhitelistContractAddress); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + BaseUri = BaseUri, + Creator = DefaultAddress, + IsBurnable = true, + Metadata = new Metadata + { + Value = + { + {"Description", "Stands for the human race."} + } + }, + NftType = NFTType.Badges.ToString(), + ProtocolName = "Badge", + TotalSupply = 1_000_000_000 // One billion + }); + var symbol = executionResult.Output.Value; + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "badge", + Metadata = new Metadata + { + Value = + { + {"Special Property", "A Value"}, + {"aelf_badge_whitelist", new Hash().ToHex()} + } + }, + Owner = DefaultAddress, + Uri = $"{BaseUri}foo" + }); + var executionResult1 = await BuyerForestContractStub.MintBadge.SendWithExceptionAsync(new MintBadgeInput() + { + Symbol = symbol, + TokenId = 1 + }); + executionResult1.TransactionResult.Error.ShouldContain("No whitelist."); + return symbol; + } + + [Fact] + public async Task List_Duplicate() + { + var symbol = await Initialize(); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + var executionResult1 = await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 5_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + //PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "1_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 1_00000000 + } + } + } + } + }, + IsWhitelistAvailable = true + }); + var whitelistId = (await SellerForestContractStub.GetWhitelistId.CallAsync(new GetWhitelistIdInput + { + Symbol = symbol, + TokenId = 233, + Owner = DefaultAddress + })).WhitelistId; + var log = ListedNFTAdded.Parser + .ParseFrom(executionResult1.TransactionResult.Logs.First(l => l.Name == nameof(ListedNFTAdded)).NonIndexed) + .WhitelistId; + log.ShouldBe(whitelistId); + await SellerForestContractStub.ListWithFixedPrice.SendAsync( + new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 10_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + //PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 10, + IsWhitelistAvailable = true, + IsMergeToPreviousListedInfo = false + }); + var log1 = ListedNFTAdded.Parser + .ParseFrom(executionResult1.TransactionResult.Logs.First(l => l.Name == nameof(ListedNFTAdded)).NonIndexed) + .WhitelistId; + log1.ShouldBe(whitelistId); + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 5, + Price = new Price + { + Symbol = "ELF", + Amount = 10_00000000 + }, + }); + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 1_00000000 - 5_00000000 * 4); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + TokenId = 233, + Address = User2Address + }); + offerList.Value.Count.ShouldBe(0); + } + } + + [Fact] + public async Task MakeOfferTest_WithWhitelistZero() + { + await AdminForestContractStub.Initialize.SendAsync(new InitializeInput + { + NftContractAddress = NFTContractAddress, + ServiceFeeReceiver = MarketServiceFeeReceiverAddress + }); + + await AdminForestContractStub.SetWhitelistContract.SendAsync(WhitelistContractAddress); + + var executionResult = await NFTContractStub.Create.SendAsync(new CreateInput + { + ProtocolName = "aelf Collections", + NftType = NFTType.Collectables.ToString(), + TotalSupply = 1000, + IsBurnable = false, + IsTokenIdReuse = true + }); + var symbol = executionResult.Output.Value; + + await NFTContractStub.Mint.SendAsync(new MintInput + { + Symbol = symbol, + Alias = "test", + Quantity = 20, + TokenId = 233 + }); + + // await TokenContractStub.Issue.SendAsync(new IssueInput + // { + // Symbol = "ELF", + // Amount = InitialELFAmount, + // To = DefaultAddress, + // }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User2Address, + }); + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = InitialELFAmount, + To = User3Address, + }); + + await NFTContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = symbol, + TokenId = 233, + Amount = 100, + Spender = ForestContractAddress + }); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24, + PublicTime = TimestampHelper.GetUtcNow().AddDays((1)) + }, + Quantity = 5, + IsWhitelistAvailable = true, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "0_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 0_00000000 + } + } + } + } + }, + IsMergeToPreviousListedInfo = false + }); + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 1, + Price = new Price + { + Symbol = "ELF", + Amount = 0_00000000 + } + }); + var quantity = ListedNFTChanged.Parser.ParseFrom(executionResult1.TransactionResult.Logs + .First(l => l.Name == nameof(ListedNFTChanged)).NonIndexed).Quantity; + quantity.ShouldBe(4); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(1); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 0_00000000); + } + } + + [Fact] + public async Task MakeOffer_WhitelistToOfferList() + { + var symbol = await Initialize(); + + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 100_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 5, + IsWhitelistAvailable = true, + Whitelists = new WhitelistInfoList() + { + Whitelists = + { + new WhitelistInfo() + { + AddressList = new AddressList + { + Value = {User2Address} + }, + PriceTag = new PriceTagInfo + { + TagName = "90_00000000 ELF", + Price = new Price + { + Symbol = "ELF", + Amount = 90_00000000 + } + } + } + } + }, + IsMergeToPreviousListedInfo = false + }); + await SellerForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPriceInput + { + Symbol = symbol, + TokenId = 233, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + Duration = new ListDuration + { + DurationHours = 24 + }, + Quantity = 7, + IsWhitelistAvailable = true, + IsMergeToPreviousListedInfo = false + }); + + await NFTBuyerTokenContractStub.Approve.SendAsync(new AElf.Contracts.MultiToken.ApproveInput + { + Symbol = "ELF", + Amount = long.MaxValue, + Spender = ForestContractAddress + }); + var executionResult1 = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput + { + Symbol = symbol, + TokenId = 233, + OfferTo = DefaultAddress, + Quantity = 13, + Price = new Price + { + Symbol = "ELF", + Amount = 200_00000000 + }, + }); + { + var nftBalance = await NFTContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = symbol, + TokenId = 233, + Owner = User2Address + }); + nftBalance.Balance.ShouldBe(12); + } + { + var balance = await TokenContractStub.GetBalance.CallAsync(new AElf.Contracts.MultiToken.GetBalanceInput + { + Symbol = "ELF", + Owner = User2Address + }); + balance.Balance.ShouldBe(InitialELFAmount - 90_00000000 - 100_00000000 * 4 - 200_00000000 * 7); + } + { + var offerList = await BuyerForestContractStub.GetOfferList.CallAsync(new GetOfferListInput + { + Symbol = symbol, + Address = User2Address, + TokenId = 233 + }); + offerList.Value.Count.ShouldBe(1); + offerList.Value[0].Quantity.ShouldBe(1); + offerList.Value[0].Price.Amount.ShouldBe(200_00000000); + } + } +} \ No newline at end of file