diff --git a/.bettercodehub.yml b/.bettercodehub.yml index 4ace3ad3ba..d1a64f481a 100644 --- a/.bettercodehub.yml +++ b/.bettercodehub.yml @@ -1,4 +1,6 @@ +# Depth of components to analyze (2 levels deep) component_depth: 2 +# Programming languages to analyze languages: - csharp diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6e4b2c3526..ea59817cf7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,31 +1,33 @@ --- -name: Bug report -about: Create a report to help us improve +name: 👾 Bug Report +about: Report a bug or issue with the project. title: '' -labels: '' +labels: 'bug' assignees: '' --- -**Describe the bug** +### Description A clear and concise description of what the bug is. -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error +### Steps To Reproduce +1. Log in... +2. Ensure that... +3. Allow a long period of inactivity to pass... +4. Observe that... +5. Attempt to log in... -**Expected behavior** -A clear and concise description of what you expected to happen. +### Current Behavior +- After the period of inactivity... +- When the user tries to log in using another method... +- This causes a bug due to... -**Screenshots** -If applicable, add screenshots to help explain your problem. +### Expected Behavior +- After a long period of inactivity... +- When a user logs in successfully... +- This ensures that only... -**Environment (please complete the following information):** - - OS and version: [e.g. Ubuntu 18.04] - - Dotnet core version [e.g. 2.2.106] - -**Additional context** -Add any other context about the problem here. +### Environment +- Platform: PC +- Node: v18.18.0 +- Browser: Chrome 126.0.6478.56 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..163c1c437c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +blank_issues_enabled: false +issue_template: + - name: 👾 Bug Report + description: Report a bug or issue with the project. + labels: ["bug"] + template: bug_report.md + - name: 💡 Feature Request + description: Create a new ticket for a new feature request. + labels: ["enhancement"] + template: feature_request.md \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d61..a4b817533a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,27 @@ --- -name: Feature request -about: Suggest an idea for this project +name: 💡 Feature Request +about: Create a new ticket for a new feature request title: '' -labels: '' +labels: 'enhancement' assignees: '' --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +### Expected Behavior +Describe the expected behavior here. -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +### Specifications +As a `user`, I would like to `action` so that `reason`. -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +**Features:** +- describe feature details here. -**Additional context** -Add any other context or screenshots about the feature request here. +**Development Tasks:** +- [ ] Task 1 +- [ ] Task 2 + +### Dependencies +List any dependencies that are required for this feature by providing links to the issues or repositories. + +### References +List any references that are related to this feature request. \ No newline at end of file diff --git a/.github/workflows/benchmark_action.yml b/.github/workflows/benchmark_action.yml new file mode 100644 index 0000000000..1717d86906 --- /dev/null +++ b/.github/workflows/benchmark_action.yml @@ -0,0 +1,48 @@ +name: benchmark_action +on: + push: + tags: + - '**' + branches: + - '**' + + + +env: + DOTNET_INSTALL_DIR: "./.dotnet" + Solution_Name: AElf.All.sln + Service_Name: AELF + +jobs: + test: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0' + + - name: 'Download AElf build tools' + run: bash scripts/download_binary.sh + + - name: 'Install protobuf' + run: bash scripts/install_protobuf.sh + + - name: Install dependencies + run: dotnet restore bench/AElf.Benchmark/AElf.Benchmark.csproj --verbosity quiet + + - name: BenchMark + run: | + cd bench/AElf.Benchmark + dotnet run --filter '*MinerTests*' + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.Service_Name }} + path: bench/AElf.Benchmark/BenchmarkDotNet.Artifacts/results + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/sonarqube.yaml b/.github/workflows/sonarqube.yaml new file mode 100644 index 0000000000..6b1c8bafc3 --- /dev/null +++ b/.github/workflows/sonarqube.yaml @@ -0,0 +1,49 @@ +on: + pull_request: + types: [opened, synchronize, reopened] + +name: PR Static Code Analysis +jobs: + static-code-analysis: + runs-on: ubuntu-latest + steps: + - name: Code Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '7.0' + - name: Create temporary global.json + run: echo '{"sdk":{"version":"8.0.7"}}' > ./global.json + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + - name: Cache SonarQube packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache SonarQube scanner + id: cache-sonar-scanner + uses: actions/cache@v1 + with: + path: ./.sonar/scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + - name: Install SonarScanner for .NET + run: dotnet tool update dotnet-sonarscanner --tool-path ./.sonar/scanner + - name: Add .NET global tools to PATH + run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH + - name: Install protobuf + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - name: Begin SonarQube analysis + run: | + ./.sonar/scanner/dotnet-sonarscanner begin /k:"AElf" /d:sonar.host.url="${{ secrets.SONAR_HOST_URL }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + dotnet build AElf.All.sln + ./.sonar/scanner/dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 04d0c6ea86..49a6e74115 100644 --- a/.gitignore +++ b/.gitignore @@ -312,4 +312,4 @@ tools .idea/.idea.AElf/.idea/projectSettingsUpdater.xml .idea/.idea.AElf/.idea/vcs.xml .idea/.idea.AElf/.idea/workspace.xml -.idea/.idea.AElf/riderModule.iml +.idea/.idea.AElf/riderModule.iml \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml index 56b2002e45..c717656544 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,7 +14,7 @@ sphinx: # Optionally build your docs in additional formats such as PDF and ePub formats: all -# Optionally set the version of Python and requirements required to build your docs +# Optionally set the version of python and requirements required to build your docs python: version: 3.7 install: diff --git a/AElf.ContractTools.targets b/AElf.ContractTools.targets index 3c3608d258..86de821de0 100644 --- a/AElf.ContractTools.targets +++ b/AElf.ContractTools.targets @@ -32,7 +32,8 @@ - + + $(ProjectDir)/Protobuf diff --git a/AElf.Contracts.sln b/AElf.Contracts.sln index ac50a9f51e..486a48541b 100644 --- a/AElf.Contracts.sln +++ b/AElf.Contracts.sln @@ -1,5 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "contract", "contract", "{651F0F6E-86CF-42D2-9110-5F3EAE5704F0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{26990847-E0A2-4FCC-8C71-267CA77557CD}" diff --git a/AElf.sln.DotSettings b/AElf.sln.DotSettings index 04a90e14d0..f728bd7795 100644 --- a/AElf.sln.DotSettings +++ b/AElf.sln.DotSettings @@ -1,14 +1,17 @@ - + False - JS - True - True - True - True - True - True - True - True - True - True - True + JS + True + True + True + True + True + True + True + True + True + True + True diff --git a/CodeCoverage.runsettings b/CodeCoverage.runsettings index 609fa6a0a2..bf1e25abec 100644 --- a/CodeCoverage.runsettings +++ b/CodeCoverage.runsettings @@ -1,4 +1,5 @@ + diff --git a/appveyor.yml b/appveyor.yml index 7be64af079..8239ba530d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,4 @@ +# appveyor configuration file version: 1.0.0.{build} skip_tags: true os: Windows Server 2012 R2 diff --git a/azure-myget-publish.yml b/azure-myget-publish.yml index d271eb5595..9a404026cb 100644 --- a/azure-myget-publish.yml +++ b/azure-myget-publish.yml @@ -1,3 +1,4 @@ + # Azure DevOps pipeline configuration for publishing NuGet packages to MyGet pool: vmImage: ubuntu-latest pr: none diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c7761f5875..ab968a5312 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,4 +1,4 @@ -# File: azure-pipelines.yml +# Azure Pipelines configuration file jobs: - template: templates/build-template-window.yml parameters: diff --git a/bench/AElf.Benchmark/HtmlSummaryExporter.cs b/bench/AElf.Benchmark/HtmlSummaryExporter.cs new file mode 100644 index 0000000000..1b9f7756c1 --- /dev/null +++ b/bench/AElf.Benchmark/HtmlSummaryExporter.cs @@ -0,0 +1,82 @@ +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Reports; + +using System.Collections.Generic; +using System.IO; + + +public class HtmlSummaryExporter : IExporter +{ + public string Name => nameof(HtmlSummaryExporter); + + public void ExportToLog(Summary summary, ILogger logger) + { + + } + + public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger) + { + string directoryPath = summary.ResultsDirectoryPath; + string outputPath = Path.Combine(directoryPath, "Summary.html"); + + var htmlFiles = Directory.GetFiles(directoryPath, "*.html"); + + using (StreamWriter writer = new StreamWriter(outputPath)) + { + writer.WriteLine(""); + writer.WriteLine(""); + writer.WriteLine("Benchmark Summary"); + + writer.WriteLine(""); + + writer.WriteLine(""); + writer.WriteLine(""); + + foreach (var file in htmlFiles) + { + string fileName = Path.GetFileName(file); + writer.WriteLine($"

{fileName}

"); + string content = File.ReadAllText(file); + string bodyContent = GetBodyContent(content); + writer.WriteLine(bodyContent); + } + + writer.WriteLine(""); + writer.WriteLine(""); + } + + consoleLogger.WriteLine($"Summary HTML file created successfully at {outputPath}."); + + return new[] { outputPath }; + } + + private string GetBodyContent(string html) + { + int bodyStartIndex = html.IndexOf("") + "".Length; + int bodyEndIndex = html.IndexOf(""); + if (bodyStartIndex >= 0 && bodyEndIndex >= 0) + { + return html.Substring(bodyStartIndex, bodyEndIndex - bodyStartIndex); + } + return string.Empty; + } + + private string GetStyleContent(string html) + { + int styleStartIndex = html.IndexOf(""); + if (styleStartIndex >= 0 && styleEndIndex >= 0) + { + return html.Substring(styleStartIndex, styleEndIndex - styleStartIndex); + } + return string.Empty; + } +} \ No newline at end of file diff --git a/bench/AElf.Benchmark/Program.cs b/bench/AElf.Benchmark/Program.cs index bc0d10d859..9d71a23611 100644 --- a/bench/AElf.Benchmark/Program.cs +++ b/bench/AElf.Benchmark/Program.cs @@ -2,6 +2,10 @@ using System.IO; using System.Reflection; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Exporters.Xml; +using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; using Volo.Abp; @@ -18,8 +22,13 @@ private static void Main(string[] args) })) { application.Initialize(); - BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig()); - } + var config = new DebugInProcessConfig() + .WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(50)) + .AddExporter(XmlExporter.Default) + .AddExporter(HtmlExporter.Default) + .AddExporter(new HtmlSummaryExporter()) + .AddExporter(CsvExporter.Default); + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); } } private static void RegisterAssemblyResolveEvent() diff --git a/build.cake b/build.cake index 11219ab033..e32a698f15 100755 --- a/build.cake +++ b/build.cake @@ -112,7 +112,6 @@ Task("Test-with-Codecov") actions.Add(action); } - var options = new ParallelOptions { MaxDegreeOfParallelism = 1, //CancellationToken = cancellationToken diff --git a/build.config b/build.config index 015ef8eca4..d956e69e20 100644 --- a/build.config +++ b/build.config @@ -1,3 +1,3 @@ #!/usr/bin/env bash CAKE_VERSION=0.37.0 -DOTNET_VERSION=6.0.300 +DOTNET_VERSION=6.0.300 \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index e48d09a09c..cb56d5562f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -6,6 +6,7 @@ $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=*') { diff --git a/build.sh b/build.sh index 06bcee8070..4f00fd2e4e 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Define varibles +# Define variables SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) source $SCRIPT_DIR/build.config TOOLS_DIR=$SCRIPT_DIR/tools diff --git a/common.props b/common.props index bae225b0b3..95bad4d697 100644 --- a/common.props +++ b/common.props @@ -11,4 +11,4 @@ - + \ No newline at end of file diff --git a/contract/AElf.Contracts.Association/Association.cs b/contract/AElf.Contracts.Association/Association.cs index db9a2d81dd..f00b978f27 100644 --- a/contract/AElf.Contracts.Association/Association.cs +++ b/contract/AElf.Contracts.Association/Association.cs @@ -35,7 +35,9 @@ public override ProposalOutput GetProposal(Hash proposalId) ToBeReleased = readyToRelease, ApprovalCount = proposal.Approvals.Count, RejectionCount = proposal.Rejections.Count, - AbstentionCount = proposal.Abstentions.Count + AbstentionCount = proposal.Abstentions.Count, + Title = proposal.Title, + Description = proposal.Description }; } diff --git a/contract/AElf.Contracts.Association/AssociationConstants.cs b/contract/AElf.Contracts.Association/AssociationConstants.cs new file mode 100644 index 0000000000..d2afee8e13 --- /dev/null +++ b/contract/AElf.Contracts.Association/AssociationConstants.cs @@ -0,0 +1,8 @@ +namespace AElf.Contracts.Association; + +public static class AssociationConstants +{ + public const int MaxLengthForTitle = 255; + public const int MaxLengthForDescription = 10200; + public const int MaxLengthForProposalDescriptionUrl = 255; +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Association/Association_Helper.cs b/contract/AElf.Contracts.Association/Association_Helper.cs index c246df26ee..cc1a7d0643 100644 --- a/contract/AElf.Contracts.Association/Association_Helper.cs +++ b/contract/AElf.Contracts.Association/Association_Helper.cs @@ -144,6 +144,7 @@ private Hash GenerateProposalId(CreateProposalInput input) private Hash CreateNewProposal(CreateProposalInput input) { + CheckCreateProposalInput(input); var proposalId = GenerateProposalId(input); var proposal = new ProposalInfo { @@ -154,7 +155,9 @@ private Hash CreateNewProposal(CreateProposalInput input) OrganizationAddress = input.OrganizationAddress, ProposalId = proposalId, Proposer = Context.Sender, - ProposalDescriptionUrl = input.ProposalDescriptionUrl + ProposalDescriptionUrl = input.ProposalDescriptionUrl, + Title = input.Title, + Description = input.Description }; Assert(Validate(proposal), "Invalid proposal."); Assert(State.Proposals[proposalId] == null, "Proposal already exists."); @@ -162,8 +165,21 @@ private Hash CreateNewProposal(CreateProposalInput input) Context.Fire(new ProposalCreated { ProposalId = proposalId, - OrganizationAddress = input.OrganizationAddress + OrganizationAddress = input.OrganizationAddress, + Title = input.Title, + Description = input.Description }); return proposalId; } + + private void CheckCreateProposalInput(CreateProposalInput input) + { + // Check the length of title + Assert(input.Title.Length <= AssociationConstants.MaxLengthForTitle, "Title is too long."); + // Check the length of description + Assert(input.Description.Length <= AssociationConstants.MaxLengthForDescription, "Description is too long."); + // Check the length of description url + Assert(input.ProposalDescriptionUrl.Length <= AssociationConstants.MaxLengthForProposalDescriptionUrl, + "Description url is too long."); + } } \ No newline at end of file diff --git a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContractConstants.cs b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContractConstants.cs index 9c4f4c1599..8fd009bab6 100644 --- a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContractConstants.cs +++ b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContractConstants.cs @@ -12,4 +12,5 @@ public static class AEDPoSContractConstants public const string SideChainShareProfitsTokenSymbol = "SHARE"; public const string PayTxFeeSymbolListName = "SymbolListToPayTxFee"; public const string PayRentalSymbolListName = "SymbolListToPayRental"; + public const string SecretSharingEnabledConfigurationKey = "SecretSharingEnabled"; } \ No newline at end of file diff --git a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_GetConsensusBlockExtraData.cs b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_GetConsensusBlockExtraData.cs index bbbce356a4..70d9a25596 100644 --- a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_GetConsensusBlockExtraData.cs +++ b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_GetConsensusBlockExtraData.cs @@ -118,7 +118,11 @@ private AElfConsensusHeaderInformation GetConsensusExtraDataToPublishOutValue(Ro updatedRound.RealTimeMinersInformation[pubkey].ImpliedIrreversibleBlockHeight = Context.CurrentHeight; // Update secret pieces of latest in value. - UpdateLatestSecretPieces(updatedRound, pubkey, triggerInformation); + + if (IsSecretSharingEnabled()) + { + UpdateLatestSecretPieces(updatedRound, pubkey, triggerInformation); + } // To publish Out Value. return new AElfConsensusHeaderInformation diff --git a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_ProcessConsensusInformation.cs b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_ProcessConsensusInformation.cs index 46240b69ec..88632c10ff 100644 --- a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_ProcessConsensusInformation.cs +++ b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_ProcessConsensusInformation.cs @@ -249,7 +249,10 @@ private void ProcessUpdateValue(UpdateValueInput updateValueInput) minerInRound.ProducedBlocks = minerInRound.ProducedBlocks.Add(1); minerInRound.ProducedTinyBlocks = minerInRound.ProducedTinyBlocks.Add(1); - PerformSecretSharing(updateValueInput, minerInRound, currentRound, _processingBlockMinerPubkey); + if (IsSecretSharingEnabled()) + { + PerformSecretSharing(updateValueInput, minerInRound, currentRound, _processingBlockMinerPubkey); + } foreach (var tuneOrder in updateValueInput.TuneOrderInformation) currentRound.RealTimeMinersInformation[tuneOrder.Key].FinalOrderOfNextRound = tuneOrder.Value; diff --git a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_SecretSharing.cs b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_SecretSharing.cs index 57f132e0de..7fafcc8d67 100644 --- a/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_SecretSharing.cs +++ b/contract/AElf.Contracts.Consensus.AEDPoS/AEDPoSContract_SecretSharing.cs @@ -1,6 +1,9 @@ using System.Linq; using AElf.Cryptography.SecretSharing; using AElf.CSharp.Core; +using AElf.Sdk.CSharp; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; namespace AElf.Contracts.Consensus.AEDPoS; @@ -49,4 +52,28 @@ private void RevealSharedInValues(Round currentRound, string publicKey) currentRound.RealTimeMinersInformation[publicKeyOfAnotherMiner].PreviousInValue = revealedInValue; } } + + private bool IsSecretSharingEnabled() + { + if (State.ConfigurationContract.Value == null) + { + var configurationContractAddress = + Context.GetContractAddressByName(SmartContractConstants.ConfigurationContractSystemName); + if (configurationContractAddress == null) + { + // Which means Configuration Contract hasn't been deployed yet. + return false; + } + + State.ConfigurationContract.Value = configurationContractAddress; + } + + var secretSharingEnabled = new BoolValue(); + secretSharingEnabled.MergeFrom(State.ConfigurationContract.GetConfiguration.Call(new StringValue + { + Value = AEDPoSContractConstants.SecretSharingEnabledConfigurationKey + }).Value); + + return secretSharingEnabled.Value; + } } \ No newline at end of file diff --git a/contract/AElf.Contracts.Consensus.AEDPoS/AElf.Contracts.Consensus.AEDPoS.csproj b/contract/AElf.Contracts.Consensus.AEDPoS/AElf.Contracts.Consensus.AEDPoS.csproj index 03898303f4..49c8eed8af 100644 --- a/contract/AElf.Contracts.Consensus.AEDPoS/AElf.Contracts.Consensus.AEDPoS.csproj +++ b/contract/AElf.Contracts.Consensus.AEDPoS/AElf.Contracts.Consensus.AEDPoS.csproj @@ -47,6 +47,9 @@ Protobuf\Proto\reference\profit_contract.proto + + Protobuf\Proto\reference\configuration_contract.proto + diff --git a/contract/AElf.Contracts.Consensus.AEDPoS/ContractsReferences.cs b/contract/AElf.Contracts.Consensus.AEDPoS/ContractsReferences.cs index 48852984c8..19274e6b45 100644 --- a/contract/AElf.Contracts.Consensus.AEDPoS/ContractsReferences.cs +++ b/contract/AElf.Contracts.Consensus.AEDPoS/ContractsReferences.cs @@ -1,3 +1,4 @@ +using AElf.Contracts.Configuration; using AElf.Contracts.Election; using AElf.Contracts.MultiToken; using AElf.Contracts.Parliament; @@ -15,4 +16,5 @@ public partial class AEDPoSContractState internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; } internal TokenHolderContractContainer.TokenHolderContractReferenceState TokenHolderContract { get; set; } internal ParliamentContractContainer.ParliamentContractReferenceState ParliamentContract { get; set; } + internal ConfigurationContainer.ConfigurationReferenceState ConfigurationContract { get; set; } } \ No newline at end of file diff --git a/contract/AElf.Contracts.Genesis/BasicContractZero.cs b/contract/AElf.Contracts.Genesis/BasicContractZero.cs index d673fbf306..536bc87bb6 100644 --- a/contract/AElf.Contracts.Genesis/BasicContractZero.cs +++ b/contract/AElf.Contracts.Genesis/BasicContractZero.cs @@ -84,6 +84,12 @@ public override Int32Value GetContractProposalExpirationTimePeriod(Empty input) return new Int32Value { Value = expirationTimePeriod }; } + public override Int32Value GetCodeCheckProposalExpirationTimePeriod(Empty input) + { + var expirationTimePeriod = GetCodeCheckProposalExpirationTimePeriod(); + return new Int32Value { Value = expirationTimePeriod }; + } + public override Address GetSigner(Address input) { return State.SignerMap[input]; @@ -245,7 +251,7 @@ public override Hash ProposeContractCodeCheck(ContractCodeCheckInput input) ContractMethodName = input.CodeCheckReleaseMethod, Params = input.ContractInput, OrganizationAddress = codeCheckController.OwnerAddress, - ExpiredTime = Context.CurrentBlockTime.AddSeconds(CodeCheckProposalExpirationTimePeriod) + ExpiredTime = Context.CurrentBlockTime.AddSeconds(GetCodeCheckProposalExpirationTimePeriod()) }, OriginProposer = proposedInfo.Proposer }; @@ -392,6 +398,14 @@ public override Empty SetContractProposalExpirationTimePeriod(SetContractProposa return new Empty(); } + public override Empty SetCodeCheckProposalExpirationTimePeriod(Int32Value input) + { + AssertSenderAddressWith(State.ContractDeploymentController.Value.OwnerAddress); + Assert(input.Value > 0, "Invalid expiration time period."); + State.CodeCheckProposalExpirationTimePeriod.Value = input.Value; + return new Empty(); + } + public override DeployUserSmartContractOutput DeployUserSmartContract(UserContractDeploymentInput input) { AssertInlineDeployOrUpdateUserContract(); diff --git a/contract/AElf.Contracts.Genesis/BasicContractZeroState.cs b/contract/AElf.Contracts.Genesis/BasicContractZeroState.cs index 4e6a5dc8a6..8324b35afe 100644 --- a/contract/AElf.Contracts.Genesis/BasicContractZeroState.cs +++ b/contract/AElf.Contracts.Genesis/BasicContractZeroState.cs @@ -36,4 +36,7 @@ public partial class BasicContractZeroState : ContractState public SingletonState ContractProposalExpirationTimePeriod { get; set; } public MappedState SignerMap { get; set; } + + public SingletonState CodeCheckProposalExpirationTimePeriod { get; set; } + } \ No newline at end of file diff --git a/contract/AElf.Contracts.Genesis/BasicContractZero_Constants.cs b/contract/AElf.Contracts.Genesis/BasicContractZero_Constants.cs index 6ed4e650d4..03911596ef 100644 --- a/contract/AElf.Contracts.Genesis/BasicContractZero_Constants.cs +++ b/contract/AElf.Contracts.Genesis/BasicContractZero_Constants.cs @@ -3,7 +3,7 @@ namespace AElf.Contracts.Genesis; public partial class BasicContractZero { public const int ContractProposalExpirationTimePeriod = 259200; // 60 * 60 * 72 - public const int CodeCheckProposalExpirationTimePeriod = 600; // 60 * 10 + public const int DefaultCodeCheckProposalExpirationTimePeriod = 900; // 60 * 15 private const int MinimalApprovalThreshold = 6667; private const int MaximalAbstentionThreshold = 1000; private const int MaximalRejectionThreshold = 1000; diff --git a/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs b/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs index b82fd27996..edbd094793 100644 --- a/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs +++ b/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs @@ -286,6 +286,13 @@ private int GetCurrentContractProposalExpirationTimePeriod() : State.ContractProposalExpirationTimePeriod.Value; } + private int GetCodeCheckProposalExpirationTimePeriod() + { + return State.CodeCheckProposalExpirationTimePeriod.Value == 0 + ? DefaultCodeCheckProposalExpirationTimePeriod + : State.CodeCheckProposalExpirationTimePeriod.Value; + } + private void AssertCurrentMiner() { RequireConsensusContractStateSet(); @@ -310,7 +317,7 @@ private void SendUserContractProposal(Hash proposingInputHash, string releaseMet { Proposer = Context.Self, Status = ContractProposingInputStatus.CodeCheckProposed, - ExpiredTime = Context.CurrentBlockTime.AddSeconds(CodeCheckProposalExpirationTimePeriod), + ExpiredTime = Context.CurrentBlockTime.AddSeconds(GetCodeCheckProposalExpirationTimePeriod()), Author = Context.Sender }; State.ContractProposingInputMap[proposingInputHash] = proposedInfo; diff --git a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs index a14a4e9496..50e0c8c092 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs @@ -14,6 +14,7 @@ public static class TokenContractConstants public const string LockCallbackExternalInfoKey = "aelf_lock_callback"; public const string UnlockCallbackExternalInfoKey = "aelf_unlock_callback"; public const string LogEventExternalInfoKey = "aelf_log_event"; + public const string TokenAliasExternalInfoKey = "aelf_token_alias"; public const int DELEGATEE_MAX_COUNT = 24; public const char NFTSymbolSeparator = '-'; public const int NFTSymbolMaxLength = 30; @@ -22,4 +23,8 @@ public static class TokenContractConstants public const string SeedCollectionSymbol = "SEED-0"; public const string SeedOwnedSymbolExternalInfoKey = "__seed_owned_symbol"; public const string SeedExpireTimeExternalInfoKey = "__seed_exp_time"; + public const string NftCreateChainIdExternalInfoKey = "__nft_create_chain_id"; + public const int DefaultMaxBatchApproveCount = 100; + public const char AllSymbolIdentifier = '*'; + } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContractState.cs b/contract/AElf.Contracts.MultiToken/TokenContractState.cs index 92da423908..6627c3625b 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContractState.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContractState.cs @@ -8,7 +8,13 @@ public partial class TokenContractState : ContractState public StringState NativeTokenSymbol { get; set; } public StringState ChainPrimaryTokenSymbol { get; set; } + + /// + /// WARNING: Use GetTokenInfo & SetTokenInfo to operate TokenInfos + /// due to token symbol alias feature. + /// public MappedState TokenInfos { get; set; } + public MappedState InsensitiveTokenExisting { get; set; } public MappedState SymbolSeedMap { get; set; } public MappedState Balances { get; set; } public MappedState Allowances { get; set; } @@ -63,4 +69,11 @@ public partial class TokenContractState : ContractState public SingletonState
ElectionContractAddress { get; set; } public SingletonState
VoteContractAddress { get; set; } + + public SingletonState TokenIssuerAndOwnerModificationDisabled { get; set; } + + public SingletonState MaxBatchApproveCount { get; set; } + + // Alias -> Actual Symbol + public MappedState SymbolAliasMap { get; set; } } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs b/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs index 9ef198b39c..e24c2d8acc 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs @@ -123,9 +123,12 @@ private bool CheckOrganizationExist(AuthorityInfo authorityInfo) private void AssertValidFeeToken(string symbol, long amount) { AssertValidSymbolAndAmount(symbol, amount); - if (State.TokenInfos[symbol] == null) + var tokenInfo = GetTokenInfo(symbol); + if (tokenInfo == null) + { throw new AssertionException("Token is not found"); - Assert(State.TokenInfos[symbol].IsBurnable, $"Token {symbol} cannot set as method fee."); + } + Assert(tokenInfo.IsBurnable, $"Token {symbol} cannot set as method fee."); } #endregion diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs b/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs index 12369beed2..79f26a4f3b 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs @@ -44,8 +44,6 @@ public override ResourceInfo GetResourceInfo(Transaction txn) { WritePaths = { - GetPath(nameof(TokenContractState.Allowances), args.From.ToString(), txn.From.ToString(), - args.Symbol), GetPath(nameof(TokenContractState.Balances), args.From.ToString(), args.Symbol), GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol), GetPath(nameof(TokenContractState.LockWhiteLists), args.Symbol, txn.From.ToString()) @@ -57,7 +55,7 @@ public override ResourceInfo GetResourceInfo(Transaction txn) GetPath(nameof(TokenContractState.TransactionFeeFreeAllowancesSymbolList)) } }; - + AddPathForAllowance(resourceInfo, args.From.ToString(), txn.From.ToString(), args.Symbol); AddPathForTransactionFee(resourceInfo, txn.From.ToString(), txn.MethodName); AddPathForDelegatees(resourceInfo, txn.From, txn.To, txn.MethodName); AddPathForTransactionFeeFreeAllowance(resourceInfo, txn.From); @@ -70,6 +68,18 @@ public override ResourceInfo GetResourceInfo(Transaction txn) } } + private void AddPathForAllowance(ResourceInfo resourceInfo, string from, string spender, string symbol) + { + resourceInfo.WritePaths.Add(GetPath(nameof(TokenContractState.Allowances), from, spender, symbol)); + resourceInfo.WritePaths.Add(GetPath(nameof(TokenContractState.Allowances), from, spender, + GetAllSymbolIdentifier())); + var symbolType = GetSymbolType(symbol); + if (symbolType == SymbolType.Nft || symbolType == SymbolType.NftCollection) + { + resourceInfo.WritePaths.Add(GetPath(nameof(TokenContractState.Allowances), from, spender, + GetNftCollectionAllSymbolIdentifier(symbol))); + } + } private void AddPathForTransactionFee(ResourceInfo resourceInfo, string from, string methodName) { diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs index 483f073b54..6adcf01a2a 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs @@ -32,9 +32,7 @@ public override Empty InitializeFromParentChain(InitializeFromParentChainInput i /// public override Empty Create(CreateInput input) { - // can not call create on side chain - Assert(State.SideChainCreator.Value == null, "Failed to create token if side chain creator already set."); - var inputSymbolType = GetCreateInputSymbolType(input.Symbol); + var inputSymbolType = GetSymbolType(input.Symbol); if (input.Owner == null) { input.Owner = input.Issuer; @@ -52,10 +50,13 @@ private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType. AssertValidCreateInput(input, symbolType); if (symbolType == SymbolType.Token || symbolType == SymbolType.NftCollection) { + // can not call create on side chain + Assert(State.SideChainCreator.Value == null, + "Failed to create token if side chain creator already set."); if (!IsAddressInCreateWhiteList(Context.Sender) && input.Symbol != TokenContractConstants.SeedCollectionSymbol) { - var symbolSeed = State.SymbolSeedMap[input.Symbol]; + var symbolSeed = State.SymbolSeedMap[input.Symbol.ToUpper()]; CheckSeedNFT(symbolSeed, input.Symbol); // seed nft for one-time use only long balance = State.Balances[Context.Sender][symbolSeed]; @@ -76,6 +77,14 @@ private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType. ExternalInfo = input.ExternalInfo ?? new ExternalInfo(), Owner = input.Owner }; + + if (IsAliasSettingExists(tokenInfo)) + { + Assert(symbolType == SymbolType.NftCollection, "Token alias can only be set for NFT Item."); + SetTokenAlias(tokenInfo); + } + + CheckTokenExists(tokenInfo.Symbol); RegisterTokenInfo(tokenInfo); if (string.IsNullOrEmpty(State.NativeTokenSymbol.Value)) { @@ -109,7 +118,7 @@ private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType. private void CheckSeedNFT(string symbolSeed, String symbol) { Assert(!string.IsNullOrEmpty(symbolSeed), "Seed NFT does not exist."); - var tokenInfo = State.TokenInfos[symbolSeed]; + var tokenInfo = GetTokenInfo(symbolSeed); Assert(tokenInfo != null, "Seed NFT does not exist."); Assert(State.Balances[Context.Sender][symbolSeed] > 0, "Seed NFT balance is not enough."); Assert(tokenInfo.ExternalInfo != null && tokenInfo.ExternalInfo.Value.TryGetValue( @@ -130,7 +139,7 @@ private void CheckSeedNFT(string symbolSeed, String symbol) public override Empty SetPrimaryTokenSymbol(SetPrimaryTokenSymbolInput input) { Assert(State.ChainPrimaryTokenSymbol.Value == null, "Failed to set primary token symbol."); - Assert(!string.IsNullOrWhiteSpace(input.Symbol) && State.TokenInfos[input.Symbol] != null, "Invalid input symbol."); + Assert(!string.IsNullOrWhiteSpace(input.Symbol) && GetTokenInfo(input.Symbol) != null, "Invalid input symbol."); State.ChainPrimaryTokenSymbol.Value = input.Symbol; Context.Fire(new ChainPrimaryTokenSymbolSet { TokenSymbol = input.Symbol }); @@ -155,7 +164,7 @@ public override Empty Issue(IssueInput input) tokenInfo.Supply = tokenInfo.Supply.Add(input.Amount); Assert(tokenInfo.Issued <= tokenInfo.TotalSupply, "Total supply exceeded"); - State.TokenInfos[input.Symbol] = tokenInfo; + SetTokenInfo(tokenInfo); ModifyBalance(input.To, input.Symbol, input.Amount); Context.Fire(new Issued @@ -170,14 +179,14 @@ public override Empty Issue(IssueInput input) public override Empty Transfer(TransferInput input) { - AssertValidToken(input.Symbol, input.Amount); - DoTransfer(Context.Sender, input.To, input.Symbol, input.Amount, input.Memo); + var tokenInfo = AssertValidToken(input.Symbol, input.Amount); + DoTransfer(Context.Sender, input.To, tokenInfo.Symbol, input.Amount, input.Memo); DealWithExternalInfoDuringTransfer(new TransferFromInput { From = Context.Sender, To = input.To, Amount = input.Amount, - Symbol = input.Symbol, + Symbol = tokenInfo.Symbol, Memo = input.Memo }); return new Empty(); @@ -244,38 +253,63 @@ public override Empty Unlock(UnlockInput input) public override Empty TransferFrom(TransferFromInput input) { - AssertValidToken(input.Symbol, input.Amount); - DoTransferFrom(input.From, input.To, Context.Sender, input.Symbol, input.Amount, input.Memo); + var tokenInfo = AssertValidToken(input.Symbol, input.Amount); + DoTransferFrom(input.From, input.To, Context.Sender, tokenInfo.Symbol, input.Amount, input.Memo); return new Empty(); } public override Empty Approve(ApproveInput input) { AssertValidInputAddress(input.Spender); - AssertValidToken(input.Symbol, input.Amount); - State.Allowances[Context.Sender][input.Spender][input.Symbol] = input.Amount; + var actualSymbol = GetActualTokenSymbol(input.Symbol); + AssertValidApproveTokenAndAmount(actualSymbol, input.Amount); + Approve(input.Spender, actualSymbol, input.Amount); + return new Empty(); + } + + private void Approve(Address spender, string symbol, long amount) + { + var actualSymbol = GetActualTokenSymbol(symbol); + State.Allowances[Context.Sender][spender][actualSymbol] = amount; Context.Fire(new Approved { Owner = Context.Sender, - Spender = input.Spender, - Symbol = input.Symbol, - Amount = input.Amount + Spender = spender, + Symbol = actualSymbol, + Amount = amount }); + } + + public override Empty BatchApprove(BatchApproveInput input) + { + Assert(input != null && input.Value != null && input.Value.Count > 0, "Invalid input ."); + Assert(input.Value.Count <= GetMaxBatchApproveCount(), "Exceeds the max batch approve count."); + foreach (var approve in input.Value) + { + AssertValidInputAddress(approve.Spender); + var actualSymbol = GetActualTokenSymbol(approve.Symbol); + AssertValidApproveTokenAndAmount(actualSymbol, approve.Amount); + } + var approveInputList = input.Value.GroupBy(approve => approve.Symbol + approve.Spender, approve => approve) + .Select(approve => approve.Last()).ToList(); + foreach (var approve in approveInputList) + Approve(approve.Spender, approve.Symbol, approve.Amount); return new Empty(); } public override Empty UnApprove(UnApproveInput input) { AssertValidInputAddress(input.Spender); - AssertValidToken(input.Symbol, input.Amount); - var oldAllowance = State.Allowances[Context.Sender][input.Spender][input.Symbol]; + var symbol = GetActualTokenSymbol(input.Symbol); + AssertValidApproveTokenAndAmount(symbol, input.Amount); + var oldAllowance = State.Allowances[Context.Sender][input.Spender][symbol]; var amountOrAll = Math.Min(input.Amount, oldAllowance); - State.Allowances[Context.Sender][input.Spender][input.Symbol] = oldAllowance.Sub(amountOrAll); + State.Allowances[Context.Sender][input.Spender][symbol] = oldAllowance.Sub(amountOrAll); Context.Fire(new UnApproved { Owner = Context.Sender, Spender = input.Spender, - Symbol = input.Symbol, + Symbol = symbol, Amount = amountOrAll }); return new Empty(); @@ -404,7 +438,7 @@ public override Empty TakeResourceTokenBack(TakeResourceTokenBackInput input) public override Empty ValidateTokenInfoExists(ValidateTokenInfoExistsInput input) { Assert(!string.IsNullOrWhiteSpace(input.Symbol), "Invalid input symbol."); - var tokenInfo = State.TokenInfos[input.Symbol]; + var tokenInfo = GetTokenInfo(input.Symbol); if (tokenInfo == null) throw new AssertionException("Token validation failed."); var validationResult = tokenInfo.TokenName == input.TokenName && @@ -467,20 +501,34 @@ public override Empty CrossChainCreateToken(CrossChainCreateTokenInput input) ExternalInfo = new ExternalInfo { Value = { validateTokenInfoExistsInput.ExternalInfo } }, Owner = validateTokenInfoExistsInput.Owner ?? validateTokenInfoExistsInput.Issuer }; - RegisterTokenInfo(tokenInfo); - Context.Fire(new TokenCreated + var isSymbolAliasSet = SyncSymbolAliasFromTokenInfo(tokenInfo); + if (State.TokenInfos[tokenInfo.Symbol] == null) { - Symbol = validateTokenInfoExistsInput.Symbol, - TokenName = validateTokenInfoExistsInput.TokenName, - TotalSupply = validateTokenInfoExistsInput.TotalSupply, - Decimals = validateTokenInfoExistsInput.Decimals, - Issuer = validateTokenInfoExistsInput.Issuer, - IsBurnable = validateTokenInfoExistsInput.IsBurnable, - IssueChainId = validateTokenInfoExistsInput.IssueChainId, - ExternalInfo = new ExternalInfo { Value = { validateTokenInfoExistsInput.ExternalInfo } }, - Owner = tokenInfo.Owner - }); + RegisterTokenInfo(tokenInfo); + Context.Fire(new TokenCreated + { + Symbol = validateTokenInfoExistsInput.Symbol, + TokenName = validateTokenInfoExistsInput.TokenName, + TotalSupply = validateTokenInfoExistsInput.TotalSupply, + Decimals = validateTokenInfoExistsInput.Decimals, + Issuer = validateTokenInfoExistsInput.Issuer, + IsBurnable = validateTokenInfoExistsInput.IsBurnable, + IssueChainId = validateTokenInfoExistsInput.IssueChainId, + ExternalInfo = new ExternalInfo { Value = { validateTokenInfoExistsInput.ExternalInfo } }, + Owner = tokenInfo.Owner, + }); + } + else + { + if (isSymbolAliasSet && + validateTokenInfoExistsInput.ExternalInfo.TryGetValue(TokenContractConstants.TokenAliasExternalInfoKey, + out var tokenAliasSetting)) + { + State.TokenInfos[tokenInfo.Symbol].ExternalInfo.Value + .Add(TokenContractConstants.TokenAliasExternalInfoKey, tokenAliasSetting); + } + } return new Empty(); } @@ -511,21 +559,21 @@ public override Empty RegisterCrossChainTokenContractAddress(RegisterCrossChainT /// public override Empty CrossChainTransfer(CrossChainTransferInput input) { - AssertValidToken(input.Symbol, input.Amount); + var tokenInfo = AssertValidToken(input.Symbol, input.Amount); AssertValidMemo(input.Memo); - var issueChainId = GetIssueChainId(input.Symbol); + var issueChainId = GetIssueChainId(tokenInfo.Symbol); Assert(issueChainId == input.IssueChainId, "Incorrect issue chain id."); var burnInput = new BurnInput { Amount = input.Amount, - Symbol = input.Symbol + Symbol = tokenInfo.Symbol }; Burn(burnInput); Context.Fire(new CrossChainTransferred { From = Context.Sender, To = input.To, - Symbol = input.Symbol, + Symbol = tokenInfo.Symbol, Amount = input.Amount, IssueChainId = input.IssueChainId, Memo = input.Memo, @@ -556,28 +604,28 @@ public override Empty CrossChainReceiveToken(CrossChainReceiveTokenInput input) var transferSender = transferTransaction.From; var tokenInfo = AssertValidToken(symbol, amount); - var issueChainId = GetIssueChainId(symbol); + var issueChainId = GetIssueChainId(tokenInfo.Symbol); Assert(issueChainId == crossChainTransferInput.IssueChainId, "Incorrect issue chain id."); Assert(targetChainId == Context.ChainId, "Unable to claim cross chain token."); var registeredTokenContractAddress = State.CrossChainTransferWhiteList[input.FromChainId]; AssertCrossChainTransaction(transferTransaction, registeredTokenContractAddress, nameof(CrossChainTransfer)); Context.LogDebug(() => - $"symbol == {symbol}, amount == {amount}, receivingAddress == {receivingAddress}, targetChainId == {targetChainId}"); + $"symbol == {tokenInfo.Symbol}, amount == {amount}, receivingAddress == {receivingAddress}, targetChainId == {targetChainId}"); CrossChainVerify(transferTransactionId, input.ParentChainHeight, input.FromChainId, input.MerklePath); State.VerifiedCrossChainTransferTransaction[transferTransactionId] = true; tokenInfo.Supply = tokenInfo.Supply.Add(amount); Assert(tokenInfo.Supply <= tokenInfo.TotalSupply, "Total supply exceeded"); - State.TokenInfos[symbol] = tokenInfo; - ModifyBalance(receivingAddress, symbol, amount); + SetTokenInfo(tokenInfo); + ModifyBalance(receivingAddress, tokenInfo.Symbol, amount); Context.Fire(new CrossChainReceived { From = transferSender, To = receivingAddress, - Symbol = symbol, + Symbol = tokenInfo.Symbol, Amount = amount, Memo = crossChainTransferInput.Memo, FromChainId = input.FromChainId, @@ -589,4 +637,187 @@ public override Empty CrossChainReceiveToken(CrossChainReceiveTokenInput input) } #endregion + + public override Empty ModifyTokenIssuerAndOwner(ModifyTokenIssuerAndOwnerInput input) + { + Assert(!State.TokenIssuerAndOwnerModificationDisabled.Value, "Set token issuer and owner disabled."); + Assert(!string.IsNullOrWhiteSpace(input.Symbol), "Invalid input symbol."); + Assert(input.Issuer != null && !input.Issuer.Value.IsNullOrEmpty(), "Invalid input issuer."); + Assert(input.Owner != null && !input.Owner.Value.IsNullOrEmpty(), "Invalid input owner."); + + var tokenInfo = GetTokenInfo(input.Symbol); + + Assert(tokenInfo != null, "Token is not found."); + Assert(tokenInfo.Issuer == Context.Sender, "Only token issuer can set token issuer and owner."); + Assert(tokenInfo.Owner == null, "Can only set token which does not have owner."); + + tokenInfo.Issuer = input.Issuer; + tokenInfo.Owner = input.Owner; + + return new Empty(); + } + + public override Empty SetTokenIssuerAndOwnerModificationEnabled(SetTokenIssuerAndOwnerModificationEnabledInput input) + { + AssertSenderAddressWith(GetDefaultParliamentController().OwnerAddress); + Assert(input != null, "Invalid input."); + + State.TokenIssuerAndOwnerModificationDisabled.Value = !input.Enabled; + + return new Empty(); + } + + public override BoolValue GetTokenIssuerAndOwnerModificationEnabled(Empty input) + { + return new BoolValue + { + Value = !State.TokenIssuerAndOwnerModificationDisabled.Value + }; + } + + public override Empty SetMaxBatchApproveCount(Int32Value input) + { + Assert(input.Value > 0, "Invalid input."); + AssertSenderAddressWith(GetDefaultParliamentController().OwnerAddress); + State.MaxBatchApproveCount.Value = input.Value; + return new Empty(); + } + + public override Int32Value GetMaxBatchApproveCount(Empty input) + { + return new Int32Value + { + Value = GetMaxBatchApproveCount() + }; + } + + private int GetMaxBatchApproveCount() + { + return State.MaxBatchApproveCount.Value == 0 + ? TokenContractConstants.DefaultMaxBatchApproveCount + : State.MaxBatchApproveCount.Value; + } + + /// + /// For example: + /// Symbol: SGR-1, Alias: SGR + /// Symbol: ABC-233, Alias: ABC + /// + /// + /// + public override Empty SetSymbolAlias(SetSymbolAliasInput input) + { + // Alias setting can only work for NFT Item for now. + // And the setting exists on the TokenInfo of the NFT Collection. + + // Can only happen on Main Chain. + Assert(Context.ChainId == ChainHelper.ConvertBase58ToChainId("AELF"), + "Symbol alias setting only works on MainChain."); + + var collectionSymbol = GetNftCollectionSymbol(input.Symbol, true); + + // For now, token alias can only be set once. + Assert(State.SymbolAliasMap[input.Alias] == null, $"Token alias {input.Alias} already exists."); + + CheckTokenAlias(input.Alias, collectionSymbol); + + var collectionTokenInfo = GetTokenInfo(collectionSymbol); + if (collectionTokenInfo == null) + { + throw new AssertionException($"NFT Collection {collectionSymbol} not found."); + } + + Assert(collectionTokenInfo.Owner == Context.Sender || collectionTokenInfo.Issuer == Context.Sender, + "No permission."); + + collectionTokenInfo.ExternalInfo.Value[TokenContractConstants.TokenAliasExternalInfoKey] + = $"{{\"{input.Symbol}\":\"{input.Alias}\"}}"; + + SetTokenInfo(collectionTokenInfo); + + State.SymbolAliasMap[input.Alias] = input.Symbol; + + Context.LogDebug(() => $"Token alias added: {input.Symbol} -> {input.Alias}"); + + Context.Fire(new SymbolAliasAdded + { + Symbol = input.Symbol, + Alias = input.Alias + }); + + return new Empty(); + } + + private bool SyncSymbolAliasFromTokenInfo(TokenInfo newTokenInfo) + { + var maybePreviousTokenInfo = State.TokenInfos[newTokenInfo.Symbol]?.Clone(); + + if (maybePreviousTokenInfo != null && IsAliasSettingExists(maybePreviousTokenInfo)) + { + return false; + } + + if (IsAliasSettingExists(newTokenInfo)) + { + SetTokenAlias(newTokenInfo); + return true; + } + + return false; + } + + private bool IsAliasSettingExists(TokenInfo tokenInfo) + { + return tokenInfo.ExternalInfo != null && + tokenInfo.ExternalInfo.Value.Count > 0 && + tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.TokenAliasExternalInfoKey); + } + + /// + /// Extract alias setting from ExternalInfo. + /// + /// + /// (Symbol, Alias) + private KeyValuePair ExtractAliasSetting(TokenInfo tokenInfo) + { + if (!tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.TokenAliasExternalInfoKey)) + { + return new KeyValuePair(string.Empty, string.Empty); + } + + var tokenAliasSetting = tokenInfo.ExternalInfo.Value[TokenContractConstants.TokenAliasExternalInfoKey]; + tokenAliasSetting = tokenAliasSetting.Trim('{', '}'); + var parts = tokenAliasSetting.Split(':'); + var key = parts[0].Trim().Trim('\"'); + var value = parts[1].Trim().Trim('\"'); + return new KeyValuePair(key, value); + } + + private void SetTokenAlias(TokenInfo tokenInfo) + { + var (symbol, alias) = ExtractAliasSetting(tokenInfo); + State.SymbolAliasMap[alias] = symbol; + + CheckTokenAlias(alias, tokenInfo.Symbol); + + Context.Fire(new SymbolAliasAdded + { + Symbol = symbol, + Alias = alias + }); + } + + private void CheckTokenAlias(string alias, string collectionSymbol) + { + if (collectionSymbol == null) + { + throw new AssertionException("Token alias can only be set for NFT Item."); + } + + // Current Rule: Alias must be the seed name. + var parts = collectionSymbol.Split(TokenContractConstants.NFTSymbolSeparator); + Assert(parts.Length == 2, $"Incorrect collection symbol: {collectionSymbol}."); + Assert(parts.Last() == TokenContractConstants.CollectionSymbolSuffix, "Incorrect collection symbol suffix."); + Assert(alias == parts.First(), $"Alias for an item of {collectionSymbol} cannot be {alias}."); + } } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs index 25f853ec11..12743edd91 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs @@ -140,7 +140,7 @@ private UserContractMethodFees GetActualFee(Address contractAddress, string meth //configuration_key:UserContractMethod_contractAddress_methodName var spec = State.ConfigurationContract.GetConfiguration.Call(new StringValue { - Value = $"{TokenContractConstants.UserContractMethodFeeKey}_{contractAddress}_{methodName}" + Value = $"{TokenContractConstants.UserContractMethodFeeKey}_{contractAddress.ToBase58()}_{methodName}" }); var fee = new UserContractMethodFees(); if (!spec.Value.IsNullOrEmpty()) @@ -320,14 +320,14 @@ private void SetOrRefreshTransactionFeeFreeAllowances(Address address) private Dictionary GetBaseFeeDictionary(MethodFees methodFees) { - return methodFees.Fees + return methodFees.Fees.Where(f => !string.IsNullOrEmpty(f.Symbol)) .GroupBy(f => f.Symbol, f => f.BasicFee) .ToDictionary(g => g.Key, g => g.Sum()); } private Dictionary GetUserContractFeeDictionary(UserContractMethodFees fees) { - return fees.Fees + return fees.Fees.Where(f => !string.IsNullOrEmpty(f.Symbol)) .GroupBy(f => f.Symbol, f => f.BasicFee) .ToDictionary(g => g.Key, g => g.Sum()); } @@ -620,7 +620,6 @@ public override Empty SetSymbolsToPayTxSizeFee(SymbolListToPayTxSizeFee input) var isPrimaryTokenExist = false; var symbolList = new List(); var primaryTokenSymbol = GetPrimaryTokenSymbol(new Empty()); - var primaryTokenInfo = State.TokenInfos[primaryTokenSymbol.Value]; Assert(!string.IsNullOrEmpty(primaryTokenSymbol.Value), "primary token does not exist"); foreach (var tokenWeightInfo in input.SymbolsToPayTxSizeFee) { @@ -1153,7 +1152,7 @@ private void TransferTransactionFeesToFeeReceiver(string symbol, long totalAmoun if (totalAmount <= 0) return; - var tokenInfo = State.TokenInfos[symbol]; + var tokenInfo = GetTokenInfo(symbol); if (!tokenInfo.IsBurnable) { return; @@ -1268,7 +1267,7 @@ public override Empty ConfigTransactionFeeFreeAllowances(ConfigTransactionFeeFre private void ValidateToken(string symbol) { Assert(!string.IsNullOrWhiteSpace(symbol), "Invalid input symbol"); - Assert(State.TokenInfos[symbol] != null, $"Symbol {symbol} not exist"); + Assert(GetTokenInfo(symbol) != null, $"Symbol {symbol} not exist"); } public override Empty RemoveTransactionFeeFreeAllowancesConfig(RemoveTransactionFeeFreeAllowancesConfigInput input) @@ -1411,7 +1410,7 @@ private bool IsDelegationEnough(string txSymbol, string baseSymbol, long cost, private void AssertSymbolToPayTxFeeIsValid(string tokenSymbol, out long totalSupply) { - var tokenInfo = State.TokenInfos[tokenSymbol]; + var tokenInfo = GetTokenInfo(tokenSymbol); if (tokenInfo == null) { throw new AssertionException($"Token is not found. {tokenSymbol}"); diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs index ba2d3e5309..3a78c60cbe 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using AElf.Contracts.Parliament; using AElf.CSharp.Core; using AElf.Sdk.CSharp; @@ -14,33 +15,72 @@ namespace AElf.Contracts.MultiToken; public partial class TokenContract { - private static bool IsValidSymbolChar(char character) + private static bool IsValidSymbol(string symbol) { - return (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || - character == TokenContractConstants.NFTSymbolSeparator; + return Regex.IsMatch(symbol, "^[a-zA-Z0-9]+(-[0-9]+)?$"); } - private bool IsValidItemIdChar(char character) + private bool IsValidItemId(string symbolItemId) { - return character >= '0' && character <= '9'; + return Regex.IsMatch(symbolItemId, "^[0-9]+$"); } - private bool IsValidCreateSymbolChar(char character) + private bool IsValidCreateSymbol(string symbol) { - return character >= 'A' && character <= 'Z'; + return Regex.IsMatch(symbol, "^[a-zA-Z0-9]+$"); } private TokenInfo AssertValidToken(string symbol, long amount) { AssertValidSymbolAndAmount(symbol, amount); - var tokenInfo = State.TokenInfos[symbol]; + var tokenInfo = GetTokenInfo(symbol); Assert(tokenInfo != null && !string.IsNullOrEmpty(tokenInfo.Symbol), $"Token is not found. {symbol}"); return tokenInfo; } + private void AssertValidApproveTokenAndAmount(string symbol, long amount) + { + Assert(amount > 0, "Invalid amount."); + AssertApproveToken(symbol); + } + + private void ValidTokenExists(string symbol) + { + var tokenInfo = State.TokenInfos[symbol]; + Assert(tokenInfo != null && !string.IsNullOrEmpty(tokenInfo.Symbol), + $"Token is not found. {symbol}"); + } + + private void AssertApproveToken(string symbol) + { + Assert(!string.IsNullOrEmpty(symbol), "Symbol can not be null."); + var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator); + var symbolPrefix = words[0]; + var allSymbolIdentifier = GetAllSymbolIdentifier(); + Assert(symbolPrefix.Length > 0 && (IsValidCreateSymbol(symbolPrefix) || symbolPrefix.Equals(allSymbolIdentifier)), "Invalid symbol."); + if (words.Length == 1) + { + if (!symbolPrefix.Equals(allSymbolIdentifier)) + { + ValidTokenExists(symbolPrefix); + } + return; + } + Assert(words.Length == 2, "Invalid symbol length."); + var itemId = words[1]; + Assert(itemId.Length > 0 && (IsValidItemId(itemId) || itemId.Equals(allSymbolIdentifier)), "Invalid NFT Symbol."); + var nftSymbol = itemId.Equals(allSymbolIdentifier) ? GetCollectionSymbol(symbolPrefix) : symbol; + ValidTokenExists(nftSymbol); + } + + private string GetCollectionSymbol(string symbolPrefix) + { + return $"{symbolPrefix}-{TokenContractConstants.CollectionSymbolSuffix}"; + } + private void AssertValidSymbolAndAmount(string symbol, long amount) { - Assert(!string.IsNullOrEmpty(symbol) && symbol.All(s => IsValidSymbolChar(s)), + Assert(!string.IsNullOrEmpty(symbol) && IsValidSymbol(symbol), "Invalid symbol."); Assert(amount > 0, "Invalid amount."); } @@ -122,13 +162,12 @@ private List GetSymbolListSortedByExpirationTime(TransactionFeeFreeAllow fromAddress][t]).Seconds).ToList(); } - private long GetBalance(Address address, string symbol) { AssertValidInputAddress(address); - Assert(!string.IsNullOrWhiteSpace(symbol), "Invalid symbol."); - - return State.Balances[address][symbol]; + var actualSymbol = GetActualTokenSymbol(symbol); + Assert(!string.IsNullOrWhiteSpace(actualSymbol), "Invalid symbol."); + return State.Balances[address][actualSymbol]; } // private MethodFeeFreeAllowance GetFreeFeeAllowance(MethodFeeFreeAllowances freeAllowances, string symbol) @@ -183,14 +222,14 @@ private void AssertCrossChainTransaction(Transaction originalTransaction, Addres private void RegisterTokenInfo(TokenInfo tokenInfo) { - CheckTokenExists(tokenInfo.Symbol); - Assert(!string.IsNullOrEmpty(tokenInfo.Symbol) && tokenInfo.Symbol.All(s => IsValidSymbolChar(s)), + Assert(!string.IsNullOrEmpty(tokenInfo.Symbol) && IsValidSymbol(tokenInfo.Symbol), "Invalid symbol."); Assert(!string.IsNullOrEmpty(tokenInfo.TokenName), "Token name can neither be null nor empty."); Assert(tokenInfo.TotalSupply > 0, "Invalid total supply."); Assert(tokenInfo.Issuer != null, "Invalid issuer address."); Assert(tokenInfo.Owner != null, "Invalid owner address."); State.TokenInfos[tokenInfo.Symbol] = tokenInfo; + State.InsensitiveTokenExisting[tokenInfo.Symbol.ToUpper()] = true; } private void CrossChainVerify(Hash transactionId, long parentChainHeight, int chainId, MerklePath merklePath) @@ -225,7 +264,7 @@ private AuthorityInfo GetCrossChainTokenContractRegistrationController() private int GetIssueChainId(string symbol) { - var tokenInfo = State.TokenInfos[symbol]; + var tokenInfo = GetTokenInfo(symbol); return tokenInfo.IssueChainId; } @@ -255,8 +294,11 @@ private void CheckTokenAndCollectionExists(string symbol) private void CheckTokenExists(string symbol) { var empty = new TokenInfo(); - var existing = State.TokenInfos[symbol]; + // check old token + var existing = GetTokenInfo(symbol); Assert(existing == null || existing.Equals(empty), "Token already exists."); + // check new token + Assert(!State.InsensitiveTokenExisting[symbol.ToUpper()], "Token already exists."); } private void CheckSymbolLength(string symbol, SymbolType symbolType) @@ -278,7 +320,7 @@ private void CheckCrossChainTokenContractRegistrationControllerAuthority() private void DealWithExternalInfoDuringLocking(TransferFromInput input) { - var tokenInfo = State.TokenInfos[input.Symbol]; + var tokenInfo = GetTokenInfo(input.Symbol); if (tokenInfo.ExternalInfo == null) return; if (tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.LockCallbackExternalInfoKey)) { @@ -293,7 +335,7 @@ private void DealWithExternalInfoDuringLocking(TransferFromInput input) private void DealWithExternalInfoDuringTransfer(TransferFromInput input) { - var tokenInfo = State.TokenInfos[input.Symbol]; + var tokenInfo = GetTokenInfo(input.Symbol); if (tokenInfo.ExternalInfo == null) return; if (tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.TransferCallbackExternalInfoKey)) { @@ -308,7 +350,7 @@ private void DealWithExternalInfoDuringTransfer(TransferFromInput input) private void DealWithExternalInfoDuringUnlock(TransferFromInput input) { - var tokenInfo = State.TokenInfos[input.Symbol]; + var tokenInfo = GetTokenInfo(input.Symbol); if (tokenInfo.ExternalInfo == null) return; if (tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.UnlockCallbackExternalInfoKey)) { @@ -358,4 +400,23 @@ private Address GetVoteContractAddress() return State.VoteContractAddress.Value; } + + private TokenInfo GetTokenInfo(string symbolOrAlias) + { + var tokenInfo = State.TokenInfos[symbolOrAlias]; + if (tokenInfo != null) return tokenInfo; + var actualTokenSymbol = State.SymbolAliasMap[symbolOrAlias]; + if (!string.IsNullOrEmpty(actualTokenSymbol)) + { + tokenInfo = State.TokenInfos[actualTokenSymbol]; + } + + return tokenInfo; + } + + private void SetTokenInfo(TokenInfo tokenInfo) + { + var symbol = tokenInfo.Symbol; + State.TokenInfos[symbol] = tokenInfo; + } } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs b/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs index bddfb444bf..ae01062131 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs @@ -4,17 +4,13 @@ namespace AElf.Contracts.MultiToken; public partial class TokenContract { - private SymbolType GetCreateInputSymbolType(string symbol) + private SymbolType GetSymbolType(string symbol) { var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator); - Assert(words[0].Length > 0 && words[0].All(IsValidCreateSymbolChar), "Invalid Symbol input"); + Assert(words[0].Length > 0 && IsValidCreateSymbol(words[0]), "Invalid Symbol input"); if (words.Length == 1) return SymbolType.Token; - Assert(words.Length == 2 && words[1].Length > 0 && words[1].All(IsValidItemIdChar), "Invalid NFT Symbol input"); + Assert(words.Length == 2 && words[1].Length > 0 && IsValidItemId(words[1]), "Invalid NFT Symbol input"); return words[1] == TokenContractConstants.CollectionSymbolSuffix ? SymbolType.NftCollection : SymbolType.Nft; } - private void AssertNFTCreateInput(CreateInput input) - { - Assert(input.Decimals == 0, "NFT's decimals must be 0"); - } } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs index 1926727e68..93d54ec7b3 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs @@ -9,35 +9,46 @@ public partial class TokenContract { private Empty CreateNFTCollection(CreateInput input) { - AssertNFTCreateInput(input); return CreateToken(input, SymbolType.NftCollection); } private Empty CreateNFTInfo(CreateInput input) { - AssertNFTCreateInput(input); var nftCollectionInfo = AssertNftCollectionExist(input.Symbol); input.IssueChainId = input.IssueChainId == 0 ? nftCollectionInfo.IssueChainId : input.IssueChainId; - Assert(input.IssueChainId == nftCollectionInfo.IssueChainId, - "NFT create ChainId must be collection's issue chainId"); - + Assert( + input.IssueChainId == nftCollectionInfo.IssueChainId, + "NFT issue ChainId must be collection's issue chainId"); + if (nftCollectionInfo.ExternalInfo != null && nftCollectionInfo.ExternalInfo.Value.TryGetValue( + TokenContractConstants.NftCreateChainIdExternalInfoKey, + out var nftCreateChainId) && long.TryParse(nftCreateChainId, out var nftCreateChainIdLong)) + { + Assert(nftCreateChainIdLong == Context.ChainId, + "NFT create ChainId must be collection's NFT create chainId"); + } + else + { + Assert(State.SideChainCreator.Value == null, + "Failed to create token if side chain creator already set."); + } + var owner = nftCollectionInfo.Owner ?? nftCollectionInfo.Issuer; Assert(Context.Sender == owner && owner == input.Owner, "NFT owner must be collection's owner"); - if (nftCollectionInfo.Symbol == TokenContractConstants.SeedCollectionSymbol) { + Assert(input.Decimals == 0 && input.TotalSupply == 1, "SEED must be unique."); Assert(input.ExternalInfo.Value.TryGetValue(TokenContractConstants.SeedOwnedSymbolExternalInfoKey, out var ownedSymbol), "OwnedSymbol does not exist."); Assert(input.ExternalInfo.Value.TryGetValue(TokenContractConstants.SeedExpireTimeExternalInfoKey, out var expirationTime) && long.TryParse(expirationTime, out var expirationTimeLong) && Context.CurrentBlockTime.Seconds <= expirationTimeLong, "Invalid ownedSymbol."); - var ownedSymbolType = GetCreateInputSymbolType(ownedSymbol); + var ownedSymbolType = GetSymbolType(ownedSymbol); Assert(ownedSymbolType != SymbolType.Nft, "Invalid OwnedSymbol."); CheckSymbolLength(ownedSymbol, ownedSymbolType); CheckTokenAndCollectionExists(ownedSymbol); CheckSymbolSeed(ownedSymbol); - State.SymbolSeedMap[ownedSymbol] = input.Symbol; + State.SymbolSeedMap[ownedSymbol.ToUpper()] = input.Symbol; } return CreateToken(input, SymbolType.Nft); @@ -45,9 +56,9 @@ private Empty CreateNFTInfo(CreateInput input) private void CheckSymbolSeed(string ownedSymbol) { - var oldSymbolSeed = State.SymbolSeedMap[ownedSymbol]; + var oldSymbolSeed = State.SymbolSeedMap[ownedSymbol.ToUpper()]; - Assert(oldSymbolSeed == null || !State.TokenInfos[oldSymbolSeed].ExternalInfo.Value + Assert(oldSymbolSeed == null || !GetTokenInfo(oldSymbolSeed).ExternalInfo.Value .TryGetValue(TokenContractConstants.SeedExpireTimeExternalInfoKey, out var oldSymbolSeedExpireTime) || !long.TryParse(oldSymbolSeedExpireTime, out var symbolSeedExpireTime) @@ -55,14 +66,13 @@ private void CheckSymbolSeed(string ownedSymbol) "OwnedSymbol has been created"); } - private void DoTransferFrom(Address from, Address to, Address spender, string symbol, long amount, string memo) { AssertValidInputAddress(from); AssertValidInputAddress(to); // First check allowance. - var allowance = State.Allowances[from][spender][symbol]; + var allowance = GetAllowance(from, spender, symbol, amount, out var allowanceSymbol); if (allowance < amount) { if (IsInWhiteList(new IsInWhiteListInput { Symbol = symbol, Address = spender }).Value) @@ -81,25 +91,80 @@ private void DoTransferFrom(Address from, Address to, Address spender, string sy DoTransfer(from, to, symbol, amount, memo); DealWithExternalInfoDuringTransfer(new TransferFromInput() { From = from, To = to, Symbol = symbol, Amount = amount, Memo = memo }); - State.Allowances[from][spender][symbol] = allowance.Sub(amount); + State.Allowances[from][spender][allowanceSymbol] = allowance.Sub(amount); } + private long GetAllowance(Address from, Address spender, string sourceSymbol, long amount, + out string allowanceSymbol) + { + allowanceSymbol = sourceSymbol; + var allowance = State.Allowances[from][spender][sourceSymbol]; + if (allowance >= amount) return allowance; + var tokenType = GetSymbolType(sourceSymbol); + if (tokenType == SymbolType.Token) + { + allowance = GetAllSymbolAllowance(from, spender, out allowanceSymbol); + } + else + { + allowance = GetNftCollectionAllSymbolAllowance(from, spender, sourceSymbol, out allowanceSymbol); + if (allowance >= amount) return allowance; + allowance = GetAllSymbolAllowance(from, spender, out allowanceSymbol); + } + + return allowance; + } + + + private long GetAllSymbolAllowance(Address from, Address spender, out string allowanceSymbol) + { + allowanceSymbol = GetAllSymbolIdentifier(); + return State.Allowances[from][spender][allowanceSymbol]; + } + + private long GetNftCollectionAllSymbolAllowance(Address from, Address spender, string sourceSymbol, + out string allowanceSymbol) + { + allowanceSymbol = GetNftCollectionAllSymbolIdentifier(sourceSymbol); + return State.Allowances[from][spender][allowanceSymbol]; + } + + private string GetNftCollectionAllSymbolIdentifier(string sourceSymbol) + { + // "AAA-*" + return $"{sourceSymbol.Split(TokenContractConstants.NFTSymbolSeparator)[0]}-{TokenContractConstants.AllSymbolIdentifier}"; + } + + private string GetAllSymbolIdentifier() + { + // "*" + return TokenContractConstants.AllSymbolIdentifier.ToString(); + } - private string GetNftCollectionSymbol(string inputSymbol) + /// + /// ELF -> null + /// NFT-1 -> NFT-0 + /// If isAllowCollection == true: NFT-0 -> NFT-0 + /// If isAllowCollection == false: NFT-0 -> null + /// + /// + /// + /// Return null if inputSymbol is not NFT. + private string GetNftCollectionSymbol(string inputSymbol, bool isAllowCollection = false) { var symbol = inputSymbol; var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator); const int tokenSymbolLength = 1; if (words.Length == tokenSymbolLength) return null; - Assert(words.Length == 2 && words[1].All(IsValidItemIdChar), "Invalid NFT Symbol Input"); - return symbol == $"{words[0]}-0" ? null : $"{words[0]}-0"; + Assert(words.Length == 2 && IsValidItemId(words[1]), "Invalid NFT Symbol Input"); + return symbol == $"{words[0]}-0" ? (isAllowCollection ? $"{words[0]}-0" : null) : $"{words[0]}-0"; } private TokenInfo AssertNftCollectionExist(string symbol) { var collectionSymbol = GetNftCollectionSymbol(symbol); if (collectionSymbol == null) return null; - var collectionInfo = State.TokenInfos[collectionSymbol]; + var collectionInfo = GetTokenInfo(collectionSymbol); Assert(collectionInfo != null, "NFT collection not exist"); return collectionInfo; } diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs index 8366261ebf..01eb2bd7be 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using AElf.Sdk.CSharp; using AElf.Types; using Google.Protobuf.WellKnownTypes; @@ -10,12 +11,12 @@ public partial class TokenContract [View] public override TokenInfo GetTokenInfo(GetTokenInfoInput input) { - return State.TokenInfos[input.Symbol]; + return GetTokenInfo(input.Symbol); } public override TokenInfo GetNativeTokenInfo(Empty input) { - return State.TokenInfos[State.NativeTokenSymbol.Value]; + return GetTokenInfo(State.NativeTokenSymbol.Value); } public override TokenInfoList GetResourceTokenInfo(Empty input) @@ -23,13 +24,13 @@ public override TokenInfoList GetResourceTokenInfo(Empty input) var tokenInfoList = new TokenInfoList(); foreach (var symbol in Context.Variables.GetStringArray(TokenContractConstants.PayTxFeeSymbolListName) .Where(symbol => - State.TokenInfos[symbol] != null)) - tokenInfoList.Value.Add(State.TokenInfos[symbol]); + GetTokenInfo(symbol) != null)) + tokenInfoList.Value.Add(GetTokenInfo(symbol)); foreach (var symbol in Context.Variables.GetStringArray(TokenContractConstants.PayRentalSymbolListName) .Where(symbol => - State.TokenInfos[symbol] != null)) - tokenInfoList.Value.Add(State.TokenInfos[symbol]); + GetTokenInfo(symbol) != null)) + tokenInfoList.Value.Add(GetTokenInfo(symbol)); return tokenInfoList; } @@ -37,26 +38,61 @@ public override TokenInfoList GetResourceTokenInfo(Empty input) [View] public override GetBalanceOutput GetBalance(GetBalanceInput input) { + var symbol = GetActualTokenSymbol(input.Symbol); return new GetBalanceOutput { Symbol = input.Symbol, Owner = input.Owner, - Balance = GetBalance(input.Owner, input.Symbol) + Balance = GetBalance(input.Owner, symbol) }; } - + [View] public override GetAllowanceOutput GetAllowance(GetAllowanceInput input) { + var symbol = GetActualTokenSymbol(input.Symbol); return new GetAllowanceOutput + { + Symbol = symbol, + Owner = input.Owner, + Spender = input.Spender, + Allowance = State.Allowances[input.Owner][input.Spender][symbol] + }; + } + + [View] + public override GetAllowanceOutput GetAvailableAllowance(GetAllowanceInput input) + { + var result = new GetAllowanceOutput { Symbol = input.Symbol, Owner = input.Owner, Spender = input.Spender, - Allowance = State.Allowances[input.Owner][input.Spender][input.Symbol] }; + var symbol = input.Symbol; + var allowance = State.Allowances[input.Owner][input.Spender][symbol]; + if (CheckSymbolIdentifier(symbol)) + { + result.Allowance = allowance; + return result; + } + var symbolType = GetSymbolType(symbol); + allowance = Math.Max(allowance, GetAllSymbolAllowance(input.Owner,input.Spender,out _)); + if (symbolType == SymbolType.Nft || symbolType == SymbolType.NftCollection) + { + allowance = Math.Max(allowance, GetNftCollectionAllSymbolAllowance(input.Owner, input.Spender, symbol, out _)); + } + result.Allowance = allowance; + return result; } + private bool CheckSymbolIdentifier(string symbol) + { + var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator); + var allSymbolIdentifier = GetAllSymbolIdentifier(); + return words[0].Equals(allSymbolIdentifier) || (words.Length > 1 && words[1].Equals(allSymbolIdentifier)); + } + public override BoolValue IsInWhiteList(IsInWhiteListInput input) { return new BoolValue { Value = State.LockWhiteLists[input.Symbol][input.Address] }; @@ -199,7 +235,6 @@ public override BoolValue IsTokenAvailableForMethodFee(StringValue input) }; } - public override StringList GetReservedExternalInfoKeyList(Empty input) { return new StringList @@ -216,7 +251,7 @@ public override StringList GetReservedExternalInfoKeyList(Empty input) private bool IsTokenAvailableForMethodFee(string symbol) { - var tokenInfo = State.TokenInfos[symbol]; + var tokenInfo = GetTokenInfo(symbol); if (tokenInfo == null) throw new AssertionException("Token is not found."); return tokenInfo.IsBurnable; } @@ -228,4 +263,33 @@ private bool IsAddressInCreateWhiteList(Address address) address == Context.GetContractAddressByName(SmartContractConstants.EconomicContractSystemName) || address == Context.GetContractAddressByName(SmartContractConstants.CrossChainContractSystemName); } + + public override StringValue GetTokenAlias(StringValue input) + { + var collectionSymbol = GetNftCollectionSymbol(input.Value, true); + var tokenInfo = GetTokenInfo(collectionSymbol); + var (_, alias) = ExtractAliasSetting(tokenInfo); + return new StringValue + { + Value = alias + }; + } + + public override StringValue GetSymbolByAlias(StringValue input) + { + return new StringValue + { + Value = GetActualTokenSymbol(input.Value) + }; + } + + private string GetActualTokenSymbol(string aliasOrSymbol) + { + if (State.TokenInfos[aliasOrSymbol] == null) + { + return State.SymbolAliasMap[aliasOrSymbol] ?? aliasOrSymbol; + } + + return aliasOrSymbol; + } } \ No newline at end of file diff --git a/contract/AElf.Contracts.Parliament/Parliament.cs b/contract/AElf.Contracts.Parliament/Parliament.cs index 98ae606896..7e52c07d2d 100644 --- a/contract/AElf.Contracts.Parliament/Parliament.cs +++ b/contract/AElf.Contracts.Parliament/Parliament.cs @@ -241,7 +241,9 @@ public override ProposalOutput GetProposal(Hash proposalId) ToBeReleased = Validate(proposal) && IsReleaseThresholdReached(proposal, organization), ApprovalCount = proposal.Approvals.Count, RejectionCount = proposal.Rejections.Count, - AbstentionCount = proposal.Abstentions.Count + AbstentionCount = proposal.Abstentions.Count, + Title = proposal.Title, + Description = proposal.Description }; } diff --git a/contract/AElf.Contracts.Parliament/ParliamentConstants.cs b/contract/AElf.Contracts.Parliament/ParliamentConstants.cs new file mode 100644 index 0000000000..a1646221e8 --- /dev/null +++ b/contract/AElf.Contracts.Parliament/ParliamentConstants.cs @@ -0,0 +1,8 @@ +namespace AElf.Contracts.Parliament; + +public static class ParliamentConstants +{ + public const int MaxLengthForTitle = 255; + public const int MaxLengthForDescription = 10200; + public const int MaxLengthForProposalDescriptionUrl = 255; +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Parliament/Parliament_Helper.cs b/contract/AElf.Contracts.Parliament/Parliament_Helper.cs index 7e1a763d2b..b1317eceb2 100644 --- a/contract/AElf.Contracts.Parliament/Parliament_Helper.cs +++ b/contract/AElf.Contracts.Parliament/Parliament_Helper.cs @@ -224,6 +224,7 @@ private Hash GenerateProposalId(CreateProposalInput input) private Hash CreateNewProposal(CreateProposalInput input) { + CheckCreateProposalInput(input); var proposalId = GenerateProposalId(input); var proposal = new ProposalInfo { @@ -234,15 +235,33 @@ private Hash CreateNewProposal(CreateProposalInput input) OrganizationAddress = input.OrganizationAddress, ProposalId = proposalId, Proposer = Context.Sender, - ProposalDescriptionUrl = input.ProposalDescriptionUrl + ProposalDescriptionUrl = input.ProposalDescriptionUrl, + Title = input.Title, + Description = input.Description }; Assert(Validate(proposal), "Invalid proposal."); Assert(State.Proposals[proposalId] == null, "Proposal already exists."); State.Proposals[proposalId] = proposal; Context.Fire(new ProposalCreated - { ProposalId = proposalId, OrganizationAddress = input.OrganizationAddress }); + { + ProposalId = proposalId, + OrganizationAddress = input.OrganizationAddress, + Title = input.Title, + Description = input.Description + }); return proposalId; } + + private void CheckCreateProposalInput(CreateProposalInput input) + { + // Check the length of title + Assert(input.Title.Length <= ParliamentConstants.MaxLengthForTitle, "Title is too long."); + // Check the length of description + Assert(input.Description.Length <= ParliamentConstants.MaxLengthForDescription, "Description is too long."); + // Check the length of description url + Assert(input.ProposalDescriptionUrl.Length <= ParliamentConstants.MaxLengthForProposalDescriptionUrl, + "Description url is too long."); + } private Address CreateNewOrganization(CreateOrganizationInput input) { diff --git a/contract/AElf.Contracts.Profit/ProfitContract.cs b/contract/AElf.Contracts.Profit/ProfitContract.cs index c3c09b3aba..a1f43d1af4 100644 --- a/contract/AElf.Contracts.Profit/ProfitContract.cs +++ b/contract/AElf.Contracts.Profit/ProfitContract.cs @@ -769,18 +769,19 @@ public override Empty ClaimProfits(ClaimProfitsInput input) Context.LogDebug(() => $"Profitable details: {profitableDetails.Aggregate("\n", (profit1, profit2) => profit1.ToString() + "\n" + profit2)}"); + var profitableDetailCount = + Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, profitableDetails.Count); + var maxProfitReceivingPeriodCount = GetMaximumPeriodCountForProfitableDetail(profitableDetailCount); // Only can get profit from last profit period to actual last period (profit.CurrentPeriod - 1), // because current period not released yet. - for (var i = 0; - i < Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, profitableDetails.Count); - i++) + for (var i = 0; i < profitableDetailCount; i++) { var profitDetail = profitableDetails[i]; if (profitDetail.LastProfitPeriod == 0) // This detail never performed profit before. profitDetail.LastProfitPeriod = profitDetail.StartPeriod; - ProfitAllPeriods(scheme, profitDetail, beneficiary); + ProfitAllPeriods(scheme, profitDetail, beneficiary, maxProfitReceivingPeriodCount); } var profitDetailsToRemove = profitableDetails @@ -807,7 +808,41 @@ public override Empty ClaimProfits(ClaimProfitsInput input) return new Empty(); } - private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail profitDetail, Address beneficiary, + /// + /// This method calculates the maximum period count for a profitable detail + /// based on the number of profitable details and the maximum profit receiving period + /// For example: + /// If the number of profitable detail is 10 and the maximum profit receiving period is 100, + /// the maximum period count for a profitable detail will be 10. + /// If the number of profitable detail is 10 and the maximum profit receiving period is 5, + /// the maximum period count for a profitable detail will be 1. + /// + /// The number of profitable details + /// + private int GetMaximumPeriodCountForProfitableDetail(int profitableDetailCount) + { + // Get the maximum profit receiving period count + var maxPeriodCount = GetMaximumProfitReceivingPeriodCount(); + // Check if the maximum period count is greater than the profitable detail count + // and if the profitable detail count is greater than 0 + return maxPeriodCount > profitableDetailCount && profitableDetailCount > 0 + // Divide the maximum period count by the profitable detail count + ? maxPeriodCount.Div(profitableDetailCount) + // If the conditions are not met, return 1 as the maximum period count + : 1; + } + + public override Empty SetMaximumProfitReceivingPeriodCount(Int32Value input) + { + ValidateContractState(State.ParliamentContract, SmartContractConstants.ParliamentContractSystemName); + Assert(Context.Sender == State.ParliamentContract.GetDefaultOrganizationAddress.Call(new Empty()), + "No permission."); + Assert(input.Value > 0, "Invalid maximum profit receiving period count."); + State.MaximumProfitReceivingPeriodCount.Value = input.Value; + return new Empty(); + } + + private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail profitDetail, Address beneficiary, long maxProfitReceivingPeriodCount, bool isView = false, string targetSymbol = null) { var profitsMap = new Dictionary(); @@ -818,13 +853,11 @@ private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail pr foreach (var symbol in symbols) { var totalAmount = 0L; - for (var period = profitDetail.LastProfitPeriod; - period <= (profitDetail.EndPeriod == long.MaxValue - ? Math.Min(scheme.CurrentPeriod - 1, - profitDetail.LastProfitPeriod.Add(ProfitContractConstants - .MaximumProfitReceivingPeriodCountOfOneTime)) - : Math.Min(scheme.CurrentPeriod - 1, profitDetail.EndPeriod)); - period++) + var targetPeriod = Math.Min(scheme.CurrentPeriod - 1, profitDetail.EndPeriod); + var maxProfitPeriod = profitDetail.EndPeriod == long.MaxValue + ? Math.Min(scheme.CurrentPeriod - 1, profitDetail.LastProfitPeriod.Add(maxProfitReceivingPeriodCount)) + : Math.Min(targetPeriod, profitDetail.LastProfitPeriod.Add(maxProfitReceivingPeriodCount)); + for (var period = profitDetail.LastProfitPeriod; period <= maxProfitPeriod; period++) { var periodToPrint = period; var detailToPrint = profitDetail; @@ -885,6 +918,15 @@ private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail pr return profitsMap; } + + private int GetMaximumProfitReceivingPeriodCount() + { + var maxPeriodCount = State.MaximumProfitReceivingPeriodCount.Value; + maxPeriodCount = maxPeriodCount == 0 + ? ProfitContractConstants.DefaultMaximumProfitReceivingPeriodCountOfOneTime + : maxPeriodCount; + return maxPeriodCount; + } private void ValidateContractState(ContractReferenceState state, string contractSystemName) { diff --git a/contract/AElf.Contracts.Profit/ProfitContractConstants.cs b/contract/AElf.Contracts.Profit/ProfitContractConstants.cs index 59b80b971f..0aaf080aa5 100644 --- a/contract/AElf.Contracts.Profit/ProfitContractConstants.cs +++ b/contract/AElf.Contracts.Profit/ProfitContractConstants.cs @@ -6,5 +6,5 @@ public class ProfitContractConstants public const int DefaultProfitReceivingDuePeriodCount = 10; public const int MaximumProfitReceivingDuePeriodCount = 1024; public const int TokenAmountLimit = 5; - public const int MaximumProfitReceivingPeriodCountOfOneTime = 100; + public const int DefaultMaximumProfitReceivingPeriodCountOfOneTime = 100; } \ No newline at end of file diff --git a/contract/AElf.Contracts.Profit/ProfitContractState.cs b/contract/AElf.Contracts.Profit/ProfitContractState.cs index 7a525e3ff6..b0046830e0 100644 --- a/contract/AElf.Contracts.Profit/ProfitContractState.cs +++ b/contract/AElf.Contracts.Profit/ProfitContractState.cs @@ -17,4 +17,6 @@ public partial class ProfitContractState : ContractState public MappedState TransactionFees { get; set; } public SingletonState MethodFeeController { get; set; } + + public SingletonState MaximumProfitReceivingPeriodCount { get; set; } } \ No newline at end of file diff --git a/contract/AElf.Contracts.Profit/ViewMethods.cs b/contract/AElf.Contracts.Profit/ViewMethods.cs index 31d8c0d4d6..190204c3c5 100644 --- a/contract/AElf.Contracts.Profit/ViewMethods.cs +++ b/contract/AElf.Contracts.Profit/ViewMethods.cs @@ -61,49 +61,53 @@ private Hash GeneratePeriodVirtualAddressFromHash(Hash schemeId, long period) public override Int64Value GetProfitAmount(GetProfitAmountInput input) { - var profitItem = State.SchemeInfos[input.SchemeId]; - Assert(profitItem != null, "Scheme not found."); - var beneficiary = input.Beneficiary ?? Context.Sender; - var profitDetails = State.ProfitDetailsMap[input.SchemeId][beneficiary]; + var allProfitsMapResult = GetAllProfitsMap(input.SchemeId, input.Beneficiary, input.Symbol); - if (profitDetails == null) return new Int64Value { Value = 0 }; - - var profitVirtualAddress = Context.ConvertVirtualAddressToContractAddress(input.SchemeId); + return new Int64Value + { + Value = allProfitsMapResult.AllProfitsMap.TryGetValue(input.Symbol, out var value) ? value : 0 + }; + } - // ReSharper disable once PossibleNullReferenceException - var availableDetails = profitDetails.Details.Where(d => - d.LastProfitPeriod < profitItem.CurrentPeriod && (d.LastProfitPeriod == 0 - ? d.EndPeriod >= d.StartPeriod - : d.EndPeriod >= d.LastProfitPeriod) - ).ToList(); + public override GetAllProfitAmountOutput GetAllProfitAmount(GetAllProfitAmountInput input) + { + var allProfitsMapResult = GetAllProfitsMap(input.SchemeId, input.Beneficiary, input.Symbol); + return new GetAllProfitAmountOutput + { + AllProfitAmount = allProfitsMapResult.AllProfitsMap.TryGetValue(input.Symbol, out var allProfitAmount) + ? allProfitAmount + : 0, + OneTimeClaimableProfitAmount = + allProfitsMapResult.OneTimeClaimableProfitsMap.TryGetValue(input.Symbol, + out var oneTimeClaimableProfitAmount) + ? oneTimeClaimableProfitAmount + : 0 + }; + } - var amount = 0L; + public override ReceivedProfitsMap GetProfitsMap(ClaimProfitsInput input) + { + var allProfitsMapResult = GetAllProfitsMap(input.SchemeId, input.Beneficiary); - for (var i = 0; - i < Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, availableDetails.Count); - i++) + return new ReceivedProfitsMap { - var profitDetail = availableDetails[i]; - if (profitDetail.LastProfitPeriod == 0) profitDetail.LastProfitPeriod = profitDetail.StartPeriod; - - var profitsDict = ProfitAllPeriods(profitItem, profitDetail, beneficiary, true, - input.Symbol); - amount = amount.Add(profitsDict[input.Symbol]); - } + Value = { allProfitsMapResult.AllProfitsMap } + }; + } - return new Int64Value { Value = amount }; + public override GetAllProfitsMapOutput GetAllProfitsMap(GetAllProfitsMapInput input) + { + return GetAllProfitsMap(input.SchemeId, input.Beneficiary); } - public override ReceivedProfitsMap GetProfitsMap(ClaimProfitsInput input) + private GetAllProfitsMapOutput GetAllProfitsMap(Hash schemeId, Address beneficiary, string symbol = null) { - var scheme = State.SchemeInfos[input.SchemeId]; + var scheme = State.SchemeInfos[schemeId]; Assert(scheme != null, "Scheme not found."); - var beneficiary = input.Beneficiary ?? Context.Sender; - var profitDetails = State.ProfitDetailsMap[input.SchemeId][beneficiary]; - - if (profitDetails == null) return new ReceivedProfitsMap(); + beneficiary = beneficiary ?? Context.Sender; + var profitDetails = State.ProfitDetailsMap[schemeId][beneficiary]; - var profitVirtualAddress = Context.ConvertVirtualAddressToContractAddress(input.SchemeId); + if (profitDetails == null) return new GetAllProfitsMapOutput(); // ReSharper disable once PossibleNullReferenceException var availableDetails = profitDetails.Details.Where(d => @@ -111,26 +115,46 @@ public override ReceivedProfitsMap GetProfitsMap(ClaimProfitsInput input) ? d.EndPeriod >= d.StartPeriod : d.EndPeriod >= d.LastProfitPeriod) ).ToList(); - - var profitsDict = new Dictionary(); - for (var i = 0; - i < Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, availableDetails.Count); - i++) + + var profitableDetailCount = + Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, availableDetails.Count); + var maxProfitReceivingPeriodCount = GetMaximumPeriodCountForProfitableDetail(profitableDetailCount); + + var allProfitsDict = new Dictionary(); + var claimableProfitsDict = new Dictionary(); + for (var i = 0; i < availableDetails.Count; i++) { var profitDetail = availableDetails[i]; if (profitDetail.LastProfitPeriod == 0) profitDetail.LastProfitPeriod = profitDetail.StartPeriod; - - var profitsDictForEachProfitDetail = ProfitAllPeriods(scheme, profitDetail, beneficiary, true); - foreach (var kv in profitsDictForEachProfitDetail) - if (profitsDict.ContainsKey(kv.Key)) - profitsDict[kv.Key] = profitsDict[kv.Key].Add(kv.Value); - else - profitsDict[kv.Key] = kv.Value; + + var totalProfitsDictForEachProfitDetail = ProfitAllPeriods(scheme, profitDetail, beneficiary, profitDetail.EndPeriod.Sub(profitDetail.LastProfitPeriod),true, symbol); + AddProfitToDict(allProfitsDict, totalProfitsDictForEachProfitDetail); + if(i >= profitableDetailCount) continue; + var claimableProfitsDictForEachProfitDetail = ProfitAllPeriods(scheme, profitDetail, beneficiary, maxProfitReceivingPeriodCount,true, symbol); + AddProfitToDict(claimableProfitsDict, claimableProfitsDictForEachProfitDetail); } - return new ReceivedProfitsMap + return new GetAllProfitsMapOutput + { + AllProfitsMap = { allProfitsDict }, + OneTimeClaimableProfitsMap = { claimableProfitsDict } + }; + } + + private void AddProfitToDict(Dictionary profitsDict, Dictionary profitsToAdd) + { + foreach (var kv in profitsToAdd) + if (profitsDict.ContainsKey(kv.Key)) + profitsDict[kv.Key] = profitsDict[kv.Key].Add(kv.Value); + else + profitsDict[kv.Key] = kv.Value; + } + + public override Int32Value GetMaximumProfitReceivingPeriodCount(Empty input) + { + return new Int32Value { - Value = { profitsDict } + Value = GetMaximumProfitReceivingPeriodCount() }; } } \ No newline at end of file diff --git a/contract/AElf.Contracts.Referendum/Referendum.cs b/contract/AElf.Contracts.Referendum/Referendum.cs index f6112e2827..b2d63bf78f 100644 --- a/contract/AElf.Contracts.Referendum/Referendum.cs +++ b/contract/AElf.Contracts.Referendum/Referendum.cs @@ -202,7 +202,9 @@ public override ProposalOutput GetProposal(Hash proposalId) ToBeReleased = readyToRelease, ApprovalCount = proposal.ApprovalCount, RejectionCount = proposal.RejectionCount, - AbstentionCount = proposal.AbstentionCount + AbstentionCount = proposal.AbstentionCount, + Title = proposal.Title, + Description = proposal.Description }; } diff --git a/contract/AElf.Contracts.Referendum/ReferendumConstants.cs b/contract/AElf.Contracts.Referendum/ReferendumConstants.cs new file mode 100644 index 0000000000..89eded59d7 --- /dev/null +++ b/contract/AElf.Contracts.Referendum/ReferendumConstants.cs @@ -0,0 +1,8 @@ +namespace AElf.Contracts.Referendum; + +public static class ReferendumConstants +{ + public const int MaxLengthForTitle = 255; + public const int MaxLengthForDescription = 10200; + public const int MaxLengthForProposalDescriptionUrl = 255; +} \ No newline at end of file diff --git a/contract/AElf.Contracts.Referendum/Referendum_Helper.cs b/contract/AElf.Contracts.Referendum/Referendum_Helper.cs index 02b00d07c1..9b6e4343d9 100644 --- a/contract/AElf.Contracts.Referendum/Referendum_Helper.cs +++ b/contract/AElf.Contracts.Referendum/Referendum_Helper.cs @@ -158,6 +158,7 @@ private Hash GenerateProposalId(CreateProposalInput input) private Hash CreateNewProposal(CreateProposalInput input) { + CheckCreateProposalInput(input); var proposalId = GenerateProposalId(input); Assert(State.Proposals[proposalId] == null, "Proposal already exists."); var proposal = new ProposalInfo @@ -168,14 +169,33 @@ private Hash CreateNewProposal(CreateProposalInput input) Params = input.Params, OrganizationAddress = input.OrganizationAddress, Proposer = Context.Sender, - ProposalDescriptionUrl = input.ProposalDescriptionUrl + ProposalDescriptionUrl = input.ProposalDescriptionUrl, + Title = input.Title, + Description = input.Description }; Assert(Validate(proposal), "Invalid proposal."); State.Proposals[proposalId] = proposal; - Context.Fire(new ProposalCreated { ProposalId = proposalId, OrganizationAddress = input.OrganizationAddress }); + Context.Fire(new ProposalCreated + { + ProposalId = proposalId, + OrganizationAddress = input.OrganizationAddress, + Title = input.Title, + Description = input.Description + }); return proposalId; } + + private void CheckCreateProposalInput(CreateProposalInput input) + { + // Check the length of title + Assert(input.Title.Length <= ReferendumConstants.MaxLengthForTitle, "Title is too long."); + // Check the length of description + Assert(input.Description.Length <= ReferendumConstants.MaxLengthForDescription, "Description is too long."); + // Check the length of description url + Assert(input.ProposalDescriptionUrl.Length <= ReferendumConstants.MaxLengthForProposalDescriptionUrl, + "Description url is too long."); + } private void AssertIsAuthorizedProposer(Address organizationAddress, Address proposer) { diff --git a/contract/AElf.Contracts.TokenConverter/TokenConverterContract.cs b/contract/AElf.Contracts.TokenConverter/TokenConverterContract.cs index 1a7aaf80d2..267d3ad622 100644 --- a/contract/AElf.Contracts.TokenConverter/TokenConverterContract.cs +++ b/contract/AElf.Contracts.TokenConverter/TokenConverterContract.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using AElf.Contracts.MultiToken; using AElf.CSharp.Core; using AElf.Sdk.CSharp; @@ -12,6 +13,9 @@ namespace AElf.Contracts.TokenConverter; public partial class TokenConverterContract : TokenConverterContractImplContainer.TokenConverterContractImplBase { private const string NtTokenPrefix = "nt"; + private const string NewNtTokenPrefix = "(NT)"; + public const string PayTxFeeSymbolListName = "SymbolListToPayTxFee"; + public const string PayRentalSymbolListName = "SymbolListToPayRental"; #region Actions @@ -77,7 +81,7 @@ public override Empty AddPairConnector(PairConnectorParam input) AssertPerformedByConnectorController(); Assert(!string.IsNullOrEmpty(input.ResourceConnectorSymbol), "resource token symbol should not be empty"); - var nativeConnectorSymbol = NtTokenPrefix.Append(input.ResourceConnectorSymbol); + var nativeConnectorSymbol = NewNtTokenPrefix.Append(input.ResourceConnectorSymbol); Assert(State.Connectors[input.ResourceConnectorSymbol] == null, "resource token symbol has existed"); var resourceConnector = new Connector @@ -304,6 +308,39 @@ public override Empty ChangeConnectorController(AuthorityInfo input) return new Empty(); } + public override Empty MigrateConnectorTokens(Empty input) + { + foreach (var resourceTokenSymbol in Context.Variables.GetStringArray(PayTxFeeSymbolListName) + .Union(Context.Variables.GetStringArray(PayRentalSymbolListName))) + { + var newConnectorTokenSymbol = NewNtTokenPrefix.Append(resourceTokenSymbol); + + if (State.Connectors[resourceTokenSymbol] == null) + { + continue; + } + + var oldConnectorTokenSymbol = State.Connectors[resourceTokenSymbol].RelatedSymbol; + + Assert(!oldConnectorTokenSymbol.StartsWith(NewNtTokenPrefix), "Already migrated."); + + // Migrate + + State.Connectors[resourceTokenSymbol].RelatedSymbol = newConnectorTokenSymbol; + + if (State.Connectors[oldConnectorTokenSymbol] != null) + { + var connector = State.Connectors[oldConnectorTokenSymbol]; + connector.Symbol = newConnectorTokenSymbol; + State.Connectors[newConnectorTokenSymbol] = connector; + } + + State.DepositBalance[newConnectorTokenSymbol] = State.DepositBalance[oldConnectorTokenSymbol]; + } + + return new Empty(); + } + #endregion Actions #region Helpers @@ -321,8 +358,7 @@ private static bool IsBetweenZeroAndOne(decimal number) private static bool IsValidSymbol(string symbol) { - return symbol.Length > 0 && - symbol.All(c => c >= 'A' && c <= 'Z'); + return Regex.IsMatch(symbol, "^[a-zA-Z0-9]+$"); } private static bool IsValidBaseSymbol(string symbol) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 14baea36ad..1d1b2aca06 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,4 +9,3 @@ services: - 8001:8000 volumes: - /opt:/opt - diff --git a/docker/start-cli.sh b/docker/start-cli.sh index e5bcf63182..daaf665766 100755 --- a/docker/start-cli.sh +++ b/docker/start-cli.sh @@ -2,4 +2,3 @@ BIND_VOLUME='-v /opt:/opt' CONFIGURE_PATH='-w /opt/aelf-node' docker run -it --rm $BIND_VOLUME $CONFIGURE_PATH aelf/node - diff --git a/docker/start-node.sh b/docker/start-node.sh index a63b5c9798..b258ddaee4 100755 --- a/docker/start-node.sh +++ b/docker/start-node.sh @@ -2,4 +2,4 @@ PUBLISH_PORT='-p 6800:6800 -p 8000:8000' BIND_VOLUME='-v /opt:/opt' CONfIGURE_PATH='/opt/aelf-node' -docker run -itd $PUBLISH_PORT $BIND_VOLUME -w $CONfIGURE_PATH aelf/node dotnet /app/AElf.Launcher.dll --config.path $CONfIGURE_PATH +docker run -itd $PUBLISH_PORT $BIND_VOLUME -w $CONfIGURE_PATH aelf/node dotnet /app/AElf.Launcher.dll --config.path $CONfIGURE_PATH \ No newline at end of file diff --git a/docs-sphinx/index.rst b/docs-sphinx/index.rst index 83e9ac6929..3cff784021 100644 --- a/docs-sphinx/index.rst +++ b/docs-sphinx/index.rst @@ -78,4 +78,4 @@ Welcome to AElf's official documentation! tutorials/cross-chain/running-side-chain tutorials/__run-node - getting-started/smart-contract-development/developing-smart-contracts/index + getting-started/smart-contract-development/developing-smart-contracts/index \ No newline at end of file diff --git a/docs/main-structure.md b/docs/main-structure.md index ba12f8e728..a079e012fe 100644 --- a/docs/main-structure.md +++ b/docs/main-structure.md @@ -88,4 +88,4 @@ * [Google cloud](resources/cloud/gcp/GCP.md) * [Browser Extension](resources/browser-extension.md) * [Joining AElf's testnet](resources/testnet.md) - * [Running a side chain](tutorials/cross-chain/running-side-chain.md) + * [Running a side chain](tutorials/cross-chain/running-side-chain.md) \ No newline at end of file diff --git a/protobuf/acs3.proto b/protobuf/acs3.proto index 65ee66e8ac..79b3391202 100644 --- a/protobuf/acs3.proto +++ b/protobuf/acs3.proto @@ -90,6 +90,10 @@ message CreateProposalInput { string proposal_description_url = 6; // The token is for proposal id generation and with this token, proposal id can be calculated before proposing. aelf.Hash token = 7; + // Title of this proposal. + string title = 8; + // Description of this proposal. + string description = 9; } message ProposalOutput { @@ -115,6 +119,10 @@ message ProposalOutput { int64 rejection_count = 10; // Abstention count for this proposal. int64 abstention_count = 11; + // Title of this proposal. + string title = 12; + // Description of this proposal. + string description = 13; } message ProposalReleaseThreshold { @@ -159,7 +167,11 @@ message ProposalCreated{ // The id of the created proposal. aelf.Hash proposal_id = 1; // The organization address of the created proposal. - aelf.Address organization_address=2 [(aelf.is_indexed) = true]; + aelf.Address organization_address = 2 [(aelf.is_indexed) = true]; + // Title of this proposal. + string title = 3; + // Description of this proposal. + string description = 4; } message ProposalReleased{ @@ -168,6 +180,10 @@ message ProposalReleased{ aelf.Hash proposal_id = 1; // The organization address of the released proposal. aelf.Address organization_address=2 [(aelf.is_indexed) = true]; + // Title of this proposal. + string title = 3; + // Description of this proposal. + string description = 4; } message OrganizationCreated{ diff --git a/protobuf/aelf/core.proto b/protobuf/aelf/core.proto index c990aa9df6..e45d7e581d 100644 --- a/protobuf/aelf/core.proto +++ b/protobuf/aelf/core.proto @@ -72,6 +72,15 @@ message TransactionResult { string error = 10; } +message InvalidTransactionResult{ + // The transaction id. + Hash transaction_id = 1; + // The transaction result status. + TransactionResultStatus status = 2; + // Failed execution error message. + string error = 3; +} + message LogEvent { // The contract address. Address address = 1; diff --git a/protobuf/association_contract.proto b/protobuf/association_contract.proto index 2efb8210c6..5b2a78e2b7 100644 --- a/protobuf/association_contract.proto +++ b/protobuf/association_contract.proto @@ -96,6 +96,10 @@ message ProposalInfo { repeated aelf.Address abstentions = 10; // Url is used for proposal describing. string proposal_description_url = 11; + // Title of this proposal. + string title = 12; + // Description of this proposal. + string description = 13; } message OrganizationMemberList { diff --git a/protobuf/basic_contract_zero.proto b/protobuf/basic_contract_zero.proto index 30960f8433..0967cf866d 100644 --- a/protobuf/basic_contract_zero.proto +++ b/protobuf/basic_contract_zero.proto @@ -37,6 +37,9 @@ service BasicContractZero { rpc SetContractProposalExpirationTimePeriod(SetContractProposalExpirationTimePeriodInput) returns(google.protobuf.Empty){ } + + rpc SetCodeCheckProposalExpirationTimePeriod(google.protobuf.Int32Value) returns(google.protobuf.Empty){ + } // Query the ContractDeploymentController authority info. rpc GetContractDeploymentController (google.protobuf.Empty) returns (AuthorityInfo) { @@ -51,6 +54,10 @@ service BasicContractZero { rpc GetContractProposalExpirationTimePeriod(google.protobuf.Empty) returns (google.protobuf.Int32Value){ option (aelf.is_view) = true; } + + rpc GetCodeCheckProposalExpirationTimePeriod(google.protobuf.Empty) returns (google.protobuf.Int32Value){ + option (aelf.is_view) = true; + } } message InitializeInput{ diff --git a/protobuf/parliament_contract.proto b/protobuf/parliament_contract.proto index e5d2ef4422..8d0fca5bef 100644 --- a/protobuf/parliament_contract.proto +++ b/protobuf/parliament_contract.proto @@ -136,6 +136,10 @@ message ProposalInfo { repeated aelf.Address abstentions = 10; // Url is used for proposal describing. string proposal_description_url = 11; + // Title of this proposal. + string title = 12; + // Description of this proposal. + string description = 13; } message InitializeInput{ diff --git a/protobuf/profit_contract.proto b/protobuf/profit_contract.proto index 1326da62ff..aac44f2d6a 100644 --- a/protobuf/profit_contract.proto +++ b/protobuf/profit_contract.proto @@ -63,6 +63,9 @@ service ProfitContract { // Reset the manager of a scheme. rpc ResetManager (ResetManagerInput) returns (google.protobuf.Empty) { } + + rpc SetMaximumProfitReceivingPeriodCount(google.protobuf.Int32Value) returns (google.protobuf.Empty) { + } // Get all schemes managed by the specified manager. rpc GetManagingSchemeIds (GetManagingSchemeIdsInput) returns (CreatedSchemeIds) { @@ -93,11 +96,25 @@ service ProfitContract { rpc GetProfitAmount (GetProfitAmountInput) returns (google.protobuf.Int64Value) { option (aelf.is_view) = true; } + + // Query the amount of profit according to token symbol. + rpc GetAllProfitAmount (GetAllProfitAmountInput) returns (GetAllProfitAmountOutput) { + option (aelf.is_view) = true; + } // Query all profit (up to 10 periods). rpc GetProfitsMap (ClaimProfitsInput) returns (ReceivedProfitsMap) { option (aelf.is_view) = true; } + + // Query all profit. + rpc GetAllProfitsMap (GetAllProfitsMapInput) returns (GetAllProfitsMapOutput) { + option (aelf.is_view) = true; + } + + rpc GetMaximumProfitReceivingPeriodCount(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (aelf.is_view) = true; + } } message CreateSchemeInput { @@ -308,11 +325,39 @@ message GetProfitAmountInput { aelf.Address beneficiary = 3; } +message GetAllProfitAmountInput { + // The scheme id. + aelf.Hash scheme_id = 1; + // The token symbol. + string symbol = 2; + // The beneficiary's address. + aelf.Address beneficiary = 3; +} + +message GetAllProfitAmountOutput{ + int64 all_profit_amount = 1; + int64 one_time_claimable_profit_amount = 2; +} + message ReceivedProfitsMap { // The collection of profits received, token symbol -> amount. map value = 1; } +message GetAllProfitsMapInput { + // The scheme id. + aelf.Hash scheme_id = 1; + // The address of beneficiary. + aelf.Address beneficiary = 2; +} + +message GetAllProfitsMapOutput { + // The collection of all profits received, token symbol -> amount. + map all_profits_map = 1; + // The collection of claimable profits received, token symbol -> amount. + map one_time_claimable_profits_map = 2; +} + message SchemeCreated { option (aelf.is_event) = true; // The virtual address of the created scheme. diff --git a/protobuf/referendum_contract.proto b/protobuf/referendum_contract.proto index 82af016f13..53bb243b6e 100644 --- a/protobuf/referendum_contract.proto +++ b/protobuf/referendum_contract.proto @@ -101,6 +101,10 @@ message ProposalInfo { int64 abstention_count = 10; // Url is used for proposal describing. string proposal_description_url = 11; + // Title of this proposal. + string title = 12; + // Description of this proposal. + string description = 13; } message CreateOrganizationBySystemContractInput { diff --git a/protobuf/token_contract.proto b/protobuf/token_contract.proto index 5082990a99..9931b680a3 100644 --- a/protobuf/token_contract.proto +++ b/protobuf/token_contract.proto @@ -40,7 +40,10 @@ service TokenContract { // enabling the Spender to call TransferFrom. rpc Approve (ApproveInput) returns (google.protobuf.Empty) { } - + + rpc BatchApprove (BatchApproveInput) returns (google.protobuf.Empty) { + } + // This is the reverse operation for Approve, it will decrease the allowance. rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { } @@ -141,6 +144,9 @@ service TokenContract { rpc RemoveTransactionFeeDelegatee (RemoveTransactionFeeDelegateeInput) returns (google.protobuf.Empty){ } + rpc SetSymbolAlias (SetSymbolAliasInput) returns (google.protobuf.Empty){ + } + // Get all delegatees' address of delegator from input rpc GetTransactionFeeDelegatees (GetTransactionFeeDelegateesInput) returns (GetTransactionFeeDelegateesOutput) { option (aelf.is_view) = true; @@ -171,6 +177,11 @@ service TokenContract { option (aelf.is_view) = true; } + // Query the account's available allowance for other addresses + rpc GetAvailableAllowance (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) { @@ -226,6 +237,14 @@ service TokenContract { rpc GetTransactionFeeDelegationsOfADelegatee(GetTransactionFeeDelegationsOfADelegateeInput) returns(TransactionFeeDelegations){ option (aelf.is_view) = true; } + + rpc GetTokenAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + + rpc GetSymbolByAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } } message TokenInfo { @@ -354,6 +373,9 @@ message ApproveInput { // The amount of token to approve. int64 amount = 3; } +message BatchApproveInput { + repeated ApproveInput value = 1; +} message UnApproveInput { // The address that allowance will be decreased. @@ -632,19 +654,6 @@ message TotalResourceTokensMap 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; } @@ -696,6 +705,11 @@ message GetTransactionFeeDelegateesOutput { repeated aelf.Address delegatee_addresses = 1; } +message SetSymbolAliasInput { + string symbol = 1; + string alias = 2; +} + // Events message Transferred { @@ -853,12 +867,6 @@ message CrossChainReceived { aelf.Hash transfer_transaction_id =9; } -message ExternalInfoChanged { - option (aelf.is_event) = true; - string symbol = 1; - ExternalInfo external_info = 2; -} - message TransactionFeeDelegationAdded { option (aelf.is_event) = true; aelf.Address delegator = 1 [(aelf.is_indexed) = true]; @@ -871,4 +879,16 @@ message TransactionFeeDelegationCancelled { aelf.Address delegator = 1 [(aelf.is_indexed) = true]; aelf.Address delegatee = 2 [(aelf.is_indexed) = true]; aelf.Address caller = 3 [(aelf.is_indexed) = true]; +} + +message SymbolAliasAdded { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + string alias = 2 [(aelf.is_indexed) = true]; +} + +message SymbolAliasDeleted { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + string alias = 2 [(aelf.is_indexed) = true]; } \ No newline at end of file diff --git a/protobuf/token_contract_impl.proto b/protobuf/token_contract_impl.proto index 97acd5b439..5885914e80 100644 --- a/protobuf/token_contract_impl.proto +++ b/protobuf/token_contract_impl.proto @@ -83,6 +83,11 @@ service TokenContractImpl { rpc RemoveTransactionFeeFreeAllowancesConfig (RemoveTransactionFeeFreeAllowancesConfigInput) returns (google.protobuf.Empty) { } + rpc SetMaxBatchApproveCount (google.protobuf.Int32Value) returns (google.protobuf.Empty) { + + } + + // Delegatee sets the delegation and related information of the delegator based on a transaction. rpc SetTransactionFeeDelegateInfos (SetTransactionFeeDelegateInfosInput) returns (google.protobuf.Empty){ } @@ -163,6 +168,20 @@ service TokenContractImpl { rpc GetTransactionFeeDelegateInfo(GetTransactionFeeDelegateInfoInput) returns (token.TransactionFeeDelegations){ option (aelf.is_view) = true; } + + rpc ModifyTokenIssuerAndOwner(ModifyTokenIssuerAndOwnerInput) returns (google.protobuf.Empty) { + } + + rpc SetTokenIssuerAndOwnerModificationEnabled(SetTokenIssuerAndOwnerModificationEnabledInput) returns (google.protobuf.Empty) { + } + + rpc GetTokenIssuerAndOwnerModificationEnabled(google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + + rpc GetMaxBatchApproveCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) { + + } } message AdvanceResourceTokenInput { @@ -416,3 +435,13 @@ message TransactionFeeDelegateInfoCancelled { aelf.Address caller = 3 ; DelegateTransactionList delegate_transaction_list = 4; } + +message ModifyTokenIssuerAndOwnerInput { + string symbol = 1; + aelf.Address issuer = 2; + aelf.Address owner = 3; +} + +message SetTokenIssuerAndOwnerModificationEnabledInput{ + bool enabled = 1; +} \ No newline at end of file diff --git a/protobuf/token_converter_contract.proto b/protobuf/token_converter_contract.proto index af6e99c10c..5f92194185 100644 --- a/protobuf/token_converter_contract.proto +++ b/protobuf/token_converter_contract.proto @@ -51,7 +51,10 @@ service TokenConverterContract { // Set the governance authority information for TokenConvert contract. rpc ChangeConnectorController (AuthorityInfo) returns (google.protobuf.Empty) { } - + + rpc MigrateConnectorTokens (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + // Query the pair connector according to token symbol. rpc GetPairConnector (TokenSymbol) returns (PairConnector) { option (aelf.is_view) = true; diff --git a/scripts/build.sh b/scripts/build.sh index 83356576d6..f614ac0aaa 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -6,4 +6,4 @@ dotnet build /clp:ErrorsOnly /p:GeneratePackageOnBuild=false -v quiet "AElf.All. if [[ $? -ne 0 ]] ; then echo "Build failed." exit 1 -fi +fi \ No newline at end of file diff --git a/scripts/deploy_docker.sh b/scripts/deploy_docker.sh index 3224bfb7ad..e7a768efdb 100644 --- a/scripts/deploy_docker.sh +++ b/scripts/deploy_docker.sh @@ -13,4 +13,4 @@ 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 +docker push aelf/node:latest \ No newline at end of file diff --git a/scripts/deploy_myget.sh b/scripts/deploy_myget.sh index 0931a825cf..09b26015ca 100644 --- a/scripts/deploy_myget.sh +++ b/scripts/deploy_myget.sh @@ -31,4 +31,4 @@ do fi done cd ../ -done +done \ No newline at end of file diff --git a/scripts/deploy_myget_daily.sh b/scripts/deploy_myget_daily.sh index 4843b3f7ab..b6916f79b2 100644 --- a/scripts/deploy_myget_daily.sh +++ b/scripts/deploy_myget_daily.sh @@ -34,4 +34,4 @@ do fi done cd ../ -done +done \ No newline at end of file diff --git a/scripts/deploy_nuget.sh b/scripts/deploy_nuget.sh index 0fdc981c3e..ef9f49c37d 100644 --- a/scripts/deploy_nuget.sh +++ b/scripts/deploy_nuget.sh @@ -31,4 +31,4 @@ do fi done cd ../ -done +done \ No newline at end of file diff --git a/scripts/download_binary.bat b/scripts/download_binary.bat index 02382a3bb9..7ec3dc18ca 100644 --- a/scripts/download_binary.bat +++ b/scripts/download_binary.bat @@ -13,4 +13,4 @@ if not exist "%scriptdir%contract_csharp_plugin.exe" ( ) echo "unzip file: %file%" unzip %scriptdir%%filename% -d %scriptdir% -) +) \ No newline at end of file diff --git a/scripts/download_binary.sh b/scripts/download_binary.sh index 49b8fdbe77..0234693718 100644 --- a/scripts/download_binary.sh +++ b/scripts/download_binary.sh @@ -30,4 +30,4 @@ if [[ ! -f ${plugin} ]]; then # Unzip unzip -o ${filename} -d "${scriptdir}" -fi +fi \ No newline at end of file diff --git a/scripts/generate_contract_base.bat b/scripts/generate_contract_base.bat index 57ca166469..ed022fa2c9 100644 --- a/scripts/generate_contract_base.bat +++ b/scripts/generate_contract_base.bat @@ -8,4 +8,4 @@ protoc --proto_path=../../protobuf ^ --contract_opt=%2,nocontract ^ --contract_out=./Protobuf/Generated ^ --plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ -%1 +%1 \ No newline at end of file diff --git a/scripts/generate_contract_base.sh b/scripts/generate_contract_base.sh index 28a1550d3e..6d35a165aa 100755 --- a/scripts/generate_contract_base.sh +++ b/scripts/generate_contract_base.sh @@ -13,4 +13,4 @@ protoc --proto_path=${solutiondir}/protobuf \ --contract_opt="$2",nocontract \ --contract_out=./Protobuf/Generated \ --plugin=protoc-gen-contract=${plugin} \ -$1 +$1 \ No newline at end of file diff --git a/scripts/generate_contract_code.bat b/scripts/generate_contract_code.bat index 3b95aad75b..ccd91e4ede 100644 --- a/scripts/generate_contract_code.bat +++ b/scripts/generate_contract_code.bat @@ -7,4 +7,4 @@ protoc --proto_path=../../protobuf ^ --csharp_opt=file_extension=.g.cs ^ --contract_out=./Protobuf/Generated ^ --plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ -%* +%* \ No newline at end of file diff --git a/scripts/generate_contract_code.sh b/scripts/generate_contract_code.sh index 60093c9a13..7dec613fc8 100755 --- a/scripts/generate_contract_code.sh +++ b/scripts/generate_contract_code.sh @@ -12,4 +12,4 @@ protoc --proto_path=${solutiondir}/protobuf \ --csharp_opt=file_extension=.g.cs \ --contract_out=./Protobuf/Generated \ --plugin=protoc-gen-contract=${plugin} \ -$@ +$@ \ No newline at end of file diff --git a/scripts/generate_contract_reference.bat b/scripts/generate_contract_reference.bat index 97fee83d02..6c69a31a54 100644 --- a/scripts/generate_contract_reference.bat +++ b/scripts/generate_contract_reference.bat @@ -8,4 +8,4 @@ protoc --proto_path=../../protobuf ^ --contract_opt=reference ^ --contract_out=internal_access:./Protobuf/Generated ^ --plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ -%* +%* \ No newline at end of file diff --git a/scripts/generate_contract_reference.sh b/scripts/generate_contract_reference.sh index 47f8027ed1..4fe24547ed 100755 --- a/scripts/generate_contract_reference.sh +++ b/scripts/generate_contract_reference.sh @@ -13,4 +13,4 @@ protoc --proto_path=${solutiondir}/protobuf \ --contract_opt=reference \ --contract_out=internal_access:./Protobuf/Generated \ --plugin=protoc-gen-contract="${plugin}" \ -$@ +$@ \ No newline at end of file diff --git a/scripts/generate_contract_stub.bat b/scripts/generate_contract_stub.bat index 9e1120e8cd..67f8d11149 100644 --- a/scripts/generate_contract_stub.bat +++ b/scripts/generate_contract_stub.bat @@ -9,4 +9,4 @@ protoc --proto_path=../../protobuf ^ --contract_opt=internal_access ^ --contract_out=./Protobuf/Generated ^ --plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ -%* +%* \ No newline at end of file diff --git a/scripts/generate_contract_stub.sh b/scripts/generate_contract_stub.sh index ac303fabb7..1e8faa7db1 100755 --- a/scripts/generate_contract_stub.sh +++ b/scripts/generate_contract_stub.sh @@ -18,4 +18,4 @@ protoc --proto_path=${solutiondir}/protobuf \ --contract_opt=internal_access \ --contract_out=${destdir} \ --plugin=protoc-gen-contract="${plugin}" \ -$@ +$@ \ No newline at end of file diff --git a/scripts/generate_event_only.bat b/scripts/generate_event_only.bat index 31dd3e1d16..c4e8fb587d 100644 --- a/scripts/generate_event_only.bat +++ b/scripts/generate_event_only.bat @@ -8,4 +8,4 @@ protoc --proto_path=../../protobuf ^ --contract_opt=nocontract ^ --contract_out=internal_access:./Protobuf/Generated ^ --plugin=protoc-gen-contract="%scriptdir%contract_csharp_plugin.exe" ^ -%* +%* \ No newline at end of file diff --git a/scripts/generate_event_only.sh b/scripts/generate_event_only.sh index db7d22d03f..8bc466768a 100755 --- a/scripts/generate_event_only.sh +++ b/scripts/generate_event_only.sh @@ -13,4 +13,4 @@ protoc --proto_path=${solutiondir}/protobuf \ --contract_opt=nocontract \ --contract_out=internal_access:./Protobuf/Generated \ --plugin=protoc-gen-contract=${plugin} \ -$@ +$@ \ No newline at end of file diff --git a/scripts/install_protobuf.sh b/scripts/install_protobuf.sh index 0d6400db02..941fd8e0dd 100644 --- a/scripts/install_protobuf.sh +++ b/scripts/install_protobuf.sh @@ -35,4 +35,4 @@ elif [[ ${osn} == "linux" ]]; then # Optional: change owner sudo chown ${USER} /usr/local/bin/protoc sudo chown -R ${USER} /usr/local/include/google -fi +fi \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh index 7922415954..47b7b1de60 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -20,4 +20,4 @@ for i in *Tests ; do echo "Test Run Successful." -done +done \ No newline at end of file diff --git a/scripts/upload_coverage.sh b/scripts/upload_coverage.sh index 082982d9a3..70326d9daf 100644 --- a/scripts/upload_coverage.sh +++ b/scripts/upload_coverage.sh @@ -2,4 +2,4 @@ curl -s https://codecov.io/bash > codecov chmod +x codecov -./codecov -f "../test/results/coverage.opencover.xml" -t $1 +./codecov -f "../test/results/coverage.opencover.xml" -t $1 \ No newline at end of file diff --git a/src/AElf.Blockchains.BasicBaseChain/GenesisSmartContractDtoProviderBase.cs b/src/AElf.Blockchains.BasicBaseChain/GenesisSmartContractDtoProviderBase.cs index 844998a236..d056ecd523 100644 --- a/src/AElf.Blockchains.BasicBaseChain/GenesisSmartContractDtoProviderBase.cs +++ b/src/AElf.Blockchains.BasicBaseChain/GenesisSmartContractDtoProviderBase.cs @@ -25,11 +25,13 @@ public virtual IEnumerable GetGenesisSmartContractDtos( { var contractCodes = GetContractCodes(); var deploymentList = ContractDeploymentListProvider.GetDeployContractNameList(); - return ContractInitializationProviders - .Where(p => deploymentList.Contains(p.SystemSmartContractName)) - .OrderBy(p => deploymentList.IndexOf(p.SystemSmartContractName)) - .Select(p => + return ContractInitializationProviders + .GroupBy(p => p.SystemSmartContractName) + .Where(g => deploymentList.Contains(g.Key)) + .OrderBy(g => deploymentList.IndexOf(g.Key)) + .Select(g => { + var p = g.Last(); var code = contractCodes[p.ContractCodeName]; var methodList = p.GetInitializeMethodList(code); var genesisSmartContractDto = new GenesisSmartContractDto diff --git a/src/AElf.Blockchains.MainChain/MainChainGenesisSmartContractDtoProvider.cs b/src/AElf.Blockchains.MainChain/MainChainGenesisSmartContractDtoProvider.cs index 78826cdd23..460f4759f2 100644 --- a/src/AElf.Blockchains.MainChain/MainChainGenesisSmartContractDtoProvider.cs +++ b/src/AElf.Blockchains.MainChain/MainChainGenesisSmartContractDtoProvider.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Linq; using AElf.Blockchains.BasicBaseChain; using AElf.ContractDeployer; +using AElf.Kernel.Plugin.Application; using AElf.Kernel.SmartContract; using AElf.Kernel.SmartContract.Application; using Microsoft.Extensions.Options; @@ -12,19 +14,23 @@ namespace AElf.Blockchains.MainChain; /// public class MainChainGenesisSmartContractDtoProvider : GenesisSmartContractDtoProviderBase { + private readonly IEnumerable _pluginContractProviders; private readonly ContractOptions _contractOptions; public MainChainGenesisSmartContractDtoProvider(IContractDeploymentListProvider contractDeploymentListProvider, IEnumerable contractInitializationProviders, + IEnumerable pluginContractProviders, IOptionsSnapshot contractOptions) : base(contractDeploymentListProvider, contractInitializationProviders) { + _pluginContractProviders = pluginContractProviders; _contractOptions = contractOptions.Value; } protected override IReadOnlyDictionary GetContractCodes() { - return ContractsDeployer.GetContractCodes(_contractOptions - .GenesisContractDir); + return ContractsDeployer.GetContractCodes( + _contractOptions.GenesisContractDir, + pluginContractNames: _pluginContractProviders.Select(p => p.GetContractName()).ToList()); } } \ No newline at end of file diff --git a/src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs b/src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs index b6c5860a16..b6a45aeedf 100644 --- a/src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs +++ b/src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Text.RegularExpressions; using AElf.Cryptography.SecretSharing; using AElf.CSharp.Core; using AElf.Kernel.SmartContract; @@ -37,6 +38,7 @@ private void WhitelistAssemblies(Whitelist whitelist) .Assembly(System.Reflection.Assembly.Load("System.Runtime.Extensions"), Trust.Partial) .Assembly(System.Reflection.Assembly.Load("System.Private.CoreLib"), Trust.Partial) .Assembly(System.Reflection.Assembly.Load("System.ObjectModel"), Trust.Partial) + .Assembly(System.Reflection.Assembly.Load("System.Text.RegularExpressions"), Trust.Partial) .Assembly(System.Reflection.Assembly.Load("System.Linq"), Trust.Full) .Assembly(System.Reflection.Assembly.Load("System.Linq.Expressions"), Trust.Full) .Assembly(System.Reflection.Assembly.Load("System.Collections"), Trust.Full) @@ -144,6 +146,17 @@ private void WhitelistOthers(Whitelist whitelist) .Member(nameof(Encoding.UTF8), Permission.Allowed) .Member(nameof(Encoding.UTF8.GetByteCount), Permission.Allowed))) .Namespace("System.Numerics", Permission.Allowed) + .Namespace("System.Text.RegularExpressions", Permission.Denied, type => type + .Type(nameof(Regex), Permission.Denied, member => member + .Member(nameof(Regex.IsMatch), Permission.Allowed) + .Member(nameof(Regex.Match), Permission.Allowed) + .Member(nameof(Regex.Matches), Permission.Allowed) + .Member(nameof(Regex.Replace), Permission.Allowed) + .Member(nameof(Regex.Split), Permission.Allowed) + ) + .Type(nameof(MatchCollection), Permission.Allowed) + .Type(nameof(Match), Permission.Allowed) + ) ; } diff --git a/src/AElf.ContractDeployer/ContractsDeployer.cs b/src/AElf.ContractDeployer/ContractsDeployer.cs index 6c2aec9dd0..a79e0e5121 100644 --- a/src/AElf.ContractDeployer/ContractsDeployer.cs +++ b/src/AElf.ContractDeployer/ContractsDeployer.cs @@ -8,9 +8,14 @@ namespace AElf.ContractDeployer; public static class ContractsDeployer { public static IReadOnlyDictionary GetContractCodes(string contractDir = null, - bool isPatched = false) + bool isPatched = false, List pluginContractNames = null) { var contractNames = GetContractNames(typeof(T).Assembly).ToList(); + if (pluginContractNames is { Count: > 0 }) + { + contractNames.AddRange(pluginContractNames); + } + if (contractNames.Count == 0) throw new NoContractDllFoundInManifestException(); return contractNames.Select(n => (n, GetCode(n, contractDir, isPatched))) diff --git a/src/AElf.Cryptography/AElf.Cryptography.csproj b/src/AElf.Cryptography/AElf.Cryptography.csproj index 0e487ed184..1f5c07064d 100644 --- a/src/AElf.Cryptography/AElf.Cryptography.csproj +++ b/src/AElf.Cryptography/AElf.Cryptography.csproj @@ -10,7 +10,6 @@ - diff --git a/src/AElf.Cryptography/CryptoHelper.cs b/src/AElf.Cryptography/CryptoHelper.cs index a631a7dd2f..ea44d70a9a 100644 --- a/src/AElf.Cryptography/CryptoHelper.cs +++ b/src/AElf.Cryptography/CryptoHelper.cs @@ -6,8 +6,15 @@ using AElf.Cryptography.ECDSA; using AElf.Cryptography.ECVRF; using AElf.Cryptography.Exceptions; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; using Secp256k1Net; -using Virgil.Crypto; using ECParameters = AElf.Cryptography.ECDSA.ECParameters; namespace AElf.Cryptography @@ -134,37 +141,54 @@ public static bool RecoverPublicKey(byte[] signature, byte[] hash, out byte[] pu public static byte[] EncryptMessage(byte[] senderPrivateKey, byte[] receiverPublicKey, byte[] plainText) { - var crypto = new VirgilCrypto(KeyPairType.EC_SECP256K1); - var ecdhKey = Ecdh(senderPrivateKey, receiverPublicKey); - var newKeyPair = crypto.GenerateKeys(KeyPairType.EC_SECP256K1, ecdhKey); - return crypto.Encrypt(plainText, newKeyPair.PublicKey); + var keyBytes = GetSharedSecret(senderPrivateKey, receiverPublicKey); + + var cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesEngine())); + cipher.Init(true, new ParametersWithIV(new KeyParameter(keyBytes), new byte[16])); + + var cipherText = new byte[cipher.GetOutputSize(plainText.Length)]; + var len = cipher.ProcessBytes(plainText, 0, plainText.Length, cipherText, 0); + cipher.DoFinal(cipherText, len); + + return cipherText; } public static byte[] DecryptMessage(byte[] senderPublicKey, byte[] receiverPrivateKey, byte[] cipherText) { - var crypto = new VirgilCrypto(KeyPairType.EC_SECP256K1); - var ecdhKey = Ecdh(receiverPrivateKey, senderPublicKey); - var newKeyPair = crypto.GenerateKeys(KeyPairType.EC_SECP256K1, ecdhKey); - return crypto.Decrypt(cipherText, newKeyPair.PrivateKey); + var keyBytes = GetSharedSecret(receiverPrivateKey, senderPublicKey); + + var cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesEngine())); + cipher.Init(false, new ParametersWithIV(new KeyParameter(keyBytes), new byte[16])); + + var temp = new byte[cipher.GetOutputSize(cipherText.Length)]; + var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, temp, 0); + len += cipher.DoFinal(temp, len); + + // Remove padding + var plainText = new byte[len]; + Array.Copy(temp, 0, plainText, 0, len); + + return plainText; } - public static byte[] Ecdh(byte[] privateKey, byte[] publicKey) + private static byte[] GetSharedSecret(byte[] privateKey, byte[] publicKey) { - try - { - Lock.EnterWriteLock(); - var usablePublicKey = new byte[Secp256k1.SERIALIZED_UNCOMPRESSED_PUBKEY_LENGTH]; - if (!Secp256K1.PublicKeyParse(usablePublicKey, publicKey)) - throw new PublicKeyOperationException("Parse public key failed."); - var ecdhKey = new byte[Secp256k1.SERIALIZED_COMPRESSED_PUBKEY_LENGTH]; - if (!Secp256K1.Ecdh(ecdhKey, usablePublicKey, privateKey)) - throw new EcdhOperationException("Compute EC Diffie- secret failed."); - return ecdhKey; - } - finally - { - Lock.ExitWriteLock(); - } + var curve = ECParameters.Curve; + var domainParams = ECParameters.DomainParams; + var privateKeyParams = new ECPrivateKeyParameters(new BigInteger(1, privateKey), domainParams); + var publicKeyParams = new ECPublicKeyParameters(curve.Curve.DecodePoint(publicKey), domainParams); + + var agreement = AgreementUtilities.GetBasicAgreement("ECDH"); + agreement.Init(privateKeyParams); + var secret = agreement.CalculateAgreement(publicKeyParams); + + var kdf = new Kdf2BytesGenerator(new Sha256Digest()); + kdf.Init(new KdfParameters(secret.ToByteArray(), null)); + + var keyBytes = new byte[32]; + kdf.GenerateBytes(keyBytes, 0, keyBytes.Length); + + return keyBytes; } public static byte[] ECVrfProve(ECKeyPair keyPair, byte[] alpha) diff --git a/src/AElf.Kernel.CodeCheck/Application/CodeCheckJob.cs b/src/AElf.Kernel.CodeCheck/Application/CodeCheckJob.cs index 7898215da7..d6e268718e 100644 --- a/src/AElf.Kernel.CodeCheck/Application/CodeCheckJob.cs +++ b/src/AElf.Kernel.CodeCheck/Application/CodeCheckJob.cs @@ -11,4 +11,12 @@ public class CodeCheckJob public Hash CodeCheckProposalId { get; set; } public Hash ProposedContractInputHash { get; set; } public long BucketIndex { get; set; } + + public override string ToString() + { + return $"BlockHash: {BlockHash}, BlockHeight: {BlockHeight}, ContractCategory: {ContractCategory}, " + + $"IsSystemContract: {IsSystemContract}, IsUserContract: {IsUserContract}, " + + $"CodeCheckProposalId: {CodeCheckProposalId}, ProposedContractInputHash: {ProposedContractInputHash}, " + + $"BucketIndex: {BucketIndex}"; + } } \ No newline at end of file diff --git a/src/AElf.Kernel.CodeCheck/Application/CodeCheckJobProcessor.cs b/src/AElf.Kernel.CodeCheck/Application/CodeCheckJobProcessor.cs index 99c53b9c35..bcb74dfd14 100644 --- a/src/AElf.Kernel.CodeCheck/Application/CodeCheckJobProcessor.cs +++ b/src/AElf.Kernel.CodeCheck/Application/CodeCheckJobProcessor.cs @@ -16,14 +16,14 @@ public interface ICodeCheckJobProcessor public class CodeCheckJobProcessor : ICodeCheckJobProcessor, ISingletonDependency { - private readonly TransformBlock _codeCheckTransformBlock; + private TransformBlock _codeCheckTransformBlock; private List> _codeCheckProcessesJobTransformBlock; private readonly CodeCheckOptions _codeCheckOptions; private readonly ICheckedCodeHashProvider _checkedCodeHashProvider; private readonly ICodeCheckService _codeCheckService; private readonly IProposalService _proposalService; private readonly ICodeCheckProposalService _codeCheckProposalService; - + public ILogger Logger { get; set; } public CodeCheckJobProcessor(IOptionsSnapshot codeCheckOptions, @@ -42,7 +42,18 @@ public CodeCheckJobProcessor(IOptionsSnapshot codeCheckOptions public async Task SendAsync(CodeCheckJob job) { - return await _codeCheckTransformBlock.SendAsync(job); + var codeCheckJobSendResult = await _codeCheckTransformBlock.SendAsync(job); + if (!codeCheckJobSendResult) + { + Logger.LogError( + $"Failed to send code check job. " + + $"Input count: {_codeCheckTransformBlock.InputCount}, " + + $"output count: {_codeCheckTransformBlock.OutputCount}"); + //Logger.LogError("Trying to recovery."); + //_codeCheckTransformBlock = CreateCodeCheckBufferBlock(); + } + + return codeCheckJobSendResult; } public async Task CompleteAsync() @@ -61,7 +72,7 @@ private TransformBlock CreateCodeCheckBufferBlock() BoundedCapacity = Math.Max(_codeCheckOptions.MaxBoundedCapacity, 1), MaxDegreeOfParallelism = _codeCheckOptions.MaxDegreeOfParallelism }); - + _codeCheckProcessesJobTransformBlock = new List>(); for (var i = 0; i < _codeCheckOptions.MaxDegreeOfParallelism; i++) { @@ -83,41 +94,45 @@ private TransformBlock CreateCodeCheckBufferBlock() private async Task ProcessCodeCheckJobAsync(CodeCheckJob job) { - var codeCheckResult = await _codeCheckService.PerformCodeCheckAsync(job.ContractCode, job.BlockHash, - job.BlockHeight, job.ContractCategory, job.IsSystemContract, job.IsUserContract); - - var codeHash = HashHelper.ComputeFrom(job.ContractCode); - Logger.LogInformation("Code check result: {codeCheckResult}, code hash: {codeHash}", codeCheckResult, - codeHash.ToHex()); - - if (!codeCheckResult) - return; - - if (job.IsUserContract) + try { - _codeCheckProposalService.AddReleasableProposal(job.CodeCheckProposalId, job.ProposedContractInputHash, - job.BlockHeight); - } + var codeCheckResult = await _codeCheckService.PerformCodeCheckAsync(job.ContractCode, job.BlockHash, + job.BlockHeight, job.ContractCategory, job.IsSystemContract, job.IsUserContract); + + var codeHash = HashHelper.ComputeFrom(job.ContractCode); + + if (!codeCheckResult) + { + Logger.LogError("Code check failed for code hash: {codeHash}", codeHash.ToHex()); + return; + } + + if (job.IsUserContract) + { + _codeCheckProposalService.AddReleasableProposal(job.CodeCheckProposalId, job.ProposedContractInputHash, + job.BlockHeight); + } - // Cache proposal id to generate system approval transaction later - _proposalService.AddNotApprovedProposal(job.CodeCheckProposalId, job.BlockHeight); + _proposalService.AddNotApprovedProposal(job.CodeCheckProposalId, job.BlockHeight); - await _checkedCodeHashProvider.AddCodeHashAsync(new BlockIndex + await _checkedCodeHashProvider.AddCodeHashAsync(new BlockIndex + { + BlockHash = job.BlockHash, + BlockHeight = job.BlockHeight + }, codeHash); + } + catch (Exception e) { - BlockHash = job.BlockHash, - BlockHeight = job.BlockHeight - }, codeHash); + Logger.LogError("Error while processing code check job: {e}", e); + throw; + } } - + private CodeCheckJob UpdateBucketIndex(CodeCheckJob job) { - var assemblyLoadContext = new AssemblyLoadContext(null, true); - var assembly = assemblyLoadContext.LoadFromStream(new MemoryStream(job.ContractCode)); - job.BucketIndex = - Math.Abs(HashHelper.ComputeFrom(assembly.GetName().Name).ToInt64() % _codeCheckOptions.MaxDegreeOfParallelism); - assemblyLoadContext.Unload(); - + Math.Abs(HashHelper.ComputeFrom(job.ContractCode).ToInt64() % _codeCheckOptions.MaxDegreeOfParallelism); + return job; } } \ No newline at end of file diff --git a/src/AElf.Kernel.CodeCheck/Application/CodeCheckValidationProvider.cs b/src/AElf.Kernel.CodeCheck/Application/CodeCheckValidationProvider.cs index 0d772880e4..357e127ac1 100644 --- a/src/AElf.Kernel.CodeCheck/Application/CodeCheckValidationProvider.cs +++ b/src/AElf.Kernel.CodeCheck/Application/CodeCheckValidationProvider.cs @@ -15,8 +15,7 @@ internal class CodeCheckValidationProvider : IBlockValidationProvider public CodeCheckValidationProvider(ISmartContractAddressService smartContractAddressService, IContractReaderFactory contractReaderFactory, - ICheckedCodeHashProvider checkedCodeHashProvider, - IOptionsSnapshot codeCheckOptions) + ICheckedCodeHashProvider checkedCodeHashProvider) { _smartContractAddressService = smartContractAddressService; _contractReaderFactory = contractReaderFactory; diff --git a/src/AElf.Kernel.CodeCheck/Application/ICheckedCodeHashProvider.cs b/src/AElf.Kernel.CodeCheck/Application/ICheckedCodeHashProvider.cs index ac8ccc666a..3b33064455 100644 --- a/src/AElf.Kernel.CodeCheck/Application/ICheckedCodeHashProvider.cs +++ b/src/AElf.Kernel.CodeCheck/Application/ICheckedCodeHashProvider.cs @@ -13,10 +13,14 @@ public interface ICheckedCodeHashProvider internal class CheckedCodeHashProvider : BlockExecutedDataBaseProvider, ICheckedCodeHashProvider, ISingletonDependency { + private readonly CodeCheckOptions _codeCheckOptions; + public CheckedCodeHashProvider( - ICachedBlockchainExecutedDataService cachedBlockchainExecutedDataService) : + ICachedBlockchainExecutedDataService cachedBlockchainExecutedDataService, + IOptionsMonitor codeCheckOptions) : base(cachedBlockchainExecutedDataService) { + _codeCheckOptions = codeCheckOptions.CurrentValue; Logger = NullLogger.Instance; } @@ -32,6 +36,11 @@ public async Task AddCodeHashAsync(BlockIndex blockIndex, Hash codeHash) public bool IsCodeHashExists(BlockIndex blockIndex, Hash codeHash) { + if (!_codeCheckOptions.CodeCheckEnabled) + { + return true; + } + var codeHashMap = GetBlockExecutedData(blockIndex); if (codeHashMap == null) return false; diff --git a/src/AElf.Kernel.CodeCheck/CodeCheckRequiredLogEventProcessor.cs b/src/AElf.Kernel.CodeCheck/CodeCheckRequiredLogEventProcessor.cs index 08085725af..4c84a307a8 100644 --- a/src/AElf.Kernel.CodeCheck/CodeCheckRequiredLogEventProcessor.cs +++ b/src/AElf.Kernel.CodeCheck/CodeCheckRequiredLogEventProcessor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using AElf.CSharp.Core.Extension; @@ -40,7 +41,6 @@ public override Task GetInterestedEventAsync(IChainContext chai public override async Task ProcessAsync(Block block, Dictionary> logEventsMap) { Logger.LogInformation("Start handling CodeCheckRequired log event."); - var blockHash = block.GetHash(); foreach (var events in logEventsMap) { var transactionResult = events.Key; @@ -49,28 +49,38 @@ public override async Task ProcessAsync(Block block, Dictionary l.Name == nameof(ProposalCreated)).NonIndexed) - .ProposalId; - - var code = eventData.Code.ToByteArray(); - var sendResult = await _codeCheckJobProcessor.SendAsync(new CodeCheckJob + try { - BlockHash = blockHash, - BlockHeight = block.Height, - ContractCode = code, - ContractCategory = eventData.Category, - IsSystemContract = eventData.IsSystemContract, - IsUserContract = eventData.IsUserContract, - CodeCheckProposalId = proposalId, - ProposedContractInputHash = eventData.ProposedContractInputHash - }); + var proposalId = ProposalCreated.Parser + .ParseFrom(transactionResult.Logs.First(l => l.Name == nameof(ProposalCreated)).NonIndexed) + .ProposalId; + + var code = eventData.Code.ToByteArray(); + var codeCheckJob = new CodeCheckJob + { + BlockHash = block.GetHash(), + BlockHeight = block.Height, + ContractCode = code, + ContractCategory = eventData.Category, + IsSystemContract = eventData.IsSystemContract, + IsUserContract = eventData.IsUserContract, + CodeCheckProposalId = proposalId, + ProposedContractInputHash = eventData.ProposedContractInputHash + }; + var sendResult = await _codeCheckJobProcessor.SendAsync(codeCheckJob); - if (!sendResult) + if (!sendResult) + { + Logger.LogError( + "Unable to perform code check. BlockHash: {BlockHash}, BlockHeight: {BlockHeight}, CodeHash: {CodeHash}, ProposalId: {ProposalId}", + block.GetHash().ToHex(), block.Height, HashHelper.ComputeFrom(code).ToHex(), + proposalId.ToHex()); + } + } + catch (Exception e) { - Logger.LogError( - "Unable to perform code check. BlockHash: {BlockHash}, BlockHeight: {BlockHeight}, CodeHash: {CodeHash}, ProposalId: {ProposalId}", - blockHash, block.Height, HashHelper.ComputeFrom(code).ToHex(), proposalId.ToHex()); + Logger.LogError("Error while processing CodeCheckRequired log event. {0}", e); + throw new CodeCheckJobException($"Error while processing CodeCheckRequired log event. {e}"); } } } diff --git a/src/AElf.Kernel.CodeCheck/Exception/CodeCheckJobException.cs b/src/AElf.Kernel.CodeCheck/Exception/CodeCheckJobException.cs new file mode 100644 index 0000000000..703de9fe62 --- /dev/null +++ b/src/AElf.Kernel.CodeCheck/Exception/CodeCheckJobException.cs @@ -0,0 +1,14 @@ +using System; + +namespace AElf.Kernel.CodeCheck; + +public class CodeCheckJobException : Exception +{ + public CodeCheckJobException() + { + } + + public CodeCheckJobException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/src/AElf.Kernel.Core/Blockchain/Domain/IInvalidTransactionResultManager.cs b/src/AElf.Kernel.Core/Blockchain/Domain/IInvalidTransactionResultManager.cs new file mode 100644 index 0000000000..9c29556ad1 --- /dev/null +++ b/src/AElf.Kernel.Core/Blockchain/Domain/IInvalidTransactionResultManager.cs @@ -0,0 +1,33 @@ +using System.Linq; +using AElf.Kernel.Blockchain.Infrastructure; +using AElf.Kernel.Infrastructure; + +namespace AElf.Kernel.Blockchain.Domain; + +public interface IInvalidTransactionResultManager +{ + Task AddInvalidTransactionResultAsync(InvalidTransactionResult transactionResult); + Task GetInvalidTransactionResultAsync(Hash transactionId); +} + +public class InvalidTransactionResultManager : IInvalidTransactionResultManager +{ + private readonly IBlockchainStore _invalidTransactionResultStore; + + public InvalidTransactionResultManager(IBlockchainStore invalidTransactionResultStore) + { + _invalidTransactionResultStore = invalidTransactionResultStore; + } + + public async Task AddInvalidTransactionResultAsync(InvalidTransactionResult transactionResult) + { + await _invalidTransactionResultStore.SetAsync(transactionResult.TransactionId.ToStorageKey(), transactionResult); + } + + + public async Task GetInvalidTransactionResultAsync(Hash transactionId) + { + return await _invalidTransactionResultStore.GetAsync(transactionId.ToStorageKey()); + } + +} \ No newline at end of file diff --git a/src/AElf.Kernel.Core/CoreKernelAElfModule.cs b/src/AElf.Kernel.Core/CoreKernelAElfModule.cs index 86df74b631..0dcf8b87bb 100644 --- a/src/AElf.Kernel.Core/CoreKernelAElfModule.cs +++ b/src/AElf.Kernel.Core/CoreKernelAElfModule.cs @@ -39,7 +39,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) services.AddStoreKeyPrefixProvide("ti"); services.AddStoreKeyPrefixProvide("tr"); services.AddStoreKeyPrefixProvide("vs"); - + services.AddStoreKeyPrefixProvide("ir"); services.AddTransient(typeof(IStateStore<>), typeof(StateStore<>)); services.AddSingleton(typeof(INotModifiedCachedStateStore<>), typeof(NotModifiedCachedStateStore<>)); diff --git a/src/AElf.Kernel.Core/Plugin/Application/IPluginContractProvider.cs b/src/AElf.Kernel.Core/Plugin/Application/IPluginContractProvider.cs new file mode 100644 index 0000000000..73f04c6d47 --- /dev/null +++ b/src/AElf.Kernel.Core/Plugin/Application/IPluginContractProvider.cs @@ -0,0 +1,6 @@ +namespace AElf.Kernel.Plugin.Application; + +public interface IPluginContractProvider +{ + string GetContractName(); +} \ No newline at end of file diff --git a/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs b/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs index ff1233b211..138d7853f3 100644 --- a/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs +++ b/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs @@ -8,21 +8,30 @@ namespace AElf.Kernel.FeeCalculation.Extensions; public static class TransactionResultExtensions { - public static Dictionary GetChargedTransactionFees(this TransactionResult transactionResult) + public static Dictionary> GetChargedTransactionFees( + this TransactionResult transactionResult) { return transactionResult.Logs .Where(l => l.Name == nameof(TransactionFeeCharged)) - .Select(l => TransactionFeeCharged.Parser.ParseFrom(l.NonIndexed)) - .GroupBy(fee => fee.Symbol, fee => fee.Amount) - .ToDictionary(g => g.Key, g => g.Sum()); + .GroupBy( + log => TransactionFeeCharged.Parser.ParseFrom(log.Indexed[0]).ChargingAddress, + log => TransactionFeeCharged.Parser.ParseFrom(log.NonIndexed)) + .ToDictionary(e => e.Key, + e => e + .GroupBy(fee => fee.Symbol, fee => fee.Amount) + .ToDictionary(g => g.Key, g => g.Sum())); } - public static Dictionary GetConsumedResourceTokens(this TransactionResult transactionResult) + public static Dictionary> GetConsumedResourceTokens(this TransactionResult transactionResult) { var relatedLogs = transactionResult.Logs.Where(l => l.Name == nameof(ResourceTokenCharged)).ToList(); - if (!relatedLogs.Any()) return new Dictionary(); + if (!relatedLogs.Any()) return new Dictionary>(); return relatedLogs.Select(l => ResourceTokenCharged.Parser.ParseFrom(l.NonIndexed)) - .ToDictionary(e => e.Symbol, e => e.Amount); + .GroupBy(g => g.ContractAddress) + .ToDictionary(e => e.Key, + e => e + .GroupBy(fee => fee.Symbol, fee => fee.Amount) + .ToDictionary(g => g.Key, g => g.Sum())); } public static Dictionary GetOwningResourceTokens(this TransactionResult transactionResult) diff --git a/src/AElf.Kernel.SmartContract.Shared/ISmartContractBridgeContext.cs b/src/AElf.Kernel.SmartContract.Shared/ISmartContractBridgeContext.cs index 5f119f84b5..8840c15b01 100644 --- a/src/AElf.Kernel.SmartContract.Shared/ISmartContractBridgeContext.cs +++ b/src/AElf.Kernel.SmartContract.Shared/ISmartContractBridgeContext.cs @@ -202,4 +202,24 @@ public StateOverSizeException(string message, Exception inner) : base(message, i protected StateOverSizeException(SerializationInfo info, StreamingContext context) : base(info, context) { } +} + +[Serializable] +public class StateKeyOverSizeException : SmartContractBridgeException +{ + public StateKeyOverSizeException() + { + } + + public StateKeyOverSizeException(string message) : base(message) + { + } + + public StateKeyOverSizeException(string message, Exception inner) : base(message, inner) + { + } + + protected StateKeyOverSizeException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } } \ No newline at end of file diff --git a/src/AElf.Kernel.SmartContract/HostSmartContractBridgeContext.cs b/src/AElf.Kernel.SmartContract/HostSmartContractBridgeContext.cs index 5f867b012d..3b723c3227 100644 --- a/src/AElf.Kernel.SmartContract/HostSmartContractBridgeContext.cs +++ b/src/AElf.Kernel.SmartContract/HostSmartContractBridgeContext.cs @@ -111,6 +111,11 @@ public void Initialize(ITransactionContext transactionContext) public async Task GetStateAsync(string key) { + if (key.Length > SmartContractConstants.StateKeyMaximumLength) + { + throw new StateKeyOverSizeException( + $"Length of state key {key} exceeds limit of {SmartContractConstants.StateKeyMaximumLength}."); + } return await _smartContractBridgeService.GetStateAsync( Self, key, CurrentHeight - 1, PreviousBlockHash); } diff --git a/src/AElf.Kernel.SmartContract/SmartContractConstants.cs b/src/AElf.Kernel.SmartContract/SmartContractConstants.cs index 0a6cb05208..9a210289ce 100644 --- a/src/AElf.Kernel.SmartContract/SmartContractConstants.cs +++ b/src/AElf.Kernel.SmartContract/SmartContractConstants.cs @@ -7,4 +7,7 @@ public class SmartContractConstants public const int ExecutionBranchThreshold = 15000; public const int StateSizeLimit = 128 * 1024; + + // The prefix `vs` occupies 2 lengths. + public const int StateKeyMaximumLength = 255 - 2; } \ No newline at end of file diff --git a/src/AElf.Kernel.TransactionPool/Application/IInvalidTransactionResultService.cs b/src/AElf.Kernel.TransactionPool/Application/IInvalidTransactionResultService.cs new file mode 100644 index 0000000000..0a1d13c0bf --- /dev/null +++ b/src/AElf.Kernel.TransactionPool/Application/IInvalidTransactionResultService.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using AElf.Kernel.Blockchain.Domain; +using AElf.Types; +using Volo.Abp.DependencyInjection; + +namespace AElf.Kernel.TransactionPool.Application; + + +public interface IInvalidTransactionResultService +{ + Task AddInvalidTransactionResultsAsync(InvalidTransactionResult transactionResult); + Task GetInvalidTransactionResultAsync(Hash transactionId); +} + +public class InvalidTransactionResultService : IInvalidTransactionResultService, ITransientDependency +{ + private readonly IInvalidTransactionResultManager _invalidTransactionResultManager; + + public InvalidTransactionResultService(IInvalidTransactionResultManager invalidTransactionResultManager) + { + _invalidTransactionResultManager = invalidTransactionResultManager; + } + + public async Task AddInvalidTransactionResultsAsync(InvalidTransactionResult transactionResult) + { + await _invalidTransactionResultManager.AddInvalidTransactionResultAsync(transactionResult); + } + + public async Task GetInvalidTransactionResultAsync(Hash transactionId) + { + return await _invalidTransactionResultManager.GetInvalidTransactionResultAsync(transactionId); + } +} \ No newline at end of file diff --git a/src/AElf.Kernel.TransactionPool/Handler/TransactionValidationStatusFailedEventHandler.cs b/src/AElf.Kernel.TransactionPool/Handler/TransactionValidationStatusFailedEventHandler.cs new file mode 100644 index 0000000000..414cc76036 --- /dev/null +++ b/src/AElf.Kernel.TransactionPool/Handler/TransactionValidationStatusFailedEventHandler.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using AElf.Kernel.TransactionPool.Application; +using AElf.Types; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; + +namespace AElf.Kernel.TransactionPool.Handler; + +public class TransactionValidationStatusFailedEventHandler : + ILocalEventHandler, + ITransientDependency +{ + private readonly IEnumerable _failStatus = new List + { + TransactionResultStatus.Failed, TransactionResultStatus.NodeValidationFailed, TransactionResultStatus.Conflict + }; + + private readonly IInvalidTransactionResultService _invalidTransactionResultService; + private readonly TransactionOptions _transactionOptions; + + public TransactionValidationStatusFailedEventHandler( + IOptionsMonitor transactionOptionsMonitor, + IInvalidTransactionResultService invalidTransactionResultService) + { + _invalidTransactionResultService = invalidTransactionResultService; + _transactionOptions = transactionOptionsMonitor.CurrentValue; + } + + public async Task HandleEventAsync(TransactionValidationStatusChangedEvent eventData) + { + if (!_failStatus.Contains(eventData.TransactionResultStatus)) return; + if (!_transactionOptions.StoreInvalidTransactionResultEnabled) return; + + // save to storage + await _invalidTransactionResultService.AddInvalidTransactionResultsAsync( + new InvalidTransactionResult + { + TransactionId = eventData.TransactionId, + Status = eventData.TransactionResultStatus, + Error = TakeErrorMessage(eventData.Error) + }); + } + + private string TakeErrorMessage(string transactionResultError) + { + if (string.IsNullOrWhiteSpace(transactionResultError)) + return null; + using var stringReader = new StringReader(transactionResultError); + return stringReader.ReadLine(); + } + +} \ No newline at end of file diff --git a/src/AElf.Kernel.TransactionPool/TransactionOptions.cs b/src/AElf.Kernel.TransactionPool/TransactionOptions.cs index 7314e9b7bc..b2dfbc08ea 100644 --- a/src/AElf.Kernel.TransactionPool/TransactionOptions.cs +++ b/src/AElf.Kernel.TransactionPool/TransactionOptions.cs @@ -19,4 +19,10 @@ public class TransactionOptions public bool EnableTransactionExecutionValidation { get; set; } = true; public static int BlockTransactionLimit { get; set; } = 512; + + /// + /// Configuration whether to save failed transaction results + /// + public bool StoreInvalidTransactionResultEnabled { get; set; } + } \ No newline at end of file diff --git a/src/AElf.Launcher/Startup.cs b/src/AElf.Launcher/Startup.cs index 1c293abd9a..42a4537bde 100644 --- a/src/AElf.Launcher/Startup.cs +++ b/src/AElf.Launcher/Startup.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.IO; using System.Linq; using AElf.Blockchains.MainChain; using AElf.Blockchains.SideChain; @@ -9,7 +10,8 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; +using Volo.Abp; +using Volo.Abp.Modularity.PlugIns; namespace AElf.Launcher; @@ -28,13 +30,23 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { var chainType = _configuration.GetValue("ChainType", ChainType.MainChain); + var pluginSourcesFolder = _configuration.GetValue("PluginSourcesFolder", Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "aelf", "plugins")); + Action optionsAction = options => + { + if (Directory.Exists(pluginSourcesFolder)) + { + options.PlugInSources.AddFolder(pluginSourcesFolder); + } + }; switch (chainType) { case ChainType.SideChain: - AddApplication(services); + services.AddApplication(optionsAction); break; - default: - AddApplication(services); + case ChainType.MainChain: + services.AddApplication(optionsAction); break; } @@ -56,11 +68,6 @@ public void ConfigureServices(IServiceCollection services) }); } - private static void AddApplication(IServiceCollection services) where T : IAbpModule - { - services.AddApplication(); - } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // ReSharper disable once UnusedMember.Global public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs b/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs index bbba632175..1446177873 100644 --- a/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs +++ b/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using AElf.Types; namespace AElf.WebApp.Application.Chain.Dto; @@ -6,7 +8,21 @@ public class CalculateTransactionFeeOutput { public bool Success { get; set; } + [Obsolete("This property is deprecated and will be removed in the next version. Use the TransactionFees instead.")] public Dictionary TransactionFee { get; set; } - + + [Obsolete("This property is deprecated and will be removed in the next version. Use the ResourceFees instead.")] public Dictionary ResourceFee { get; set; } + + public FeeDto TransactionFees { get; set; } + + public FeeDto ResourceFees { get; set; } + + public string Error { get; set; } +} + +public class FeeDto +{ + public string ChargingAddress { get; set; } + public Dictionary Fee { get; set; } } \ No newline at end of file diff --git a/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs b/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs index 01b8459811..f4836f8128 100644 --- a/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs +++ b/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs @@ -17,6 +17,7 @@ using Google.Protobuf.WellKnownTypes; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.EventBus.Local; using Volo.Abp.ObjectMapping; @@ -47,17 +48,21 @@ public class TransactionAppService : AElfAppService, ITransactionAppService private readonly ITransactionReadOnlyExecutionService _transactionReadOnlyExecutionService; private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider; private readonly IPlainTransactionExecutingService _plainTransactionExecutingService; + private readonly WebAppOptions _webAppOptions; + public TransactionAppService(ITransactionReadOnlyExecutionService transactionReadOnlyExecutionService, IBlockchainService blockchainService, IObjectMapper objectMapper, ITransactionResultStatusCacheProvider transactionResultStatusCacheProvider, - IPlainTransactionExecutingService plainTransactionExecutingService) + IPlainTransactionExecutingService plainTransactionExecutingService, + IOptionsMonitor webAppOptions) { _transactionReadOnlyExecutionService = transactionReadOnlyExecutionService; _blockchainService = blockchainService; _objectMapper = objectMapper; _transactionResultStatusCacheProvider = transactionResultStatusCacheProvider; _plainTransactionExecutingService = plainTransactionExecutingService; + _webAppOptions = webAppOptions.CurrentValue; LocalEventBus = NullLocalEventBus.Instance; Logger = NullLogger.Instance; @@ -293,17 +298,39 @@ private async Task EstimateTransactionFee(Transac executionReturnSets.FirstOrDefault()?.TransactionResult.GetChargedTransactionFees(); var resourceFees = executionReturnSets.FirstOrDefault()?.TransactionResult.GetConsumedResourceTokens(); result.Success = true; - result.TransactionFee = transactionFees; - result.ResourceFee = resourceFees; + result.TransactionFee = GetFeeValue(transactionFees); + result.ResourceFee = GetFeeValue(resourceFees); + result.TransactionFees = GetFee(transactionFees); + result.ResourceFees = GetFee(resourceFees); } else { result.Success = false; + result.Error = TransactionErrorResolver.TakeErrorMessage( + executionReturnSets.FirstOrDefault()?.TransactionResult.Error, _webAppOptions.IsDebugMode); } return result; } + private Dictionary GetFeeValue(Dictionary> feeMap) + { + return feeMap?.SelectMany(pair => pair.Value) + .GroupBy(p => p.Key) + .ToDictionary(g => g.Key, g => g.Sum(pair => pair.Value)); + } + + private FeeDto GetFee(Dictionary> feeMap) + { + var fee = feeMap?.Select(f => new FeeDto + { + ChargingAddress = f.Key.ToBase58(), + Fee = f.Value + }).FirstOrDefault(); + + return fee; + } + private async Task PublishTransactionsAsync(string[] rawTransactions) { var txIds = new string[rawTransactions.Length]; diff --git a/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs b/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs index 08552f42e5..483dbb4366 100644 --- a/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs +++ b/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs @@ -7,6 +7,7 @@ using AElf.Kernel.Blockchain.Application; using AElf.Kernel.Blockchain.Domain; using AElf.Kernel.SmartContract.Application; +using AElf.Kernel.TransactionPool; using AElf.Types; using AElf.WebApp.Application.Chain.Dto; using AElf.WebApp.Application.Chain.Infrastructure; @@ -39,6 +40,7 @@ public class TransactionResultAppService : AElfAppService, ITransactionResultApp private readonly ITransactionResultProxyService _transactionResultProxyService; private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider; private readonly WebAppOptions _webAppOptions; + private readonly TransactionOptions _transactionOptions; public TransactionResultAppService(ITransactionResultProxyService transactionResultProxyService, ITransactionManager transactionManager, @@ -46,7 +48,7 @@ public TransactionResultAppService(ITransactionResultProxyService transactionRes ITransactionReadOnlyExecutionService transactionReadOnlyExecutionService, IObjectMapper objectMapper, ITransactionResultStatusCacheProvider transactionResultStatusCacheProvider, - IOptionsMonitor optionsSnapshot) + IOptionsMonitor optionsSnapshot, IOptionsMonitor transactionOptions) { _transactionResultProxyService = transactionResultProxyService; _transactionManager = transactionManager; @@ -54,6 +56,7 @@ public TransactionResultAppService(ITransactionResultProxyService transactionRes _transactionReadOnlyExecutionService = transactionReadOnlyExecutionService; _objectMapper = objectMapper; _transactionResultStatusCacheProvider = transactionResultStatusCacheProvider; + _transactionOptions = transactionOptions.CurrentValue; _webAppOptions = optionsSnapshot.CurrentValue; Logger = NullLogger.Instance; @@ -88,23 +91,36 @@ public async Task GetTransactionResultAsync(string transac output.Transaction = _objectMapper.Map(transaction); output.TransactionSize = transaction?.CalculateSize() ?? 0; - if (transactionResult.Status == TransactionResultStatus.NotExisted) + if (transactionResult.Status != TransactionResultStatus.NotExisted) { - var validationStatus = - _transactionResultStatusCacheProvider.GetTransactionResultStatus(transactionIdHash); - if (validationStatus != null) - { - output.Status = validationStatus.TransactionResultStatus.ToString().ToUpper(); - output.Error = - TransactionErrorResolver.TakeErrorMessage(validationStatus.Error, _webAppOptions.IsDebugMode); - } + await FormatTransactionParamsAsync(output.Transaction, transaction.Params); + return output; + } + var validationStatus = _transactionResultStatusCacheProvider.GetTransactionResultStatus(transactionIdHash); + if (validationStatus != null) + { + output.Status = validationStatus.TransactionResultStatus.ToString().ToUpper(); + output.Error = + TransactionErrorResolver.TakeErrorMessage(validationStatus.Error, _webAppOptions.IsDebugMode); return output; } - - await FormatTransactionParamsAsync(output.Transaction, transaction.Params); - + + if (_transactionOptions.StoreInvalidTransactionResultEnabled) + { + var failedTransactionResult = + await _transactionResultProxyService.InvalidTransactionResultService.GetInvalidTransactionResultAsync( + transactionIdHash); + if (failedTransactionResult != null) + { + output.Status = failedTransactionResult.Status.ToString().ToUpper(); + output.Error = failedTransactionResult.Error; + return output; + } + } + return output; + } /// diff --git a/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs b/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs index 5ebfcc8598..9d13296930 100644 --- a/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs +++ b/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs @@ -7,17 +7,21 @@ public interface ITransactionResultProxyService { ITransactionPoolService TransactionPoolService { get; } ITransactionResultQueryService TransactionResultQueryService { get; } + IInvalidTransactionResultService InvalidTransactionResultService { get; } } public class TransactionResultProxyService : ITransactionResultProxyService { public TransactionResultProxyService(ITransactionPoolService transactionPoolService, - ITransactionResultQueryService transactionResultQueryService) + ITransactionResultQueryService transactionResultQueryService, + IInvalidTransactionResultService invalidTransactionResultService) { TransactionPoolService = transactionPoolService; TransactionResultQueryService = transactionResultQueryService; + InvalidTransactionResultService = invalidTransactionResultService; } public ITransactionPoolService TransactionPoolService { get; set; } public ITransactionResultQueryService TransactionResultQueryService { get; set; } + public IInvalidTransactionResultService InvalidTransactionResultService { get; } } \ No newline at end of file diff --git a/test/AElf.Contracts.Association.Tests/AssociationContractTests.cs b/test/AElf.Contracts.Association.Tests/AssociationContractTests.cs index 0da11d0fa3..89ec1025ed 100644 --- a/test/AElf.Contracts.Association.Tests/AssociationContractTests.cs +++ b/test/AElf.Contracts.Association.Tests/AssociationContractTests.cs @@ -169,6 +169,8 @@ public async Task Get_Proposal_Test() getProposal.Output.OrganizationAddress.ShouldBe(organizationAddress); getProposal.Output.ToAddress.ShouldBe(TokenContractAddress); getProposal.Output.Params.ShouldBe(transferInput.ToByteString()); + getProposal.Output.Title.ShouldNotBeNullOrEmpty(); + getProposal.Output.Description.ShouldNotBeNullOrEmpty(); } } @@ -1231,7 +1233,9 @@ private async Task CreateProposalAsync(ECKeyPair proposalKeyPair, Address ToAddress = TokenContractAddress, Params = transferInput.ToByteString(), ExpiredTime = BlockTimeProvider.GetBlockTime().AddDays(2), - OrganizationAddress = organizationAddress + OrganizationAddress = organizationAddress, + Title = "Token Transfer", + Description = "Transfer 100 ELF to Reviewer1's address", }; var proposal = await associationContractStub.CreateProposal.SendAsync(createProposalInput); var proposalCreated = ProposalCreated.Parser.ParseFrom(proposal.TransactionResult.Logs diff --git a/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs b/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs index cbe677415b..96657c3cd2 100644 --- a/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs +++ b/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs @@ -383,11 +383,11 @@ public async Task CheckTreasuryProfitsDistribution_Test() })).Balance; var profitTester = GetProfitContractTester(VoterKeyPairs[0]); - var profitAmount = (await profitTester.GetProfitAmount.CallAsync(new GetProfitAmountInput + var profitAmount = (await profitTester.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput { SchemeId = ProfitItemsIds[ProfitType.CitizenWelfare], Symbol = EconomicContractsTestConstants.NativeTokenSymbol - })).Value; + })).OneTimeClaimableProfitAmount; profitAmount.ShouldBeGreaterThan(0); var profitResult = await profitTester.ClaimProfits.SendAsync(new ClaimProfitsInput @@ -636,11 +636,11 @@ private async Task GetProfitAmount(ProfitType type) break; } - return (await stub.GetProfitAmount.CallAsync(new GetProfitAmountInput + return (await stub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput { SchemeId = ProfitItemsIds[type], Symbol = EconomicContractsTestConstants.NativeTokenSymbol - })).Value; + })).OneTimeClaimableProfitAmount; } private async Task GetReleasedAmount() diff --git a/test/AElf.Contracts.Genesis.Tests/GenesisContractAuthTest.cs b/test/AElf.Contracts.Genesis.Tests/GenesisContractAuthTest.cs index 3962b989b8..a5d5b5bde9 100644 --- a/test/AElf.Contracts.Genesis.Tests/GenesisContractAuthTest.cs +++ b/test/AElf.Contracts.Genesis.Tests/GenesisContractAuthTest.cs @@ -1372,6 +1372,67 @@ public async Task ChangeCodeCheckController_Test() } } + [Fact] + public async Task SetCodeCheckProposalExpirationTime_Test() + { + var createOrganizationResult = await Tester.ExecuteContractWithMiningAsync(ParliamentAddress, + nameof(ParliamentContractImplContainer.ParliamentContractImplStub.CreateOrganization), + new CreateOrganizationInput + { + ProposalReleaseThreshold = new ProposalReleaseThreshold + { + MinimalApprovalThreshold = 1000, + MinimalVoteThreshold = 1000 + } + }); + var organizationAddress = Address.Parser.ParseFrom(createOrganizationResult.ReturnValue); + + var defaultTime = await Tester.CallContractMethodAsync(BasicContractZeroAddress, + nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub.GetCodeCheckProposalExpirationTimePeriod), + new Empty()); + var proposalExpirationTime = Int32Value.Parser.ParseFrom(defaultTime); + Assert.True(proposalExpirationTime.Value == 900); + + var byteResult = await Tester.CallContractMethodAsync(BasicContractZeroAddress, + nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub.GetContractDeploymentController), + new Empty()); + var contractDeploymentController = AuthorityInfo.Parser.ParseFrom(byteResult); + + const string methodName = + nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub.SetCodeCheckProposalExpirationTimePeriod); + { + var proposalId = await CreateProposalAsync(Tester, ParliamentAddress, + organizationAddress, methodName, + new Int32Value + { + Value = 86400 + } + ); + await ApproveWithMinersAsync(Tester, ParliamentAddress, proposalId); + var txResult = await ReleaseProposalAsync(Tester, ParliamentAddress, proposalId); + txResult.Status.ShouldBe(TransactionResultStatus.Failed); + txResult.Error.ShouldContain("Unauthorized behavior."); + } + { + var proposalId = await CreateProposalAsync(Tester, ParliamentAddress, + contractDeploymentController.OwnerAddress, methodName, + new Int32Value + { + Value = 86400 + }); + await ApproveWithMinersAsync(Tester, ParliamentAddress, proposalId); + var txResult2 = await ReleaseProposalAsync(Tester, ParliamentAddress, proposalId); + txResult2.Status.ShouldBe(TransactionResultStatus.Mined); + + byteResult = await Tester.CallContractMethodAsync(BasicContractZeroAddress, + nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub + .GetCodeCheckProposalExpirationTimePeriod), + new Empty()); + var newProposalExpirationTime = Int32Value.Parser.ParseFrom(byteResult); + Assert.True(newProposalExpirationTime.Value == 86400); + } + } + [Fact] public async Task SetContractProposalExpirationTime_Test() { @@ -1386,18 +1447,18 @@ public async Task SetContractProposalExpirationTime_Test() } }); var organizationAddress = Address.Parser.ParseFrom(createOrganizationResult.ReturnValue); - + var defaultTime = await Tester.CallContractMethodAsync(BasicContractZeroAddress, nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub.GetContractProposalExpirationTimePeriod), new Empty()); var contractProposalExpirationTime = Int32Value.Parser.ParseFrom(defaultTime); Assert.True(contractProposalExpirationTime.Value == 259200); - + var byteResult = await Tester.CallContractMethodAsync(BasicContractZeroAddress, nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub.GetContractDeploymentController), new Empty()); var contractDeploymentController = AuthorityInfo.Parser.ParseFrom(byteResult); - + const string methodName = nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub.SetContractProposalExpirationTimePeriod); { @@ -1422,9 +1483,10 @@ public async Task SetContractProposalExpirationTime_Test() await ApproveWithMinersAsync(Tester, ParliamentAddress, proposalId); var txResult2 = await ReleaseProposalAsync(Tester, ParliamentAddress, proposalId); txResult2.Status.ShouldBe(TransactionResultStatus.Mined); - + byteResult = await Tester.CallContractMethodAsync(BasicContractZeroAddress, - nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub.GetContractProposalExpirationTimePeriod), + nameof(BasicContractZeroImplContainer.BasicContractZeroImplStub + .GetContractProposalExpirationTimePeriod), new Empty()); var newContractProposalExpirationTime = Int32Value.Parser.ParseFrom(byteResult); Assert.True(newContractProposalExpirationTime.Value == 86400); diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs index 35f84f7154..8729acf7c1 100644 --- a/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs @@ -48,6 +48,24 @@ public async Task ACS2_GetResourceInfo_TransferFrom_Test() result.NonParallelizable.ShouldBeFalse(); result.WritePaths.Count.ShouldBeGreaterThan(0); } + + [Fact] + public async Task ACS2_GetResourceInfo_TransferFrom_NFT_Test() + { + var transaction = GenerateTokenTransaction(Accounts[0].Address, nameof(TokenContractStub.TransferFrom), + new TransferFromInput + { + Amount = 100, + Symbol = "ABC-1", + From = Accounts[1].Address, + To = Accounts[2].Address, + Memo = "Test get resource" + }); + + var result = await Acs2BaseStub.GetResourceInfo.CallAsync(transaction); + result.NonParallelizable.ShouldBeFalse(); + result.WritePaths.Count.ShouldBeGreaterThan(0); + } private async Task
GetDefaultParliamentAddressAsync() { @@ -179,7 +197,7 @@ await TokenContractStubDelegate3.SetTransactionFeeDelegateInfos.SendAsync(new Se var result = await Acs2BaseStub.GetResourceInfo.CallAsync(transaction); result.NonParallelizable.ShouldBeFalse(); - result.WritePaths.Count.ShouldBe(9); + result.WritePaths.Count.ShouldBe(10); } [Fact] diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs index 24055b432b..9c2dfab8a2 100644 --- a/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs @@ -17,6 +17,7 @@ public static class NftCollectionMetaFields public static string BaseUriKey = "__nft_base_uri"; public static string NftType = "__nft_type"; public const string IsItemIdReuseKey = "__nft_is_item_id_reuse"; + public const string NftCreateChainIdExternalInfoKey = "__nft_create_chain_id"; } public static class NftInfoMetaFields @@ -184,6 +185,47 @@ private async Task> CreateNftCollectionAndNft(bool reuseItemId = tr return symbols; } + private async Task CreateNftFailed() + { + var collectionInfo = new TokenInfo + { + Symbol = NftCollection1155Info.Symbol, + TokenName = NftCollection1155Info.TokenName, + TotalSupply = NftCollection1155Info.TotalSupply, + Decimals = NftCollection1155Info.Decimals, + Issuer = NftCollection1155Info.Issuer, + IssueChainId = NftCollection1155Info.IssueChainId, + ExternalInfo = new ExternalInfo() + { + Value = + { + { + NftCollectionMetaFields.NftCreateChainIdExternalInfoKey, + "1234" + } + } + }, + Owner = NftCollection1155Info.Issuer + }; + var createCollectionRes = await CreateNftCollectionAsync(collectionInfo); + createCollectionRes.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + var createNft2Res = await TokenContractStub.Create.SendWithExceptionAsync(new CreateInput + { + Symbol = $"{collectionInfo.Symbol}{Nft1155Info.Symbol}", + TokenName = Nft1155Info.TokenName, + TotalSupply = Nft1155Info.TotalSupply, + Decimals = Nft1155Info.Decimals, + Issuer = Nft1155Info.Issuer, + IsBurnable = Nft1155Info.IsBurnable, + IssueChainId = Nft1155Info.IssueChainId, + ExternalInfo = Nft1155Info.ExternalInfo, + Owner = Nft1155Info.Issuer + }); + createNft2Res.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + createNft2Res.TransactionResult.Error.Contains("NFT create ChainId must be collection's NFT create chainId") + .ShouldBeTrue(); + } + private void AssertTokenEqual(TokenCreated log, TokenInfo input) { Assert.Equal(log.TokenName, input.TokenName); @@ -204,6 +246,12 @@ public async Task MultiTokenContract_Create_1155Nft_Test() await CreateNftCollectionAndNft(); } + [Fact(DisplayName = "[MultiToken_Nft] Create 1155 nfts failed.")] + public async Task MultiTokenContract_Create_1155Nft_failed_Test() + { + await CreateNftFailed(); + } + [Fact(DisplayName = "[MultiToken_Nft] Create 721 nfts.")] public async Task MultiTokenContract_Create_721Nft_Test() { @@ -214,27 +262,26 @@ public async Task MultiTokenContract_Create_721Nft_Test() public async Task MultiTokenContract_Create_NFTCollection_Input_Check_Test() { var input = NftCollection721Info; - // Decimals check + // Symbol check { - var result = await CreateMutiTokenWithExceptionAsync(TokenContractStub, new CreateInput + var seedInput = BuildSeedCreateInput( new CreateInput { - Symbol = $"{input.Symbol}0", + Symbol = "ABC123", TokenName = input.TokenName, TotalSupply = input.TotalSupply, - Decimals = 8, + Decimals = input.Decimals, Issuer = input.Issuer, IssueChainId = input.IssueChainId, ExternalInfo = input.ExternalInfo, Owner = input.Owner }); - result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); - result.TransactionResult.Error.ShouldContain("NFT's decimals must be 0"); - } - // Symbol check - { - var seedInput = BuildSeedCreateInput( new CreateInput + + var result = await TokenContractStub.Create.SendAsync(seedInput); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + + seedInput = BuildSeedCreateInput(new CreateInput { - Symbol = "ABC123", + Symbol = "ABC123()", TokenName = input.TokenName, TotalSupply = input.TotalSupply, Decimals = input.Decimals, @@ -243,8 +290,8 @@ public async Task MultiTokenContract_Create_NFTCollection_Input_Check_Test() ExternalInfo = input.ExternalInfo, Owner = input.Owner }); - - var result = await TokenContractStub.Create.SendWithExceptionAsync(seedInput);; + + result = await TokenContractStub.Create.SendWithExceptionAsync(seedInput); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); result.TransactionResult.Error.ShouldContain("Invalid Symbol input"); } @@ -289,21 +336,6 @@ public async Task MultiTokenContract_Create_NFT_Input_Check_Test() await CreateNftCollectionAsync(NftCollection721Info); var input = Nft721Info; - // Decimals check - { - var result = await CreateMutiTokenWithExceptionAsync(TokenContractStub, new CreateInput - { - Symbol = "GHJ-0", - TokenName = input.TokenName, - TotalSupply = input.TotalSupply, - Decimals = 8, - Issuer = input.Issuer, - IssueChainId = input.IssueChainId, - ExternalInfo = input.ExternalInfo - }); - result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); - result.TransactionResult.Error.ShouldContain("NFT's decimals must be 0"); - } // Symbol check { var result = await CreateSeedNftWithExceptionAsync(TokenContractStub, new CreateInput @@ -378,7 +410,7 @@ public async Task MultiTokenContract_Create_NFT_Input_Check_Test() Owner = input.Owner }); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); - result.TransactionResult.Error.ShouldContain("NFT create ChainId must be collection's issue chainId"); + result.TransactionResult.Error.ShouldContain("NFT issue ChainId must be collection's issue chainId"); } } diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/SymbolValidationTest.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/SymbolValidationTest.cs new file mode 100644 index 0000000000..3d3f94dd9a --- /dev/null +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/SymbolValidationTest.cs @@ -0,0 +1,23 @@ +using System.Text.RegularExpressions; +using Shouldly; +using Xunit; + +namespace AElf.Contracts.MultiToken; + +public class SymbolValidationTest +{ + private const string RegexPattern = "^[a-zA-Z0-9]+(-[0-9]+)?$"; + + [Theory] + [InlineData("ELF", true)] + [InlineData("ELF-", false)] + [InlineData("ABC-123", true)] + [InlineData("abc-1", true)] + [InlineData("ABC-ABC", false)] + [InlineData("ABC--", false)] + [InlineData("121-1", true)] + public void SymbolValidation(string symbol, bool isValid) + { + Regex.IsMatch(symbol, RegexPattern).ShouldBe(isValid); + } +} \ No newline at end of file diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenAliasTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenAliasTests.cs new file mode 100644 index 0000000000..d95e92816a --- /dev/null +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenAliasTests.cs @@ -0,0 +1,379 @@ +using System.Threading.Tasks; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; + +namespace AElf.Contracts.MultiToken; + +public partial class MultiTokenContractTests +{ + public const string TokenAliasExternalInfoKey = "aelf_token_alias"; + + [Fact] + public async Task SetTokenAlias_NFTCollection_Test() + { + var symbols = await CreateNftCollectionAndNft(); + await TokenContractStub.SetSymbolAlias.SendAsync(new SetSymbolAliasInput + { + Symbol = symbols[1], + Alias = "TP" + }); + + { + // Check TokenInfo of NFT Collection. + var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = symbols[0] + }); + tokenInfo.ExternalInfo.Value.ContainsKey(TokenAliasExternalInfoKey); + tokenInfo.ExternalInfo.Value[TokenAliasExternalInfoKey].ShouldBe("{\"TP-31175\":\"TP\"}"); + } + + { + // Check TokenInfo of NFT Item. + var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = "TP" + }); + tokenInfo.Symbol.ShouldBe(symbols[1]); + } + + { + // Check alias. + var alias = await TokenContractStub.GetTokenAlias.CallAsync(new StringValue { Value = "TP-31175" }); + alias.Value.ShouldBe("TP"); + } + + { + var alias = await TokenContractStub.GetSymbolByAlias.CallAsync(new StringValue { Value = "TP" }); + alias.Value.ShouldBe("TP-31175"); + } + } + + [Fact] + public async Task SetTokenAlias_NFTCollection_CollectionSymbol_Test() + { + await CreateNftCollectionAndNft(); + await TokenContractStub.SetSymbolAlias.SendAsync(new SetSymbolAliasInput + { + Symbol = "TP-0", + Alias = "TP" + }); + + { + // Check TokenInfo of NFT Collection. + var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = "TP-0" + }); + tokenInfo.ExternalInfo.Value.ContainsKey(TokenAliasExternalInfoKey); + tokenInfo.ExternalInfo.Value[TokenAliasExternalInfoKey].ShouldBe("{\"TP-0\":\"TP\"}"); + } + + { + // Check TokenInfo of NFT Item. + var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = "TP" + }); + tokenInfo.Symbol.ShouldBe("TP-0"); + } + + { + // Check alias. + var alias = await TokenContractStub.GetTokenAlias.CallAsync(new StringValue { Value = "TP-0" }); + alias.Value.ShouldBe("TP"); + } + + { + var alias = await TokenContractStub.GetSymbolByAlias.CallAsync(new StringValue { Value = "TP" }); + alias.Value.ShouldBe("TP-0"); + } + } + + [Fact] + public async Task SetTokenAlias_FT_Test() + { + await CreateNormalTokenAsync(); + + // Set token alias for FT. + var result = await TokenContractStub.SetSymbolAlias.SendWithExceptionAsync(new SetSymbolAliasInput + { + Symbol = AliceCoinTokenInfo.Symbol, + }); + result.TransactionResult.Error.ShouldContain("Token alias can only be set for NFT Item."); + } + + [Fact] + public async Task CreateTokenWithAlias_Test() + { + var createCollectionResult = await CreateNftCollectionAsync(NftCollection1155WithAliasInfo); + createCollectionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + + await CreateNftAsync(NftCollection1155WithAliasInfo.Symbol, Nft721Info); + + { + // Check alias. + var alias = await TokenContractStub.GetTokenAlias.CallAsync(new StringValue { Value = "TP-31175" }); + alias.Value.ShouldBe("TP"); + } + + { + // Check TokenInfo of NFT Item. + var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = "TP" + }); + tokenInfo.Symbol.ShouldBe("TP-31175"); + } + } + + [Fact] + public async Task CreateTokenWithAlias_FT_Test() + { + var createInput = new CreateInput + { + Symbol = AliceCoinTokenInfo.Symbol, + TokenName = AliceCoinTokenInfo.TokenName, + TotalSupply = AliceCoinTokenInfo.TotalSupply, + Decimals = AliceCoinTokenInfo.Decimals, + Issuer = AliceCoinTokenInfo.Issuer, + Owner = AliceCoinTokenInfo.Issuer, + IsBurnable = AliceCoinTokenInfo.IsBurnable, + LockWhiteList = + { + BasicFunctionContractAddress, + OtherBasicFunctionContractAddress, + TokenConverterContractAddress, + TreasuryContractAddress + }, + ExternalInfo = new ExternalInfo + { + Value = + { + { TokenAliasExternalInfoKey, "{\"ALICE-111\":\"ALICE\"}" } + } + } + }; + await CreateSeedNftAsync(TokenContractStub, createInput); + var result = await TokenContractStub.Create.SendWithExceptionAsync(createInput); + result.TransactionResult.Error.ShouldContain("Token alias can only be set for NFT Item."); + } + + [Fact] + public async Task TransferViaAlias_Test() + { + await CreateTokenWithAlias_Test(); + + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "TP-31175", + Amount = 1, + To = DefaultAddress + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "TP" + }); + balance.Balance.ShouldBe(1); + } + + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + // Transfer via alias. + Symbol = "TP", + Amount = 1, + To = User1Address + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = User1Address, + Symbol = "TP" + }); + balance.Balance.ShouldBe(1); + } + } + + [Fact] + public async Task ApproveAndTransferFromViaAlias_Test() + { + await CreateTokenWithAlias_Test(); + + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "TP-31175", + Amount = 1, + To = DefaultAddress + }); + + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Symbol = "TP", + Amount = 1, + Spender = User1Address + }); + + await TokenContractStubUser.TransferFrom.SendAsync(new TransferFromInput + { + Symbol = "TP", + Amount = 1, + From = DefaultAddress, + To = User2Address, + }); + + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = User2Address, + Symbol = "TP" + }); + balance.Balance.ShouldBe(1); + } + } + + [Fact] + public async Task GetBalanceOfNotExistToken_Test() + { + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = User2Address, + Symbol = "TP" + }); + balance.Balance.ShouldBe(0); + } + + [Fact] + public async Task GetAllowanceOfNotExistToken_Test() + { + var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = User2Address, + Symbol = "TP", + Spender = DefaultAddress + }); + allowance.Allowance.ShouldBe(0); + } + + [Fact] + public async Task BatchApproveWithAlias_Test() + { + await SetTokenAlias_NFTCollection_Test(); + await CreateTokenAndIssue(); + var approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 2000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = "TP", + Amount = 1000L, + Spender = OtherBasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 5000L, + Spender = TreasuryContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = BasicFunctionContractAddress, + Symbol = SymbolForTest + }); + basicAllowanceOutput.Allowance.ShouldBe(2000L); + var otherBasicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = OtherBasicFunctionContractAddress, + Symbol = "TP" + }); + otherBasicAllowanceOutput.Allowance.ShouldBe(1000L); + var treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = TreasuryContractAddress, + Symbol = SymbolForTest + }); + treasuryAllowanceOutput.Allowance.ShouldBe(5000L); + + approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = "TP", + Amount = 1000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 3000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 3000L, + Spender = TreasuryContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined); + basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = BasicFunctionContractAddress, + Symbol = SymbolForTest + }); + basicAllowanceOutput.Allowance.ShouldBe(3000L); + + treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = TreasuryContractAddress, + Symbol = SymbolForTest + }); + treasuryAllowanceOutput.Allowance.ShouldBe(3000L); + } + + private TokenInfo NftCollection1155WithAliasInfo => new() + { + Symbol = "TP-", + TokenName = "Trump Digital Trading Cards #1155", + TotalSupply = TotalSupply, + Decimals = 0, + Issuer = DefaultAddress, + IssueChainId = _chainId, + ExternalInfo = new ExternalInfo + { + Value = + { + { + NftCollectionMetaFields.ImageUrlKey, + "https://i.seadn.io/gcs/files/0f5cdfaaf687de2ebb5834b129a5bef3.png?auto=format&w=3840" + }, + { NftCollectionMetaFields.NftType, NftType }, + { TokenAliasExternalInfoKey, "{\"TP-31175\":\"TP\"}" } + } + } + }; +} \ No newline at end of file diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs index b4aa6e54d7..201e56d6e6 100644 --- a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs @@ -171,6 +171,145 @@ public async Task MultiTokenContract_Approve_ContractAddress_Test() basicAllowanceOutput.Allowance.ShouldBe(2000L); } + [Fact(DisplayName = "[MultiToken] BatchApprove token to Contract")] + public async Task MultiTokenContract_BatchApprove_ContractAddress_Test() + { + await CreateTokenAndIssue(); + var approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 2000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 1000L, + Spender = OtherBasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 5000L, + Spender = TreasuryContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = BasicFunctionContractAddress, + Symbol = SymbolForTest + }); + basicAllowanceOutput.Allowance.ShouldBe(2000L); + var otherBasicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = OtherBasicFunctionContractAddress, + Symbol = SymbolForTest + }); + otherBasicAllowanceOutput.Allowance.ShouldBe(1000L); + var treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = TreasuryContractAddress, + Symbol = SymbolForTest + }); + treasuryAllowanceOutput.Allowance.ShouldBe(5000L); + + approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 1000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 3000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 3000L, + Spender = TreasuryContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined); + basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = BasicFunctionContractAddress, + Symbol = SymbolForTest + }); + basicAllowanceOutput.Allowance.ShouldBe(3000L); + + treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = TreasuryContractAddress, + Symbol = SymbolForTest + }); + treasuryAllowanceOutput.Allowance.ShouldBe(3000L); + } + + [Fact] + public async Task MultiTokenContract_SetMaximumBatchApproveCount_Test() + { + var result = await TokenContractStub.SetMaxBatchApproveCount.SendWithExceptionAsync(new Int32Value + { + Value = 1 + }); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + result.TransactionResult.Error.ShouldContain("Unauthorized behavior"); + var maximumBatchApproveCountOutput = await TokenContractStub.GetMaxBatchApproveCount.CallAsync(new Empty()); + maximumBatchApproveCountOutput.Value.ShouldBe(100); + var defaultParliament = await ParliamentContractStub.GetDefaultOrganizationAddress.CallAsync(new Empty()); + var proposalId = await CreateProposalAsync(TokenContractAddress, + defaultParliament, nameof(TokenContractStub.SetMaxBatchApproveCount), + new Int32Value + { + Value = 1 + }); + await ApproveWithMinersAsync(proposalId); + await ParliamentContractStub.Release.SendAsync(proposalId); + maximumBatchApproveCountOutput = await TokenContractStub.GetMaxBatchApproveCount.CallAsync(new Empty()); + maximumBatchApproveCountOutput.Value.ShouldBe(1); + await CreateTokenAndIssue(); + var approveBasisResult = (await TokenContractStub.BatchApprove.SendWithExceptionAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 2000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 1000L, + Spender = OtherBasicFunctionContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Failed); + approveBasisResult.Error.ShouldContain("Exceeds the max batch approve count"); + } + [Fact(DisplayName = "[MultiToken] Approve token out of owner's balance")] public async Task MultiTokenContract_Approve_OutOfAmount_Test() { @@ -365,6 +504,452 @@ await TreasuryContractStub.Donate.SendAsync(new DonateInput afterTransferFromBalance.Balance.ShouldBe(beforeTransferFromBalance.Balance.Sub(transferAmount)); } + private async Task CreateNft() + { + await CreateMutiTokenAsync(TokenContractStub, new CreateInput + { + TokenName = "Test", + TotalSupply = TotalSupply, + Decimals = 0, + Issuer = DefaultAddress, + Owner = DefaultAddress, + IssueChainId = _chainId, + Symbol = "ABC-0" + }); + await TokenContractStub.Create.SendAsync(new CreateInput + { + TokenName = "Test", + TotalSupply = TotalSupply, + Decimals = 0, + Issuer = DefaultAddress, + Owner = DefaultAddress, + IssueChainId = _chainId, + Symbol = "ABC-1" + }); + } + [Fact] + public async Task MultiTokenContract_TransferFrom_Nft_Global_Test() + { + await CreateNft(); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ABC-1", + Amount = 100, + To = DefaultAddress, + Memo = "test" + }); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ABC-1", + Amount = 200, + To = User1Address, + Memo = "test" + }); + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ABC-1" + }); + balance.Balance.ShouldBe(100); + balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = User1Address, + Symbol = "ABC-1" + }); + balance.Balance.ShouldBe(200); + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Amount = 1000, + Symbol = "*", + Spender = User1Address + }); + + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Amount = 1, + Symbol = "ABC-*", + Spender = User1Address + }); + var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ABC-1" + }); + allowance.Allowance.ShouldBe(0); + allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ELF" + }); + allowance.Allowance.ShouldBe(0); + { + var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ABC-1" + }); + realAllowance.Allowance.ShouldBe(1000); + } + { + var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ELF" + }); + realAllowance.Allowance.ShouldBe(1000); + } + var user1Stub = + GetTester(TokenContractAddress, User1KeyPair); + var result2 = await user1Stub.TransferFrom.SendAsync(new TransferFromInput + { + Amount = 50, + From = DefaultAddress, + Memo = "test", + Symbol = "ABC-1", + To = User1Address + }); + result2.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + { + var realAllowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ABC-1" + }); + realAllowance.Allowance.ShouldBe(0); + } + allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ABC-1" + }); + allowance.Allowance.ShouldBe(1000-50); + balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ABC-1" + }); + balance.Balance.ShouldBe(50); + balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = User1Address, + Symbol = "ABC-1" + }); + balance.Balance.ShouldBe(250); + } + + [Fact] + public async Task MultiTokenContract_TransferFrom_Nft_Collection_Test() + { + await CreateNft(); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ABC-1", + Amount = 100, + To = DefaultAddress, + Memo = "test" + }); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ABC-1", + Amount = 200, + To = User1Address, + Memo = "test" + }); + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Amount = 20, + Symbol = "*", + Spender = User1Address + }); + + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Amount = 1000, + Symbol = "ABC-*", + Spender = User1Address + }); + { + var realAllowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ABC-1" + }); + realAllowance.Allowance.ShouldBe(0); + } + var allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ABC-1" + }); + allowance.Allowance.ShouldBe(1000); + allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ELF" + }); + allowance.Allowance.ShouldBe(20); + var user1Stub = + GetTester(TokenContractAddress, User1KeyPair); + var result2 = await user1Stub.TransferFrom.SendAsync(new TransferFromInput + { + Amount = 50, + From = DefaultAddress, + Memo = "test", + Symbol = "ABC-1", + To = User1Address + }); + result2.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ABC-1" + }); + allowance.Allowance.ShouldBe(1000-50); + allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "*" + }); + allowance.Allowance.ShouldBe(20); + + } + + [Fact] + public async Task MultiTokenContract_TransferFrom_Token_Test() + { + await CreateAndIssueToken(); + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Amount = 100_00000000, + Symbol = "*", + Spender = User1Address + }); + var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "SSS" + }); + allowance.Allowance.ShouldBe(0); + { + var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "SSS" + }); + realAllowance.Allowance.ShouldBe(100_00000000); + } + allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "ELF" + }); + allowance.Allowance.ShouldBe(0); + { + var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "SSS" + }); + realAllowance.Allowance.ShouldBe(100_00000000); + } + var user1Stub = + GetTester(TokenContractAddress, User1KeyPair); + var result2 = await user1Stub.TransferFrom.SendAsync(new TransferFromInput + { + Amount = 50_00000000, + From = DefaultAddress, + Memo = "test", + Symbol = "SSS", + To = User1Address + }); + result2.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "SSS" + }); + allowance.Allowance.ShouldBe(100_00000000-50_00000000); + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "SSS" + }); + balance.Balance.ShouldBe(TotalSupply - 50_00000000); + balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = User1Address, + Symbol = "SSS" + }); + balance.Balance.ShouldBe(50_00000000); + } + + private async Task CreateAndIssueToken() + { + await CreateMutiTokenAsync(TokenContractStub, new CreateInput + { + TokenName = "Test", + TotalSupply = TotalSupply, + Decimals = 8, + Issuer = DefaultAddress, + Owner = DefaultAddress, + IssueChainId = _chainId, + Symbol = "SSS" + }); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "SSS", + Amount = TotalSupply, + To = DefaultAddress, + Memo = "Issue" + }); + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "SSS" + }); + balance.Balance.ShouldBe(TotalSupply); + } + [Fact] + public async Task MultiTokenContract_Approve_Test_New() + { + await CreateAndIssueToken(); + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Spender = User1Address, + Symbol = "SSS", + Amount = 100_000000000 + }); + var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "SSS" + }); + allowance.Allowance.ShouldBe(100_000000000); + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Spender = User1Address, + Symbol = "*", + Amount = 200_000000000 + }); + { + var realAllowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "SSS" + }); + realAllowance.Allowance.ShouldBe(100_000000000); + } + allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "SSS" + }); + allowance.Allowance.ShouldBe(200_000000000); + allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "*" + }); + allowance.Allowance.ShouldBe(200_000000000); + await TokenContractStub.UnApprove.SendAsync(new UnApproveInput + { + Spender = User1Address, + Symbol = "*", + Amount = 20_000000000 + }); + allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = User1Address, + Symbol = "*" + }); + allowance.Allowance.ShouldBe(200_000000000-20_000000000); + } + + [Fact] + public async Task MultiTokenContract_Approve_Test_New_Fail() + { + await CreateAndIssueToken(); + { + var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput + { + Spender = User1Address, + Symbol = "SSS*", + Amount = 100_000000000 + }); + executionResult.TransactionResult.Error.ShouldContain("Invalid symbol."); + } + { + var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput + { + Spender = User1Address, + Symbol = "SSS**", + Amount = 100_000000000 + }); + executionResult.TransactionResult.Error.ShouldContain("Invalid symbol."); + } + { + var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput + { + Spender = User1Address, + Symbol = "*-*", + Amount = 100_000000000 + }); + executionResult.TransactionResult.Error.ShouldContain("Token is not found"); + } + } + + [Fact] + public async Task MultiTokenContract_Approve_Test_New_Nft_Fail() + { + await CreateNft(); + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = "ABC-1", + Amount = 100, + To = DefaultAddress, + Memo = "test" + }); + { + var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput + { + Spender = User1Address, + Symbol = "AB*-*", + Amount = 100_000000000 + }); + executionResult.TransactionResult.Error.ShouldContain("Invalid Symbol"); + } + { + var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput + { + Spender = User1Address, + Symbol = "ABC-*9", + Amount = 100_000000000 + }); + executionResult.TransactionResult.Error.ShouldContain("Invalid NFT Symbol."); + } + } + private async Task CreateTokenAndIssue(List
whitelist = null, Address issueTo = null) { if (whitelist == null) @@ -999,16 +1584,21 @@ public async Task Side_Chain_Creat_Token_Test() }); await ApproveWithMinersAsync(proposalId); await ParliamentContractStub.Release.SendAsync(proposalId); - var createTokenRet = await TokenContractStub.Create.SendWithExceptionAsync(new CreateInput - { - Symbol = "ALI", - TokenName = "Ali", - Decimals = 4, - TotalSupply = 100_000, - Issuer = DefaultAddress, - Owner = DefaultAddress - }); - createTokenRet.TransactionResult.Error.ShouldContain( + + proposalId = await CreateProposalAsync(TokenContractAddress, + defaultParliament, nameof(TokenContractStub.Create), + new CreateInput + { + Symbol = "ALI", + TokenName = "Ali", + Decimals = 4, + TotalSupply = 100_000, + Issuer = DefaultAddress, + Owner = DefaultAddress + }); + await ApproveWithMinersAsync(proposalId); + var createTokenRe = await ParliamentContractStub.Release.SendWithExceptionAsync(proposalId); + createTokenRe.TransactionResult.Error.ShouldContain( "Failed to create token if side chain creator already set."); } @@ -1227,4 +1817,80 @@ await TokenContractStub.Issue.SendAsync(new IssueInput checkTokenInfo.IsBurnable.ShouldBe(createTokenInput.IsBurnable); checkTokenInfo.ExternalInfo.Value.ShouldBe(createTokenInput.ExternalInfo.Value); } + + [Fact] + public async Task TokenIssuerAndOwnerModification_Test() + { + var result = await TokenContractStub.ModifyTokenIssuerAndOwner.SendWithExceptionAsync(new ModifyTokenIssuerAndOwnerInput()); + result.TransactionResult.Error.ShouldContain("Invalid input symbol."); + + result = await TokenContractStub.ModifyTokenIssuerAndOwner.SendWithExceptionAsync(new ModifyTokenIssuerAndOwnerInput + { + Symbol = "TEST" + }); + result.TransactionResult.Error.ShouldContain("Invalid input issuer."); + + result = await TokenContractStub.ModifyTokenIssuerAndOwner.SendWithExceptionAsync(new ModifyTokenIssuerAndOwnerInput + { + Symbol = "TEST", + Issuer = DefaultAddress + }); + result.TransactionResult.Error.ShouldContain("Invalid input owner."); + + result = await TokenContractStub.ModifyTokenIssuerAndOwner.SendWithExceptionAsync(new ModifyTokenIssuerAndOwnerInput + { + Symbol = "TEST", + Issuer = DefaultAddress, + Owner = DefaultAddress + }); + result.TransactionResult.Error.ShouldContain("Token is not found."); + + result = await TokenContractStubUser.ModifyTokenIssuerAndOwner.SendWithExceptionAsync(new ModifyTokenIssuerAndOwnerInput + { + Symbol = DefaultSymbol, + Issuer = DefaultAddress, + Owner = DefaultAddress + }); + result.TransactionResult.Error.ShouldContain("Only token issuer can set token issuer and owner."); + + result = await TokenContractStub.ModifyTokenIssuerAndOwner.SendWithExceptionAsync(new ModifyTokenIssuerAndOwnerInput + { + Symbol = DefaultSymbol, + Issuer = DefaultAddress, + Owner = DefaultAddress + }); + result.TransactionResult.Error.ShouldContain("Can only set token which does not have owner."); + + var output = await TokenContractStub.GetTokenIssuerAndOwnerModificationEnabled.CallAsync(new Empty()); + output.Value.ShouldBeTrue(); + + result = await TokenContractStub.SetTokenIssuerAndOwnerModificationEnabled.SendWithExceptionAsync( + new SetTokenIssuerAndOwnerModificationEnabledInput + { + Enabled = false + }); + result.TransactionResult.Error.ShouldContain("Unauthorized behavior."); + + var defaultParliament = await ParliamentContractStub.GetDefaultOrganizationAddress.CallAsync(new Empty()); + var proposalId = await CreateProposalAsync(TokenContractAddress, + defaultParliament, nameof(TokenContractStub.SetTokenIssuerAndOwnerModificationEnabled), + new SetTokenIssuerAndOwnerModificationEnabledInput + { + Enabled = false + }); + await ApproveWithMinersAsync(proposalId); + await ParliamentContractStub.Release.SendAsync(proposalId); + + output = await TokenContractStub.GetTokenIssuerAndOwnerModificationEnabled.CallAsync(new Empty()); + output.Value.ShouldBeFalse(); + + result = await TokenContractStub.ModifyTokenIssuerAndOwner.SendWithExceptionAsync(new ModifyTokenIssuerAndOwnerInput + { + Symbol = DefaultSymbol, + Issuer = DefaultAddress, + Owner = DefaultAddress + }); + result.TransactionResult.Error.ShouldContain("Set token issuer and owner disabled."); + + } } \ No newline at end of file diff --git a/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs b/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs index 4c76a04b09..30a6d59c74 100644 --- a/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs +++ b/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using AElf.Contracts.Parliament; using AElf.ContractTestBase.ContractTestKit; @@ -11,6 +12,7 @@ using AElf.Standards.ACS7; using AElf.Types; using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; using Shouldly; using Xunit; @@ -19,11 +21,13 @@ namespace AElf.Contracts.MultiToken; public class MultiTokenContractCrossChainTest : MultiTokenContractCrossChainTestBase { private const string SymbolForTesting = "ELFTEST"; + private const string NFTSymbolForTesting = "ELFNFT"; private const string NativeToken = "ELF"; private static readonly long _totalSupply = 1000L; private readonly Hash _fakeBlockHeader = HashHelper.ComputeFrom("fakeBlockHeader"); private readonly int _parentChainHeightOfCreation = 5; private readonly string sideChainSymbol = "STA"; + public const string TokenAliasExternalInfoKey = "aelf_token_alias"; #region register test @@ -321,7 +325,7 @@ public async Task SideChain_CrossChainSideChainCreateToken_Test() } - [Fact] + [Fact(Skip = "Now we allow this.")] public async Task SideChain_CrossChainCreateToken_WithAlreadyCreated_Test() { await GenerateSideChainAsync(); @@ -387,6 +391,146 @@ public async Task CrossChainCreateToken_With_Invalid_Verification_Test() Assert.True(result.Status == TransactionResultStatus.Failed); Assert.Contains("Invalid transaction", result.Error); } + + [Fact] + public async Task SideChain_CrossChainSideChainCreateToken_WithAlias_Test() + { + await GenerateSideChainAsync(); + await RegisterSideChainContractAddressOnMainChainAsync(); + + // Main chain create token + await BootMinerChangeRoundAsync(AEDPoSContractStub, true); + var createTransaction = await CreateTransactionForNFTCreation(TokenContractStub, + DefaultAccount.Address, $"{NFTSymbolForTesting}-0", TokenContractAddress); + var blockExecutedSet = await MineAsync(new List { createTransaction }); + var createResult = blockExecutedSet.TransactionResultMap[createTransaction.GetHash()]; + Assert.True(createResult.Status == TransactionResultStatus.Mined, createResult.Error); + + var createdTokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = $"{NFTSymbolForTesting}-0" + }); + var tokenValidationTransaction = CreateTokenInfoValidationTransaction(createdTokenInfo, + TokenContractStub); + + blockExecutedSet = await MineAsync(new List { tokenValidationTransaction }); + var merklePath = GetTransactionMerklePathAndRoot(tokenValidationTransaction, out var blockRoot); + await IndexMainChainTransactionAsync(blockExecutedSet.Height, blockRoot, blockRoot); + var crossChainCreateTokenInput = new CrossChainCreateTokenInput + { + FromChainId = MainChainId, + ParentChainHeight = blockExecutedSet.Height, + TransactionBytes = tokenValidationTransaction.ToByteString(), + MerklePath = merklePath + }; + // Side chain cross chain create + var executionResult = + await SideChainTokenContractStub.CrossChainCreateToken.SendAsync(crossChainCreateTokenInput); + executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined, + executionResult.TransactionResult.Error); + + var newTokenInfo = await SideChainTokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = $"{NFTSymbolForTesting}-0" + }); + newTokenInfo.TotalSupply.ShouldBe(_totalSupply); + + var alias = await SideChainTokenContractStub.GetTokenAlias.CallAsync(new StringValue + { + Value = $"{NFTSymbolForTesting}-{1}" + }); + alias.Value.ShouldBe(NFTSymbolForTesting); + } + + [Fact] + public async Task SideChain_CrossChainSideChainCreateToken_SetAliasAndSyncAgain_Test() + { + await GenerateSideChainAsync(); + await RegisterSideChainContractAddressOnMainChainAsync(); + + // Main chain create token + await BootMinerChangeRoundAsync(AEDPoSContractStub, true); + var createTransaction = await CreateTransactionForNFTCreation(TokenContractStub, + DefaultAccount.Address, $"{NFTSymbolForTesting}-0", TokenContractAddress, false); + var blockExecutedSet = await MineAsync(new List { createTransaction }); + var createResult = blockExecutedSet.TransactionResultMap[createTransaction.GetHash()]; + Assert.True(createResult.Status == TransactionResultStatus.Mined, createResult.Error); + + // Sync for the first time + { + var createdTokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = $"{NFTSymbolForTesting}-0" + }); + var tokenValidationTransaction = CreateTokenInfoValidationTransaction(createdTokenInfo, + TokenContractStub); + + blockExecutedSet = await MineAsync(new List { tokenValidationTransaction }); + var merklePath = GetTransactionMerklePathAndRoot(tokenValidationTransaction, out var blockRoot); + await IndexMainChainTransactionAsync(blockExecutedSet.Height, blockRoot, blockRoot); + var crossChainCreateTokenInput = new CrossChainCreateTokenInput + { + FromChainId = MainChainId, + ParentChainHeight = blockExecutedSet.Height, + TransactionBytes = tokenValidationTransaction.ToByteString(), + MerklePath = merklePath + }; + // Side chain cross chain create + var executionResult = + await SideChainTokenContractStub.CrossChainCreateToken.SendAsync(crossChainCreateTokenInput); + executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined, + executionResult.TransactionResult.Error); + } + + // Set Alias + { + var setAliasTransaction = await TokenContractStub.SetSymbolAlias.SendAsync(new SetSymbolAliasInput + { + Symbol = $"{NFTSymbolForTesting}-1", + Alias = NFTSymbolForTesting + }); + var setAliasResult = setAliasTransaction.TransactionResult; + setAliasResult.Status.ShouldBe(TransactionResultStatus.Mined); + } + + // Sync for the second time + { + var createdTokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = $"{NFTSymbolForTesting}-0" + }); + var tokenValidationTransaction = CreateTokenInfoValidationTransaction(createdTokenInfo, + TokenContractStub); + + blockExecutedSet = await MineAsync(new List { tokenValidationTransaction }); + var merklePath = GetTransactionMerklePathAndRoot(tokenValidationTransaction, out var blockRoot); + await IndexMainChainTransactionAsync(blockExecutedSet.Height, blockRoot, blockRoot); + var crossChainCreateTokenInput = new CrossChainCreateTokenInput + { + FromChainId = MainChainId, + ParentChainHeight = blockExecutedSet.Height, + TransactionBytes = tokenValidationTransaction.ToByteString(), + MerklePath = merklePath + }; + // Side chain cross chain create + var executionResult = + await SideChainTokenContractStub.CrossChainCreateToken.SendAsync(crossChainCreateTokenInput); + executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined, + executionResult.TransactionResult.Error); + } + + var newTokenInfo = await SideChainTokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = $"{NFTSymbolForTesting}-0" + }); + newTokenInfo.TotalSupply.ShouldBe(_totalSupply); + + var alias = await SideChainTokenContractStub.GetTokenAlias.CallAsync(new StringValue + { + Value = $"{NFTSymbolForTesting}-1" + }); + alias.Value.ShouldBe(NFTSymbolForTesting); + } #endregion @@ -891,12 +1035,48 @@ private async Task CreateTransactionForTokenCreation( await CreateSeedNftAsync(tokenContractImplStub, input, lockWhiteAddress); return tokenContractImplStub.Create.GetTransaction(input); } + + private async Task CreateTransactionForNFTCreation( + TokenContractImplContainer.TokenContractImplStub tokenContractImplStub, + Address issuer, string symbol, Address lockWhiteAddress, bool withAlias = true) + { + await CreateSeedNftCollection(tokenContractImplStub, issuer); + var tokenInfo = GetTokenInfo(symbol, issuer); + var input = new CreateInput + { + Symbol = tokenInfo.Symbol, + Decimals = 0, + Issuer = tokenInfo.Issuer, + Owner = tokenInfo.Issuer, + IsBurnable = tokenInfo.IsBurnable, + TokenName = tokenInfo.TokenName, + TotalSupply = tokenInfo.TotalSupply, + }; + if (withAlias) + { + input.ExternalInfo = new ExternalInfo + { + Value = + { + { + TokenAliasExternalInfoKey, JsonSerializer.Serialize(new Dictionary + { + { $"{NFTSymbolForTesting}-{1}", NFTSymbolForTesting } + }) + } + } + }; + } + + await CreateSeedNftAsync(tokenContractImplStub, input, lockWhiteAddress); + return tokenContractImplStub.Create.GetTransaction(input); + } private Transaction CreateTokenInfoValidationTransaction(TokenInfo createdTokenInfo, TokenContractImplContainer.TokenContractImplStub tokenContractImplStub) { - return tokenContractImplStub.ValidateTokenInfoExists.GetTransaction(new ValidateTokenInfoExistsInput + var input = new ValidateTokenInfoExistsInput { TokenName = createdTokenInfo.TokenName, Symbol = createdTokenInfo.Symbol, @@ -906,7 +1086,12 @@ private Transaction CreateTokenInfoValidationTransaction(TokenInfo createdTokenI IsBurnable = createdTokenInfo.IsBurnable, TotalSupply = createdTokenInfo.TotalSupply, IssueChainId = createdTokenInfo.IssueChainId - }); + }; + if (createdTokenInfo.ExternalInfo != null) + { + input.ExternalInfo.Add(createdTokenInfo.ExternalInfo.Value); + } + return tokenContractImplStub.ValidateTokenInfoExists.GetTransaction(input); } private TokenInfo GetTokenInfo(string symbol, Address issuer, bool isBurnable = true) diff --git a/test/AElf.Contracts.Parliament.Tests/ParliamentContractTest.cs b/test/AElf.Contracts.Parliament.Tests/ParliamentContractTest.cs index 5a82089232..caeb34f557 100644 --- a/test/AElf.Contracts.Parliament.Tests/ParliamentContractTest.cs +++ b/test/AElf.Contracts.Parliament.Tests/ParliamentContractTest.cs @@ -108,6 +108,8 @@ public async Task Get_Proposal_Test() getProposal.Output.ProposalId.ShouldBe(proposalId); getProposal.Output.OrganizationAddress.ShouldBe(organizationAddress); getProposal.Output.ToAddress.ShouldBe(TokenContractAddress); + getProposal.Output.Title.ShouldNotBeNullOrEmpty(); + getProposal.Output.Description.ShouldNotBeNullOrEmpty(); var transferParam = TransferInput.Parser.ParseFrom(getProposal.Output.Params); transferParam.Symbol.ShouldBe(transferInput.Symbol); @@ -1541,7 +1543,9 @@ private async Task CreateProposalAsync(ECKeyPair proposalKeyPair, Address ToAddress = TokenContractAddress, Params = transferInput.ToByteString(), ExpiredTime = BlockTimeProvider.GetBlockTime().AddDays(2), - OrganizationAddress = organizationAddress + OrganizationAddress = organizationAddress, + Title = "Token Transfer", + Description = "Transfer 100 ELF to Tester's address", }; var parliamentContractStub = GetParliamentContractTester(proposalKeyPair); var proposal = await parliamentContractStub.CreateProposal.SendAsync(createProposalInput); diff --git a/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs b/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs index d4f937117a..bda82297c0 100644 --- a/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs +++ b/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs @@ -5,4 +5,5 @@ public class ProfitContractTestConstants public const string NativeTokenSymbol = "ELF"; public const long NativeTokenTotalSupply = 1_000_000_000_00000000; public const int MaximumProfitReceivingDuePeriodCount = 1024; + public const int DefaultMaximumProfitReceivingPeriodCountOfOneTime = 100; } \ No newline at end of file diff --git a/test/AElf.Contracts.Profit.Tests/ProfitTests.cs b/test/AElf.Contracts.Profit.Tests/ProfitTests.cs index 34979069da..601fd7ccf6 100644 --- a/test/AElf.Contracts.Profit.Tests/ProfitTests.cs +++ b/test/AElf.Contracts.Profit.Tests/ProfitTests.cs @@ -3,6 +3,7 @@ using AElf.Contracts.MultiToken; using AElf.CSharp.Core; using AElf.Types; +using Google.Protobuf.WellKnownTypes; using Shouldly; using Xunit; @@ -1604,13 +1605,32 @@ await creator.DistributeProfits.SendAsync(new DistributeProfitsInput Symbol = tokenSymbol, SchemeId = schemeId }); + profitAmount.Value.ShouldBe(amount); + + var allProfitAmount = await ProfitContractStub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput + { + Beneficiary = receiver, + Symbol = tokenSymbol, + SchemeId = schemeId + }); + allProfitAmount.AllProfitAmount.ShouldBe(amount); + allProfitAmount.OneTimeClaimableProfitAmount.ShouldBe(amount); var profitMap = await ProfitContractStub.GetProfitsMap.CallAsync(new ClaimProfitsInput { SchemeId = schemeId, Beneficiary = receiver }); + profitMap.Value[tokenSymbol].ShouldBe(amount); + + var allProfitMap = await ProfitContractStub.GetAllProfitsMap.CallAsync(new GetAllProfitsMapInput + { + SchemeId = schemeId, + Beneficiary = receiver + }); + allProfitMap.AllProfitsMap[tokenSymbol].ShouldBe(amount); + allProfitMap.OneTimeClaimableProfitsMap[tokenSymbol].ShouldBe(amount); } // after claim @@ -1633,12 +1653,30 @@ await ProfitContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput SchemeId = schemeId }); profitAmount.Value.ShouldBe(0); + + var allProfitAmount = await ProfitContractStub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput + { + Beneficiary = receiver, + Symbol = tokenSymbol, + SchemeId = schemeId + }); + allProfitAmount.AllProfitAmount.ShouldBe(0); + allProfitAmount.OneTimeClaimableProfitAmount.ShouldBe(0); + var profitMap = await ProfitContractStub.GetProfitsMap.CallAsync(new ClaimProfitsInput { SchemeId = schemeId, Beneficiary = receiver }); profitMap.Value.ShouldNotContainKey(tokenSymbol); + + var allProfitMap = await ProfitContractStub.GetAllProfitsMap.CallAsync(new GetAllProfitsMapInput + { + SchemeId = schemeId, + Beneficiary = receiver + }); + allProfitMap.AllProfitsMap.ShouldNotContainKey(tokenSymbol); + allProfitMap.OneTimeClaimableProfitsMap.ShouldNotContainKey(tokenSymbol); } //second time @@ -1659,12 +1697,28 @@ await creator.DistributeProfits.SendAsync(new DistributeProfitsInput SchemeId = schemeId }); profitAmount.Value.ShouldBe(amount); + var allProfitAmount = await ProfitContractStub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput + { + Beneficiary = receiver, + Symbol = tokenSymbol, + SchemeId = schemeId + }); + allProfitAmount.AllProfitAmount.ShouldBe(amount); + allProfitAmount.OneTimeClaimableProfitAmount.ShouldBe(amount); var profitMap = await ProfitContractStub.GetProfitsMap.CallAsync(new ClaimProfitsInput { SchemeId = schemeId, Beneficiary = receiver }); profitMap.Value[tokenSymbol].ShouldBe(amount); + + var allProfitMap = await ProfitContractStub.GetAllProfitsMap.CallAsync(new GetAllProfitsMapInput + { + SchemeId = schemeId, + Beneficiary = receiver + }); + allProfitMap.AllProfitsMap[tokenSymbol].ShouldBe(amount); + allProfitMap.OneTimeClaimableProfitsMap[tokenSymbol].ShouldBe(amount); await ProfitContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput { @@ -1680,6 +1734,47 @@ await ProfitContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput } } + + [Fact] + public async Task MaximumProfitReceivingPeriodCount_Test() + { + var maximumProfitReceivingPeriodCount = + await ProfitContractStub.GetMaximumProfitReceivingPeriodCount.CallAsync(new Empty()); + maximumProfitReceivingPeriodCount.Value.ShouldBe(ProfitContractTestConstants + .DefaultMaximumProfitReceivingPeriodCountOfOneTime); + var maxPeriodCount = 10; + var result = await ProfitContractStub.SetMaximumProfitReceivingPeriodCount.SendWithExceptionAsync(new Int32Value + { + Value = maxPeriodCount + }); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + result.TransactionResult.Error.ShouldContain("No permission"); + + var defaultOrganizationAddress = + await ParliamentContractStub.GetDefaultOrganizationAddress.CallAsync(new Empty()); + var proposalId = await CreateProposalAsync(ProfitContractAddress, + defaultOrganizationAddress, nameof(ProfitContractStub.SetMaximumProfitReceivingPeriodCount), new Int32Value + { + Value = 0 + }); + await ApproveWithMinersAsync(proposalId); + result = await ParliamentContractStub.Release.SendWithExceptionAsync(proposalId); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + result.TransactionResult.Error.ShouldContain("Invalid maximum profit receiving period count"); + + proposalId = await CreateProposalAsync(ProfitContractAddress, + defaultOrganizationAddress, nameof(ProfitContractStub.SetMaximumProfitReceivingPeriodCount), new Int32Value + { + Value = maxPeriodCount + }); + await ApproveWithMinersAsync(proposalId); + await ParliamentContractStub.Release.SendAsync(proposalId); + + maximumProfitReceivingPeriodCount = + await ProfitContractStub.GetMaximumProfitReceivingPeriodCount.CallAsync(new Empty()); + maximumProfitReceivingPeriodCount.Value.ShouldBe(maxPeriodCount); + } + private async Task ContributeProfits(Hash schemeId, long amount = 100) { await ProfitContractStub.ContributeProfits.SendAsync(new ContributeProfitsInput diff --git a/test/AElf.Contracts.Referendum.Tests/ReferendumContractTest.cs b/test/AElf.Contracts.Referendum.Tests/ReferendumContractTest.cs index d22a250401..81dd45896c 100644 --- a/test/AElf.Contracts.Referendum.Tests/ReferendumContractTest.cs +++ b/test/AElf.Contracts.Referendum.Tests/ReferendumContractTest.cs @@ -110,6 +110,8 @@ public async Task Get_Proposal_Test() getProposal.Output.OrganizationAddress.ShouldBe(organizationAddress); getProposal.Output.ToAddress.ShouldBe(TokenContractAddress); getProposal.Output.Params.ShouldBe(createInput.ToByteString()); + getProposal.Output.Title.ShouldNotBeNullOrEmpty(); + getProposal.Output.Description.ShouldNotBeNullOrEmpty(); } [Fact] @@ -1320,7 +1322,9 @@ private async Task CreateProposalAsync(ECKeyPair proposalKeyPair, Address ToAddress = TokenContractAddress, Params = createInput.ToByteString(), ExpiredTime = timestamp ?? BlockTimeProvider.GetBlockTime().AddSeconds(1000), - OrganizationAddress = organizationAddress + OrganizationAddress = organizationAddress, + Title = "Create token: NEW", + Description = "Create a new token named NEW." }; ReferendumContractStub = GetReferendumContractTester(proposalKeyPair); var proposal = await ReferendumContractStub.CreateProposal.SendAsync(createProposalInput); diff --git a/test/AElf.Contracts.TestContract.Tests/PatchedContractSecurityTests.cs b/test/AElf.Contracts.TestContract.Tests/PatchedContractSecurityTests.cs index 587a9fff7a..b2c8df1424 100644 --- a/test/AElf.Contracts.TestContract.Tests/PatchedContractSecurityTests.cs +++ b/test/AElf.Contracts.TestContract.Tests/PatchedContractSecurityTests.cs @@ -262,7 +262,7 @@ await TestBasicSecurityContractStub.TestMappedState.SendAsync(new ProtobufInput StringValue = str } }); - txResult.TransactionResult.Error.ShouldContain($"exceeds limit of {stateSizeLimit}"); + txResult.TransactionResult.Error.ShouldContain($"exceeds limit"); var str1 = Encoding.UTF8.GetString(new byte[10]); var message = new ProtobufMessage @@ -309,7 +309,7 @@ await TestBasicSecurityContractStub.TestMapped2State.SendAsync(new ProtobufInput { ProtobufValue = new ProtobufMessage() }); - txResult.TransactionResult.Error.ShouldContain($"exceeds limit of {stateSizeLimit}"); + txResult.TransactionResult.Error.ShouldContain($"exceeds limit"); var str1 = Encoding.UTF8.GetString(new byte[10]); var message = new ProtobufMessage diff --git a/test/AElf.Contracts.TokenConverter.Tests/ConnectorTokenMigrateTest.cs b/test/AElf.Contracts.TokenConverter.Tests/ConnectorTokenMigrateTest.cs new file mode 100644 index 0000000000..e6ec3b2886 --- /dev/null +++ b/test/AElf.Contracts.TokenConverter.Tests/ConnectorTokenMigrateTest.cs @@ -0,0 +1,154 @@ +using System; +using System.Threading.Tasks; +using AElf.CSharp.Core; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; + +namespace AElf.Contracts.TokenConverter; + +public partial class TokenConverterContractTests +{ + [Fact] + public async Task CanBuyResourceTokenAfterMigration() + { + await CreateWriteToken(); + await InitializeTreasuryContractAsync(); + await InitializeTokenConverterContract(); + await PrepareToBuyAndSell(); + + await DefaultStub.MigrateConnectorTokens.SendAsync(new Empty()); + + //check the price and fee + var fromConnectorBalance = ELFConnector.VirtualBalance; + var fromConnectorWeight = decimal.Parse(ELFConnector.Weight); + var toConnectorBalance = await GetBalanceAsync(WriteSymbol, TokenConverterContractAddress); + var toConnectorWeight = decimal.Parse(WriteConnector.Weight); + + var amountToPay = BancorHelper.GetAmountToPayFromReturn(fromConnectorBalance, fromConnectorWeight, + toConnectorBalance, toConnectorWeight, 1000L); + var depositAmountBeforeBuy = await DefaultStub.GetDepositConnectorBalance.CallAsync(new StringValue + { + Value = WriteConnector.Symbol + }); + var fee = Convert.ToInt64(amountToPay * 5 / 1000); + + var buyResult = (await DefaultStub.Buy.SendAsync( + new BuyInput + { + Symbol = WriteConnector.Symbol, + Amount = 1000L, + PayLimit = amountToPay + fee + 10L + })).TransactionResult; + buyResult.Status.ShouldBe(TransactionResultStatus.Mined); + + //Verify the outcome of the transaction + var depositAmountAfterBuy = await DefaultStub.GetDepositConnectorBalance.CallAsync(new StringValue + { + Value = WriteConnector.Symbol + }); + depositAmountAfterBuy.Value.Sub(depositAmountBeforeBuy.Value).ShouldBe(amountToPay); + var balanceOfTesterWrite = await GetBalanceAsync(WriteSymbol, DefaultSender); + balanceOfTesterWrite.ShouldBe(1000L); + + var elfBalanceLoggedInTokenConvert = await DefaultStub.GetDepositConnectorBalance.CallAsync(new StringValue + { + Value = WriteConnector.Symbol + }); + elfBalanceLoggedInTokenConvert.Value.ShouldBe(ELFConnector.VirtualBalance + amountToPay); + var balanceOfElfToken = await GetBalanceAsync(NativeSymbol, TokenConverterContractAddress); + balanceOfElfToken.ShouldBe(amountToPay); + + var donatedFee = await TreasuryContractStub.GetUndistributedDividends.CallAsync(new Empty()); + donatedFee.Value[NativeSymbol].ShouldBe(fee.Div(2)); + + var balanceOfRamToken = await GetBalanceAsync(WriteSymbol, TokenConverterContractAddress); + balanceOfRamToken.ShouldBe(100_0000L - 1000L); + + var balanceOfTesterToken = await GetBalanceAsync(NativeSymbol, DefaultSender); + balanceOfTesterToken.ShouldBe(100_0000L - amountToPay - fee); + } + + [Fact] + public async Task CanSellResourceTokenAfterMigration() + { + await CreateWriteToken(); + await InitializeTreasuryContractAsync(); + await InitializeTokenConverterContract(); + await PrepareToBuyAndSell(); + + await DefaultStub.MigrateConnectorTokens.SendAsync(new Empty()); + + var buyResult = (await DefaultStub.Buy.SendAsync( + new BuyInput + { + Symbol = WriteConnector.Symbol, + Amount = 1000L, + PayLimit = 1010L + })).TransactionResult; + buyResult.Status.ShouldBe(TransactionResultStatus.Mined); + + //Balance before Sell + var treasuryBeforeSell = + (await TreasuryContractStub.GetUndistributedDividends.CallAsync(new Empty())).Value[NativeSymbol]; + var balanceOfElfToken = await GetBalanceAsync(NativeSymbol, TokenConverterContractAddress); + var balanceOfTesterToken = await GetBalanceAsync(NativeSymbol, DefaultSender); + + //check the price and fee + var toConnectorBalance = ELFConnector.VirtualBalance + balanceOfElfToken; + var toConnectorWeight = decimal.Parse(ELFConnector.Weight); + var fromConnectorBalance = await GetBalanceAsync(WriteSymbol, TokenConverterContractAddress); + var fromConnectorWeight = decimal.Parse(WriteConnector.Weight); + + var amountToReceive = BancorHelper.GetReturnFromPaid(fromConnectorBalance, fromConnectorWeight, + toConnectorBalance, toConnectorWeight, 1000L); + var depositAmountBeforeSell = await DefaultStub.GetDepositConnectorBalance.CallAsync(new StringValue + { + Value = WriteConnector.Symbol + }); + var fee = Convert.ToInt64(amountToReceive * 5 / 1000); + + var sellResult = (await DefaultStub.Sell.SendAsync(new SellInput + { + Symbol = WriteConnector.Symbol, + Amount = 1000L, + ReceiveLimit = amountToReceive - fee - 10L + })).TransactionResult; + sellResult.Status.ShouldBe(TransactionResultStatus.Mined); + + //Verify the outcome of the transaction + var depositAmountAfterSell = await DefaultStub.GetDepositConnectorBalance.CallAsync(new StringValue + { + Value = WriteConnector.Symbol + }); + depositAmountBeforeSell.Value.Sub(depositAmountAfterSell.Value).ShouldBe(amountToReceive); + var balanceOfTesterRam = await GetBalanceAsync(WriteSymbol, DefaultSender); + balanceOfTesterRam.ShouldBe(0L); + + var treasuryAfterSell = await TreasuryContractStub.GetUndistributedDividends.CallAsync(new Empty()); + treasuryAfterSell.Value[NativeSymbol].ShouldBe(fee.Div(2) + treasuryBeforeSell); + + var balanceOfElfTokenAfterSell = await GetBalanceAsync(NativeSymbol, TokenConverterContractAddress); + balanceOfElfTokenAfterSell.ShouldBe(balanceOfElfToken - amountToReceive); + + var balanceOfRamToken = await GetBalanceAsync(WriteSymbol, TokenConverterContractAddress); + balanceOfRamToken.ShouldBe(100_0000L); + + var balanceOfTesterTokenAfterSell = await GetBalanceAsync(NativeSymbol, DefaultSender); + balanceOfTesterTokenAfterSell.ShouldBe(balanceOfTesterToken + amountToReceive - fee); + } + + [Fact] + public async Task MigrateTwiceTest() + { + await CreateWriteToken(); + await InitializeTreasuryContractAsync(); + await InitializeTokenConverterContract(); + await PrepareToBuyAndSell(); + + await DefaultStub.MigrateConnectorTokens.SendAsync(new Empty()); + var result = await DefaultStub.MigrateConnectorTokens.SendWithExceptionAsync(new Empty()); + result.TransactionResult.Error.ShouldContain("Already migrated."); + } +} \ No newline at end of file diff --git a/test/AElf.Contracts.TokenConverter.Tests/TokenConvertConnectorTest.cs b/test/AElf.Contracts.TokenConverter.Tests/TokenConvertConnectorTest.cs index 6c462e1d96..eadfb2f2f2 100644 --- a/test/AElf.Contracts.TokenConverter.Tests/TokenConvertConnectorTest.cs +++ b/test/AElf.Contracts.TokenConverter.Tests/TokenConvertConnectorTest.cs @@ -87,7 +87,7 @@ await ExecuteProposalForParliamentTransaction(TokenConverterContractAddress, [Theory] [InlineData("WRITE", "0.5", "0.5", "resource token symbol has existed")] [InlineData("", "0.5", "0.5", "resource token symbol should not be empty")] - [InlineData("N89", "0.2", "0.5", "Invalid symbol.")] + [InlineData("N89()", "0.2", "0.5", "Invalid symbol.")] [InlineData("MKA", "0", "0.5", "Connector Shares has to be a decimal between 0 and 1.")] [InlineData("JUN", "0.9", "1", "Connector Shares has to be a decimal between 0 and 1.")] public async Task AddPairConnector_With_Invalid_Input_Test(string tokenSymbol, string resourceWeight, diff --git a/test/AElf.Contracts.TokenConverter.Tests/TokenConverterContractTests.cs b/test/AElf.Contracts.TokenConverter.Tests/TokenConverterContractTests.cs index 2f598d96e1..c783813905 100644 --- a/test/AElf.Contracts.TokenConverter.Tests/TokenConverterContractTests.cs +++ b/test/AElf.Contracts.TokenConverter.Tests/TokenConverterContractTests.cs @@ -84,7 +84,7 @@ public async Task Initialize_Failed_Test() //Base token symbol is invalid. { var input = GetLegalInitializeInput(); - input.BaseTokenSymbol = "elf1"; + input.BaseTokenSymbol = "elf1<>"; var result = (await DefaultStub.Initialize.SendWithExceptionAsync(input)).TransactionResult; result.Status.ShouldBe(TransactionResultStatus.Failed); result.Error.Contains("Base token symbol is invalid.").ShouldBeTrue(); @@ -111,7 +111,7 @@ public async Task Initialize_Failed_Test() //Invalid connector symbol { var input = GetLegalInitializeInput(); - input.Connectors[0].Symbol = "write"; + input.Connectors[0].Symbol = "write-0"; var result = (await DefaultStub.Initialize.SendWithExceptionAsync(input)).TransactionResult; result.Status.ShouldBe(TransactionResultStatus.Failed); result.Error.Contains("Invalid symbol.").ShouldBeTrue(); @@ -158,7 +158,7 @@ public async Task Initialize_With_Default_Base_Token_Test() [Fact] public async Task Buy_Success_Test() { - await CreateRamToken(); + await CreateWriteToken(); await InitializeTreasuryContractAsync(); await InitializeTokenConverterContract(); await PrepareToBuyAndSell(); @@ -216,7 +216,7 @@ public async Task Buy_Success_Test() [Fact] public async Task Buy_With_Invalid_Input_Test() { - await CreateRamToken(); + await CreateWriteToken(); await InitializeTokenConverterContract(); await PrepareToBuyAndSell(); @@ -244,7 +244,7 @@ public async Task Buy_With_Invalid_Input_Test() [Fact] public async Task Sell_Success_Test() { - await CreateRamToken(); + await CreateWriteToken(); await InitializeTreasuryContractAsync(); await InitializeTokenConverterContract(); await PrepareToBuyAndSell(); @@ -311,7 +311,7 @@ public async Task Sell_Success_Test() [Fact] public async Task Sell_With_Invalid_Input_Test() { - await CreateRamToken(); + await CreateWriteToken(); await InitializeTreasuryContractAsync(); await InitializeTokenConverterContract(); await PrepareToBuyAndSell(); @@ -361,7 +361,7 @@ private InitializeInput GetLegalInitializeInput() }; } - private async Task CreateRamToken() + private async Task CreateWriteToken() { await ExecuteProposalForParliamentTransaction(TokenContractAddress, nameof(TokenContractStub.Create), new CreateInput diff --git a/test/AElf.Cryptography.Tests/CryptoHelperTests.cs b/test/AElf.Cryptography.Tests/CryptoHelperTests.cs index 7ec4e7c59e..4b5d2915d7 100644 --- a/test/AElf.Cryptography.Tests/CryptoHelperTests.cs +++ b/test/AElf.Cryptography.Tests/CryptoHelperTests.cs @@ -2,9 +2,8 @@ using System.Text; using AElf.Cryptography.Exceptions; using AElf.Types; -using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Crypto; using Shouldly; -using Virgil.Crypto; using Xunit; namespace AElf.Cryptography.Tests; @@ -100,30 +99,12 @@ public void Decrypt_Message_Test() // Bob decrypt the message. var decrypt = CryptoHelper.DecryptMessage(alice.PublicKey, bob.PrivateKey, cipherText); - Assert.True(decrypt.BytesEqual(plainText)); + decrypt.ShouldBe(plainText); // Sam can't decrypt this message. var func = new Func(() => CryptoHelper.DecryptMessage(alice.PublicKey, sam.PrivateKey, cipherText)); - Assert.Throws(func); - } - - [Fact] - public void Ecdh_Test() - { - var alice = CryptoHelper.GenerateKeyPair(); - var bob = CryptoHelper.GenerateKeyPair(); - - var ecdhKey1 = CryptoHelper.Ecdh(alice.PrivateKey, bob.PublicKey); - var ecdhKey2 = CryptoHelper.Ecdh(bob.PrivateKey, alice.PublicKey); - - Assert.Equal(ecdhKey1.ToHex(), ecdhKey2.ToHex()); - } - - [Fact] - public void Ecdh_BadArgument_ShouldThrowException() - { - Assert.Throws(() => CryptoHelper.Ecdh(new byte[32], new byte[33])); + Assert.Throws(func); } [Fact] diff --git a/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs b/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs index 2e8484cd82..211d309fb0 100644 --- a/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs +++ b/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using AElf.Contracts.MultiToken; using AElf.CSharp.Core.Extension; @@ -65,30 +66,47 @@ public void GetChargedTransactionFees_Test() var transactionResult = new TransactionResult(); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[0], Amount = 1, Symbol = "ELF" }.ToLogEvent()); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[0], Amount = 2, Symbol = "ELF" }.ToLogEvent()); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[0], + Amount = 3, + Symbol = "USDT" + }.ToLogEvent()); + transactionResult.Logs.Add(new TransactionFeeCharged + { + ChargingAddress = SampleAddress.AddressList[0], + Amount = 4, + Symbol = "USDT" + }.ToLogEvent()); + transactionResult.Logs.Add(new TransactionFeeCharged + { + ChargingAddress = SampleAddress.AddressList[1], Amount = 3, Symbol = "TEST" }.ToLogEvent()); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[1], Amount = 4, Symbol = "TEST" }.ToLogEvent()); var feeDic = transactionResult.GetChargedTransactionFees(); feeDic.Count.ShouldBe(2); - feeDic.Keys.First().ShouldBe("ELF"); - feeDic.Values.First().ShouldBe(3); - feeDic.Keys.Last().ShouldBe("TEST"); - feeDic.Values.Last().ShouldBe(7); + feeDic.Keys.First().ShouldBe(SampleAddress.AddressList[0]); + feeDic.Values.First()["ELF"].ShouldBe(3); + feeDic.Values.First()["USDT"].ShouldBe(7); + feeDic.Keys.Last().ShouldBe(SampleAddress.AddressList[1]); + feeDic.Values.Last()["TEST"].ShouldBe(7); } [Fact] diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs index b265f1cca9..1264e6fb14 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs @@ -268,14 +268,14 @@ public async Task ChargeFee_SuccessfulTest() chargingAddress.ShouldContain(dummy.Transaction.From); var transactionFeeDic = dummy.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultSender,transactionFeeDic); var after = await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput { Owner = DefaultSender, Symbol = "ELF" }); - after.Balance.ShouldBe(before.Balance - transactionFeeDic[before.Symbol]); + after.Balance.ShouldBe(before.Balance - transactionFeeDic[DefaultAddress][before.Symbol]); } private static List
GetChargingAddress(TransactionResult transactionResult) @@ -284,7 +284,7 @@ private static List
GetChargingAddress(TransactionResult transactionRes return relatedLogs.Select(l => TransactionFeeCharged.Parser.ParseFrom(l.Indexed[0]).ChargingAddress).ToList(); } - private async Task CheckTransactionFeesMapAsync(Dictionary transactionFeeDic) + private async Task CheckTransactionFeesMapAsync(Address chargingAddress, Dictionary> transactionFeeDic) { var chain = await _blockchainService.GetChainAsync(); var transactionFeesMap = await _totalTransactionFeesMapProvider.GetTotalTransactionFeesMapAsync(new ChainContext @@ -293,7 +293,13 @@ private async Task CheckTransactionFeesMapAsync(Dictionary transac BlockHeight = chain.BestChainHeight }); foreach (var transactionFee in transactionFeeDic) - transactionFeesMap.Value[transactionFee.Key].ShouldBe(transactionFee.Value); + { + transactionFee.Key.ShouldBe(chargingAddress); + foreach (var value in transactionFee.Value) + { + transactionFeesMap.Value[value.Key].ShouldBe(value.Value); + } + } } [Fact] @@ -324,7 +330,7 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput dummy.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); dummy.TransactionResult.Error.ShouldBe("Pre-Error: Transaction fee not enough."); var transactionFeeDic = dummy.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(Accounts[1].Address,transactionFeeDic); var afterFee = (await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -332,7 +338,7 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput Symbol = "ELF" })).Balance; afterFee.ShouldBe(0); - transactionFeeDic["ELF"].ShouldBe(issueAmount); + transactionFeeDic[Accounts[1].Address]["ELF"].ShouldBe(issueAmount); } [Theory] @@ -378,7 +384,7 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput Symbol = chargedSymbol ?? "ELF" })).Balance; - Dictionary transactionFeeDic; + Dictionary> transactionFeeDic; var userTestContractStub = GetTester(_testContractAddress, Accounts[1].KeyPair); if (isChargingSuccessful) @@ -387,8 +393,9 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput dummyResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); if (chargedSymbol != null) { - dummyResult.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); - dummyResult.TransactionResult.GetChargedTransactionFees().Values.ShouldContain(chargedAmount); + dummyResult.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(Accounts[1].Address); + dummyResult.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address].Keys.ShouldContain(chargedSymbol); + dummyResult.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address].Values.ShouldContain(chargedAmount); } transactionFeeDic = dummyResult.TransactionResult.GetChargedTransactionFees(); @@ -399,13 +406,13 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput dummyResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); dummyResult.TransactionResult.Error.ShouldBe("Pre-Error: Transaction fee not enough."); if (chargedSymbol != null) - dummyResult.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); + dummyResult.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address].Keys.ShouldContain(chargedSymbol); transactionFeeDic = dummyResult.TransactionResult.GetChargedTransactionFees(); } - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(Accounts[1].Address,transactionFeeDic); if (chargedSymbol != null) - transactionFeeDic[chargedSymbol].ShouldBe(chargedAmount); + transactionFeeDic[Accounts[1].Address][chargedSymbol].ShouldBe(chargedAmount); var finalBalance = (await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput { diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs index 6a6ffe5a73..02aebc36aa 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs @@ -55,8 +55,9 @@ await SetMethodFeeWithProposalAsync(new MethodFees Memo = Guid.NewGuid().ToString(), To = SampleAddress.AddressList[0] }); + var fromAddress = Address.FromPublicKey(Tester.KeyPair.PublicKey); var transactionResult = await Tester.GetTransactionResultAsync(result.Item2.GetHash()); - var targetFee = transactionResult.GetChargedTransactionFees().First().Value; + var targetFee = transactionResult.GetChargedTransactionFees()[fromAddress].First().Value; var transactionFeesMap = await GetTransactionFeesMapAsync(new ChainContext { @@ -97,7 +98,7 @@ await SetMethodFeeWithProposalAsync(new MethodFees To = SampleAddress.AddressList[0] }); transactionResult = await Tester.GetTransactionResultAsync(result.Item2.GetHash()); - var fee = transactionResult.GetChargedTransactionFees().First().Value; + var fee = transactionResult.GetChargedTransactionFees()[fromAddress].First().Value; transactionFeesMap = await GetTransactionFeesMapAsync(new ChainContext { BlockHash = result.Item1.GetHash(), BlockHeight = result.Item1.Height diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs index 25e8daa5f4..aebf96ff37 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs @@ -75,14 +75,14 @@ public async Task ChargeUserContractFeeTest_Success() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress,transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { Owner = DefaultAddress, Symbol = "ELF" }); - after.Balance.ShouldBe(beforeBalance.Balance - transactionFeeDic[beforeBalance.Symbol]); + after.Balance.ShouldBe(beforeBalance.Balance - transactionFeeDic[DefaultAddress][beforeBalance.Symbol]); } [Fact] @@ -97,7 +97,7 @@ public async Task ChargeUserContractFeeTest_Success_BaseFeeIsFree() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress,transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -193,7 +193,7 @@ await TokenContractStub.Transfer.SendAsync(new TransferInput Symbol = chargedSymbol ?? "ELF" })).Balance; - Dictionary transactionFeeDic; + Dictionary> transactionFeeDic; var userTestContractStub = GetTester(_testContractAddress, Accounts[1].KeyPair); if (isChargingSuccessful) @@ -202,8 +202,13 @@ await TokenContractStub.Transfer.SendAsync(new TransferInput result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); if (chargedSymbol != null) { - result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); - result.TransactionResult.GetChargedTransactionFees().Values.ShouldContain(chargedAmount); + var token = new Dictionary + { + [chargedSymbol] = chargedAmount + }; + result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(Accounts[1].Address); + var fee = result.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address]; + fee.ShouldContainKeyAndValue(chargedSymbol,chargedAmount); } transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); @@ -214,13 +219,21 @@ await TokenContractStub.Transfer.SendAsync(new TransferInput result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); result.TransactionResult.Error.ShouldBe("Pre-Error: Transaction fee not enough."); if (chargedSymbol != null) - result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); + { + var token = new Dictionary + { + [chargedSymbol] = chargedAmount + }; + result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(Accounts[1].Address); + var fee = result.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address]; + fee.ShouldContainKeyAndValue(chargedSymbol,chargedAmount); + } transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); } - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(Accounts[1].Address,transactionFeeDic); if (chargedSymbol != null) - transactionFeeDic[chargedSymbol].ShouldBe(chargedAmount); + transactionFeeDic[Accounts[1].Address][chargedSymbol].ShouldBe(chargedAmount); var finalBalance = (await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -253,7 +266,7 @@ public async Task ChargeFee_SizeFeeIsFree() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress, transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -282,7 +295,8 @@ public async Task ChargeFee_SpecConfigurationFee() }; var createProposalInput = new SetConfigurationInput { - Key = $"{ConfigurationKey}_{_testContractAddress}_{nameof(TestContractContainer.TestContractStub.TestMethod)}", + Key = + $"{ConfigurationKey}_{_testContractAddress.ToBase58()}_{nameof(TestContractContainer.TestContractStub.TestMethod)}", Value = transactionFee.ToByteString() }; await ConfigurationStub.SetConfiguration.SendAsync(createProposalInput); @@ -294,7 +308,7 @@ public async Task ChargeFee_SpecConfigurationFee() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress, transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -356,7 +370,7 @@ private async Task Initialize() } } - private async Task CheckTransactionFeesMapAsync(Dictionary transactionFeeDic) + private async Task CheckTransactionFeesMapAsync(Address chargingAddress, Dictionary> transactionFeeDic) { var chain = await _blockchainService.GetChainAsync(); var transactionFeesMap = await _totalTransactionFeesMapProvider.GetTotalTransactionFeesMapAsync(new ChainContext @@ -365,7 +379,14 @@ private async Task CheckTransactionFeesMapAsync(Dictionary transac BlockHeight = chain.BestChainHeight }); foreach (var transactionFee in transactionFeeDic) - transactionFeesMap.Value[transactionFee.Key].ShouldBe(transactionFee.Value); + { + transactionFee.Key.ShouldBe(chargingAddress); + foreach (var value in transactionFee.Value) + { + transactionFeesMap.Value[value.Key].ShouldBe(value.Value); + } + } + } private async Task SetUserContractFeeAsync(int amount) diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs index 6a47875b59..af015ab1bb 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs @@ -258,8 +258,8 @@ public async Task CompareConsumptions() write.ShouldBeGreaterThan(write1); traffic.ShouldBe(traffic1); - var consumedTokens1 = txResult1.GetConsumedResourceTokens(); - var consumedTokens2 = txResult2.GetConsumedResourceTokens(); + var consumedTokens1 = txResult1.GetConsumedResourceTokens()[TestContractAddress]; + var consumedTokens2 = txResult2.GetConsumedResourceTokens()[TestContractAddress]; consumedTokens1["READ"].ShouldBeGreaterThan(consumedTokens2["READ"]); consumedTokens1["WRITE"].ShouldBeGreaterThan(consumedTokens2["WRITE"]); consumedTokens1["TRAFFIC"].ShouldBe(consumedTokens2["TRAFFIC"]); diff --git a/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs b/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs index b898977dc3..67e9db0b83 100644 --- a/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs +++ b/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs @@ -15,6 +15,7 @@ using AElf.Kernel.SmartContract.Application; using AElf.Kernel.SmartContract.Domain; using AElf.Kernel.Token; +using AElf.Kernel.TransactionPool.Handler; using AElf.Kernel.TransactionPool.Infrastructure; using AElf.OS; using AElf.Runtime.CSharp; @@ -44,6 +45,8 @@ public sealed class BlockChainAppServiceTest : WebAppTestBase private readonly ISmartContractAddressService _smartContractAddressService; private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider; private readonly TransactionValidationStatusChangedEventHandler _transactionValidationStatusChangedEventHandler; + private readonly TransactionValidationStatusFailedEventHandler _transactionExecutionValidationFailedEventHandler; + private readonly ITransactionResultProxyService _transactionResultProxyService; private readonly ITxHub _txHub; private IReadOnlyDictionary _codes; @@ -58,6 +61,9 @@ public BlockChainAppServiceTest(ITestOutputHelper outputHelper) : base(outputHel _osTestHelper = GetRequiredService(); _accountService = GetRequiredService(); _blockStateSetManger = GetRequiredService(); + _transactionResultProxyService = GetRequiredService(); + _transactionExecutionValidationFailedEventHandler = + GetRequiredService(); _transactionValidationStatusChangedEventHandler = GetRequiredService(); } @@ -1682,6 +1688,12 @@ public async Task CalculateTransactionFee_Success_Test() }; var response = await PostResponseAsObjectAsync("/api/blockChain/CalculateTransactionFee", parameters); response.Success.ShouldBe(true); + response.TransactionFees.ChargingAddress.ShouldBe(transaction.From.ToBase58()); + response.TransactionFees.Fee.First().Key.ShouldBe("ELF"); + response.TransactionFees.Fee.First().Value.ShouldBeGreaterThan(10000000L); + response.TransactionFee.First().Key.ShouldBe("ELF"); + response.TransactionFee.First().Value.ShouldBeGreaterThan(10000000L); + } [Fact] @@ -1822,4 +1834,31 @@ await PostResponseAsObjectAsync("/api/blockChain/sendTran response.Transaction.Params.ShouldNotBe( AddOptionInput.Parser.ParseFrom(transaction.Params).ToString()); } + + [Fact] + public async Task InvalidTransactionResultTest() + { + + var txId = HashHelper.ComputeFrom("InvalidTransactionResultTest"); + await _transactionExecutionValidationFailedEventHandler.HandleEventAsync(new TransactionValidationStatusChangedEvent + { + TransactionId = txId, + TransactionResultStatus = TransactionResultStatus.NodeValidationFailed, + Error = "tx error" + }); + + var invalidResult = await _transactionResultProxyService.InvalidTransactionResultService + .GetInvalidTransactionResultAsync(txId); + invalidResult.ShouldNotBeNull(); + invalidResult.Status.ShouldBe(TransactionResultStatus.NodeValidationFailed); + invalidResult.Error.ShouldBe("tx error"); + + var response = await GetResponseAsObjectAsync( + $"/api/blockChain/transactionResult?transactionId={txId.ToHex()}"); + response.ShouldNotBeNull(); + response.Status.ShouldBe(TransactionResultStatus.NodeValidationFailed.ToString().ToUpper()); + response.Error.ShouldBe("tx error"); + + } + } \ No newline at end of file diff --git a/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs b/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs index c2d6783b8f..c9dcf809ec 100644 --- a/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs +++ b/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs @@ -6,6 +6,7 @@ using AElf.Kernel.FeeCalculation; using AElf.Kernel.SmartContract.Application; using AElf.Kernel.SmartContract.ExecutionPluginForMethodFee; +using AElf.Kernel.TransactionPool; using AElf.Modularity; using AElf.OS; using AElf.OS.Network.Application; @@ -88,5 +89,9 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.UserName = BasicAuth.DefaultUserName; options.Password = BasicAuth.DefaultPassword; }); + Configure(o => { + o.PoolLimit = 20; + o.StoreInvalidTransactionResultEnabled = true; + }); } } \ No newline at end of file