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..01e315a740 --- /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":"7.0.410"}}' > ./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.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 8a41b78140..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; @@ -24,4 +25,6 @@ public static class TokenContractConstants 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 b687cf1598..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; } @@ -67,4 +73,7 @@ public partial class TokenContractState : ContractState 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 8d12f604e1..8d013d3180 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 2a477cafe7..6adcf01a2a 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs @@ -32,7 +32,7 @@ public override Empty InitializeFromParentChain(InitializeFromParentChainInput i /// public override Empty Create(CreateInput input) { - var inputSymbolType = GetCreateInputSymbolType(input.Symbol); + var inputSymbolType = GetSymbolType(input.Symbol); if (input.Owner == null) { input.Owner = input.Issuer; @@ -56,7 +56,7 @@ private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType. 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]; @@ -77,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)) { @@ -110,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( @@ -131,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 }); @@ -156,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 @@ -171,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(); @@ -245,27 +253,29 @@ 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); - Approve(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) { - State.Allowances[Context.Sender][spender][symbol] = amount; + var actualSymbol = GetActualTokenSymbol(symbol); + State.Allowances[Context.Sender][spender][actualSymbol] = amount; Context.Fire(new Approved { Owner = Context.Sender, Spender = spender, - Symbol = symbol, + Symbol = actualSymbol, Amount = amount }); } @@ -277,7 +287,8 @@ public override Empty BatchApprove(BatchApproveInput input) foreach (var approve in input.Value) { AssertValidInputAddress(approve.Spender); - AssertValidToken(approve.Symbol, approve.Amount); + 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(); @@ -289,15 +300,16 @@ public override Empty BatchApprove(BatchApproveInput input) 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(); @@ -426,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 && @@ -489,24 +501,38 @@ 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(); } - public override Empty RegisterCrossChainTokenContractAddress(RegisterCrossChainTokenContractAddressInput input) { CheckCrossChainTokenContractRegistrationControllerAuthority(); @@ -533,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, @@ -578,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, @@ -619,7 +645,7 @@ public override Empty ModifyTokenIssuerAndOwner(ModifyTokenIssuerAndOwnerInput i Assert(input.Issuer != null && !input.Issuer.Value.IsNullOrEmpty(), "Invalid input issuer."); Assert(input.Owner != null && !input.Owner.Value.IsNullOrEmpty(), "Invalid input owner."); - var tokenInfo = State.TokenInfos[input.Symbol]; + 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."); @@ -648,7 +674,7 @@ public override BoolValue GetTokenIssuerAndOwnerModificationEnabled(Empty input) Value = !State.TokenIssuerAndOwnerModificationDisabled.Value }; } - + public override Empty SetMaxBatchApproveCount(Int32Value input) { Assert(input.Value > 0, "Invalid input."); @@ -671,4 +697,127 @@ private int GetMaxBatchApproveCount() ? 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 ed210282d0..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(IsValidSymbolChar), + 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(IsValidSymbolChar), + 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 4c3efdf6a3..ae01062131 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs @@ -4,12 +4,12 @@ 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; } diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs index 124d1dac73..93d54ec7b3 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs @@ -43,12 +43,12 @@ private Empty CreateNFTInfo(CreateInput input) 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); @@ -56,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) @@ -66,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) @@ -92,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.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/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/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/token_contract.proto b/protobuf/token_contract.proto index e1174b8b45..9931b680a3 100644 --- a/protobuf/token_contract.proto +++ b/protobuf/token_contract.proto @@ -144,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; @@ -174,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) { @@ -229,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 { @@ -689,6 +705,11 @@ message GetTransactionFeeDelegateesOutput { repeated aelf.Address delegatee_addresses = 1; } +message SetSymbolAliasInput { + string symbol = 1; + string alias = 2; +} + // Events message Transferred { @@ -858,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_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.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs b/src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs index fc0212b85c..61e888d5c9 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; @@ -38,6 +39,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.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 7abeea313b..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; @@ -48,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 = block.GetHash(), - 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}", - block.GetHash(), 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.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 7847da5094..491ddd51be 100644 --- a/src/AElf.Kernel.TransactionPool/TransactionOptions.cs +++ b/src/AElf.Kernel.TransactionPool/TransactionOptions.cs @@ -17,4 +17,12 @@ public class TransactionOptions /// But common node needs to enable it to prevent transaction flood attack /// public bool EnableTransactionExecutionValidation { get; set; } = true; + + + /// + /// Configuration whether to save failed transaction results + /// + public bool StoreInvalidTransactionResultEnabled { get; set; } + + } \ No newline at end of file diff --git a/src/AElf.Launcher/appsettings.json b/src/AElf.Launcher/appsettings.json index 80b0e93f8f..45c0923284 100644 --- a/src/AElf.Launcher/appsettings.json +++ b/src/AElf.Launcher/appsettings.json @@ -31,6 +31,7 @@ "MinerIncreaseInterval": 31536000 }, "Transaction": { + "StoreInvalidTransactionResultEnabled" : false, "PoolLimit": 10240 }, "BasicAuth": { 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.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 d6a6945ef2..9c2dfab8a2 100644 --- a/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs @@ -275,8 +275,23 @@ public async Task MultiTokenContract_Create_NFTCollection_Input_Check_Test() ExternalInfo = input.ExternalInfo, Owner = input.Owner }); - - var result = await TokenContractStub.Create.SendWithExceptionAsync(seedInput);; + + var result = await TokenContractStub.Create.SendAsync(seedInput); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + + seedInput = BuildSeedCreateInput(new CreateInput + { + Symbol = "ABC123()", + TokenName = input.TokenName, + TotalSupply = input.TotalSupply, + Decimals = input.Decimals, + Issuer = input.Issuer, + IssueChainId = input.IssueChainId, + ExternalInfo = input.ExternalInfo, + Owner = input.Owner + }); + + result = await TokenContractStub.Create.SendWithExceptionAsync(seedInput); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); result.TransactionResult.Error.ShouldContain("Invalid Symbol input"); } 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 49642bae3a..201e56d6e6 100644 --- a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs @@ -504,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) 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.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.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