diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58da635 --- /dev/null +++ b/.gitignore @@ -0,0 +1,158 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.svclog +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml +*.azurePubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +packages/ +## TODO: If the tool you use requires repositories.config, also uncomment the next line +!packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +![Ss]tyle[Cc]op.targets +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml + +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +_NCrunch* + +# ================================= +# Common IntelliJ Platform excludes +# ================================= + +# User specific +**/.idea/**/workspace.xml +**/.idea/**/tasks.xml +**/.idea/shelf/* +**/.idea/dictionaries +**/.idea/httpRequests/ + +# Sensitive or high-churn files +**/.idea/**/dataSources/ +**/.idea/**/dataSources.ids +**/.idea/**/dataSources.xml +**/.idea/**/dataSources.local.xml +**/.idea/**/sqlDataSources.xml +**/.idea/**/dynamic.xml + +# Rider +# Rider auto-generates .iml files, and contentModel.xml +**/.idea/**/*.iml +**/.idea/**/contentModel.xml +**/.idea/**/modules.xml \ No newline at end of file diff --git a/.idea/.idea.ExampleContract/.idea/.gitignore b/.idea/.idea.ExampleContract/.idea/.gitignore new file mode 100644 index 0000000..087463d --- /dev/null +++ b/.idea/.idea.ExampleContract/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.ExampleContract.iml +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.ExampleContract/.idea/.name b/.idea/.idea.ExampleContract/.idea/.name new file mode 100644 index 0000000..c99d07a --- /dev/null +++ b/.idea/.idea.ExampleContract/.idea/.name @@ -0,0 +1 @@ +ExampleContract \ No newline at end of file diff --git a/.idea/.idea.ExampleContract/.idea/encodings.xml b/.idea/.idea.ExampleContract/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.ExampleContract/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.ExampleContract/.idea/indexLayout.xml b/.idea/.idea.ExampleContract/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.ExampleContract/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.ExampleContract/.idea/vcs.xml b/.idea/.idea.ExampleContract/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.ExampleContract/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.examples.dir/.idea/indexLayout.xml b/.idea/.idea.examples.dir/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.examples.dir/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.examples.dir/.idea/projectSettingsUpdater.xml b/.idea/.idea.examples.dir/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..4bb9f4d --- /dev/null +++ b/.idea/.idea.examples.dir/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.examples.dir/.idea/vcs.xml b/.idea/.idea.examples.dir/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.examples.dir/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CodeCoverage.runsettings b/CodeCoverage.runsettings new file mode 100644 index 0000000..790dcc0 --- /dev/null +++ b/CodeCoverage.runsettings @@ -0,0 +1,17 @@ + + + + + + + + cobertura + [xunit.*]*,[*Tests]* + **/test/**/*.cs + Obsolete,GeneratedCodeAttribute + false + + + + + \ No newline at end of file diff --git a/ExampleContract.sln b/ExampleContract.sln new file mode 100644 index 0000000..c7f0dab --- /dev/null +++ b/ExampleContract.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basics", "basics", "{0B1211B7-ADC5-4934-9573-D19E429C7178}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "timelock-contract", "timelock-contract", "{2F26B697-A6FE-4A9F-A7DB-756E364B7BD8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Contracts.TimelockContract", "basics\timelock-contract\src\AElf.Contracts.TimelockContract.csproj", "{ECC2BE86-A75D-4526-B10B-A12915EEEEFC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Contracts.TimelockContract.Tests", "basics\timelock-contract\test\AElf.Contracts.TimelockContract.Tests.csproj", "{FFFB1BAF-52A1-4773-83BE-2BA1ED34D745}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ECC2BE86-A75D-4526-B10B-A12915EEEEFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECC2BE86-A75D-4526-B10B-A12915EEEEFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECC2BE86-A75D-4526-B10B-A12915EEEEFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECC2BE86-A75D-4526-B10B-A12915EEEEFC}.Release|Any CPU.Build.0 = Release|Any CPU + {FFFB1BAF-52A1-4773-83BE-2BA1ED34D745}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFFB1BAF-52A1-4773-83BE-2BA1ED34D745}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFFB1BAF-52A1-4773-83BE-2BA1ED34D745}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFFB1BAF-52A1-4773-83BE-2BA1ED34D745}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2F26B697-A6FE-4A9F-A7DB-756E364B7BD8} = {0B1211B7-ADC5-4934-9573-D19E429C7178} + {ECC2BE86-A75D-4526-B10B-A12915EEEEFC} = {2F26B697-A6FE-4A9F-A7DB-756E364B7BD8} + {FFFB1BAF-52A1-4773-83BE-2BA1ED34D745} = {2F26B697-A6FE-4A9F-A7DB-756E364B7BD8} + EndGlobalSection +EndGlobal diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..2a830d1 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,73 @@ +jobs: + +# All tasks on Windows.... +#- job: build_all_windows +# displayName: Build all tasks (Windows) +# pool: +# vmImage: windows-latest +# variables: +# CI_TEST: true +# steps: +# - task: UseDotNet@2 +# displayName: 'Install .NET Core SDK' +# inputs: +# version: 3.1.102 +# +# - task: BatchScript@1 +# displayName: 'Download AElf build tools' +# inputs: +# filename: 'scripts/download_binary.bat' +# - script: PowerShell.exe -file scripts/install.ps1 +# displayName: 'Install protobuf' +# - script: choco install unzip +# displayName: 'Install unzip' +# - powershell: Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +# - script: PowerShell.exe -file build.ps1 -target=Run-Unit-Tests +# displayName: 'Build and Test' +# All tasks on Linux +- job: build_all_linux + displayName: Build all tasks (Linux) + timeoutInMinutes: 120 + pool: + vmImage: ubuntu-latest + variables: + CI_TEST: true + steps: + - task: UseDotNet@2 + displayName: 'Install .NET Core SDK' + inputs: + version: 7.0.x + - script: bash build.sh --target=Test-with-Codecov + displayName: 'Build and Test' + - task: PublishTestResults@2 + condition: always() + inputs: + testRunner: VSTest + testResultsFiles: '**/*.trx' + - task: reportgenerator@5 + displayName: ReportGenerator + inputs: + reports: '$(Build.SourcesDirectory)/**/test/TestResults/*/coverage.cobertura.xml' + targetdir: '$(Build.SourcesDirectory)/CodeCoverage' + reporttypes: 'Cobertura' + assemblyfilters: '-xunit*' + - script: bash build.sh --target=Upload-Coverage-Azure + displayName: 'Upload data to Codecov' +# All tasks on macOS +#- job: build_all_darwin +# displayName: Build all tasks (macOS) +# pool: +# vmImage: macos-latest +# variables: +# CI_TEST: true +# steps: +# - task: UseDotNet@2 +# displayName: 'Install .NET Core SDK' +# inputs: +# version: 3.1.102 +# - script: bash scripts/download_binary.sh +# displayName: 'Download AElf build tools' +# - script: bash scripts/install.sh +# displayName: 'Install protobuf' +# - script: bash build.sh -target=Run-Unit-Tests +# displayName: 'Build and Test' diff --git a/basics/.idea/.idea.basics.dir/.idea/.gitignore b/basics/.idea/.idea.basics.dir/.idea/.gitignore new file mode 100644 index 0000000..a896001 --- /dev/null +++ b/basics/.idea/.idea.basics.dir/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +/.idea.basics.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/basics/.idea/.idea.basics.dir/.idea/encodings.xml b/basics/.idea/.idea.basics.dir/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/basics/.idea/.idea.basics.dir/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/basics/.idea/.idea.basics.dir/.idea/indexLayout.xml b/basics/.idea/.idea.basics.dir/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/basics/.idea/.idea.basics.dir/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/basics/.idea/.idea.basics.dir/.idea/vcs.xml b/basics/.idea/.idea.basics.dir/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/basics/.idea/.idea.basics.dir/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/.gitignore b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/.gitignore new file mode 100644 index 0000000..affda2e --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +/.idea.TimelockContract.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/.name b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/.name new file mode 100644 index 0000000..de88a6e --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/.name @@ -0,0 +1 @@ +TimelockContract \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/encodings.xml b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/indexLayout.xml b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/indexLayout.xml new file mode 100644 index 0000000..0fd1d51 --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/indexLayout.xml @@ -0,0 +1,10 @@ + + + + + ../../../examples + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/vcs.xml b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.TimelockContract/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/.gitignore b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/encodings.xml b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/indexLayout.xml b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/projectSettingsUpdater.xml b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..4bb9f4d --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/vcs.xml b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/basics/timelock-contract/.idea/.idea.timelock-contract.dir/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/src/AElf.Contracts.TimelockContract.csproj b/basics/timelock-contract/src/AElf.Contracts.TimelockContract.csproj new file mode 100644 index 0000000..ce2fc55 --- /dev/null +++ b/basics/timelock-contract/src/AElf.Contracts.TimelockContract.csproj @@ -0,0 +1,30 @@ + + + net6.0 + AElf.Contracts.Timelock + true + + + true + + + true + + + $(OutputPath)obj/$(Configuration)/$(TargetFramework)/ + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/src/ContractsReferences.cs b/basics/timelock-contract/src/ContractsReferences.cs new file mode 100644 index 0000000..76a0c5e --- /dev/null +++ b/basics/timelock-contract/src/ContractsReferences.cs @@ -0,0 +1,9 @@ +using AElf.Contracts.MultiToken; + +namespace AElf.Contracts.Timelock +{ + public partial class TimelockContractState + { + internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; } + } +} \ No newline at end of file diff --git a/basics/timelock-contract/src/Protobuf/base/acs1.proto b/basics/timelock-contract/src/Protobuf/base/acs1.proto new file mode 100644 index 0000000..74b3a98 --- /dev/null +++ b/basics/timelock-contract/src/Protobuf/base/acs1.proto @@ -0,0 +1,53 @@ +/** + * AElf Standards ACS1(Transaction Fee Standard) + * + * Used to manage the transaction fee. + */ +syntax = "proto3"; + +package acs1; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; +import "Protobuf/message/authority_info.proto"; + +option (aelf.identity) = "acs1"; +option csharp_namespace = "AElf.Standards.ACS1"; + +service MethodFeeProviderContract { + + // Set the method fees for the specified method. Note that this will override all fees of the method. + rpc SetMethodFee (MethodFees) returns (google.protobuf.Empty) { + } + + // Change the method fee controller, the default is parliament and default organization. + rpc ChangeMethodFeeController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Query method fee information by method name. + rpc GetMethodFee (google.protobuf.StringValue) returns (MethodFees) { + option (aelf.is_view) = true; + } + + // Query the method fee controller. + rpc GetMethodFeeController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } +} + +message MethodFees { + // The name of the method to be charged. + string method_name = 1; + // List of fees to be charged. + repeated MethodFee fees = 2; + bool is_size_fee_free = 3;// Optional based on the implementation of SetMethodFee method. +} + +message MethodFee { + // The token symbol of the method fee. + string symbol = 1; + // The amount of fees to be charged. + int64 basic_fee = 2; +} \ No newline at end of file diff --git a/basics/timelock-contract/src/Protobuf/contract/timelock_contract.proto b/basics/timelock-contract/src/Protobuf/contract/timelock_contract.proto new file mode 100644 index 0000000..09dfd6c --- /dev/null +++ b/basics/timelock-contract/src/Protobuf/contract/timelock_contract.proto @@ -0,0 +1,93 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "AElf.Contracts.Timelock"; + +service TimelockContract { + option (aelf.csharp_state) = "AElf.Contracts.Timelock.TimelockContractState"; + + // Actions + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc SetDelay (SetDelayInput) returns (google.protobuf.Empty) { + } + rpc ChangeAdmin (ChangeAdminInput) returns (google.protobuf.Empty) { + } + rpc QueueTransaction (TransactionInput) returns (aelf.Hash) { + } + rpc ExecuteTransaction (TransactionInput) returns (google.protobuf.Empty) { + } + rpc CancelTransaction (TransactionInput) returns (google.protobuf.Empty) { + } + + // View + rpc GetDelay (google.protobuf.Empty) returns (google.protobuf.UInt64Value) { + option (aelf.is_view) = true; + } + rpc GetAdmin (google.protobuf.Empty) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetTransaction(aelf.Hash) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} + +// Params +message InitializeInput { + uint64 delay = 1; +} + +message SetDelayInput { + uint64 delay = 1; +} + +message ChangeAdminInput { + aelf.Address admin = 1; +} + +message TransactionInput { + aelf.Address target = 1; + string method = 2; + bytes data = 3; + google.protobuf.Timestamp execute_time = 4; +} + +// Events +message NewAdmin { + option (aelf.is_event) = true; + aelf.Address admin = 1; +} + +message NewDelay { + option (aelf.is_event) = true; + uint64 delay = 1; +} + +message QueueTransaction { + option (aelf.is_event) = true; + aelf.Address target = 1; + string method = 2; + bytes data = 3; // data equivalent to ConvertToByteString(IMessage) + google.protobuf.Timestamp execute_time = 4; +} + +message ExecuteTransaction { + option (aelf.is_event) = true; + aelf.Address target = 1; + string method = 2; + bytes data = 3; + google.protobuf.Timestamp execute_time = 4; +} + +message CancelTransaction { + option (aelf.is_event) = true; + aelf.Address target = 1; + string method = 2; + bytes data = 3; + google.protobuf.Timestamp execute_time = 4; +} \ No newline at end of file diff --git a/basics/timelock-contract/src/Protobuf/message/authority_info.proto b/basics/timelock-contract/src/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..1b7bc25 --- /dev/null +++ b/basics/timelock-contract/src/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.Timelock"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/basics/timelock-contract/src/Protobuf/reference/acs0.proto b/basics/timelock-contract/src/Protobuf/reference/acs0.proto new file mode 100644 index 0000000..d55d01a --- /dev/null +++ b/basics/timelock-contract/src/Protobuf/reference/acs0.proto @@ -0,0 +1,287 @@ +/** + * AElf Standards ACS0(Contract Deployment Standard) + * + * Used to manage the deployment and update of contracts. + */ +syntax = "proto3"; + +package acs0; +option csharp_namespace = "AElf.Standards.ACS0"; + +import public "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service ACS0 { + // Deploy a system smart contract on chain and return the address of the system contract deployed. + rpc DeploySystemSmartContract (SystemContractDeploymentInput) returns (aelf.Address) { + } + + // Deploy a smart contract on chain and return the address of the contract deployed. + rpc DeploySmartContract (ContractDeploymentInput) returns (aelf.Address) { + } + + // Update a smart contract on chain. + rpc UpdateSmartContract (ContractUpdateInput) returns (aelf.Address) { + } + + // Create a proposal to deploy a new contract and returns the id of the proposed contract. + rpc ProposeNewContract (ContractDeploymentInput) returns (aelf.Hash) { + } + + // Create a proposal to check the code of a contract and return the id of the proposed contract. + rpc ProposeContractCodeCheck (ContractCodeCheckInput) returns (aelf.Hash) { + } + + // Create a proposal to update the specified contract + // and return the id of the proposed contract. + rpc ProposeUpdateContract (ContractUpdateInput) returns (aelf.Hash) { + } + + // Release the contract proposal which has been approved. + rpc ReleaseApprovedContract (ReleaseContractInput) returns (google.protobuf.Empty) { + } + + // Release the proposal which has passed the code check. + rpc ReleaseCodeCheckedContract (ReleaseContractInput) returns (google.protobuf.Empty) { + } + + // Deploy a user smart contract on chain and return the hash of the contract code. + rpc DeployUserSmartContract (ContractDeploymentInput) returns (DeployUserSmartContractOutput) { + } + + // Update a user smart contract on chain. + rpc UpdateUserSmartContract (ContractUpdateInput) returns (google.protobuf.Empty) { + } + + // Release the proposal which has passed the code check. + rpc ReleaseApprovedUserSmartContract(ReleaseContractInput) returns (google.protobuf.Empty) { + } + + // Perform user contract deployment. + rpc PerformDeployUserSmartContract(ContractDeploymentInput) returns (aelf.Address) { + } + + // Perform user contract update. + rpc PerformUpdateUserSmartContract(ContractUpdateInput) returns (google.protobuf.Empty) { + } + + // Set author of the specified contract. + rpc SetContractAuthor (SetContractAuthorInput) returns (google.protobuf.Empty) { + } + + // Validate whether the input system contract exists. + rpc ValidateSystemContractAddress(ValidateSystemContractAddressInput) returns (google.protobuf.Empty){ + } + + // Set authority of contract deployment. + rpc SetContractProposerRequiredState (google.protobuf.BoolValue) returns (google.protobuf.Empty) { + } + + // Get the current serial number of genesis contract + // (corresponds to the serial number that will be given to the next deployed contract). + rpc CurrentContractSerialNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + + // Get detailed information about the specified contract. + rpc GetContractInfo (aelf.Address) returns (ContractInfo) { + option (aelf.is_view) = true; + } + + // Get author of the specified contract. + rpc GetContractAuthor (aelf.Address) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Get the code hash of the contract about the specified address. + rpc GetContractHash (aelf.Address) returns (aelf.Hash) { + option (aelf.is_view) = true; + } + + // Get the address of a system contract by its name. + rpc GetContractAddressByName (aelf.Hash) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Get the registration of a smart contract by its address. + rpc GetSmartContractRegistrationByAddress (aelf.Address) returns (aelf.SmartContractRegistration) { + option (aelf.is_view) = true; + } + + // Get the registration of a smart contract by code hash. + rpc GetSmartContractRegistrationByCodeHash (aelf.Hash) returns (aelf.SmartContractRegistration) { + option (aelf.is_view) = true; + } + + rpc GetContractCodeHashListByDeployingBlockHeight (google.protobuf.Int64Value) returns (ContractCodeHashList) { + option (aelf.is_view) = true; + } +} + +message ContractInfo +{ + // The serial number of the contract. + int64 serial_number = 1; + // The author of the contract, this is the person who deployed the contract. + aelf.Address author = 2; + // The category of contract code(0: C#). + sint32 category = 3; + // The hash of the contract code. + aelf.Hash code_hash = 4; + // Whether it is a system contract. + bool is_system_contract = 5; + // The version of the current contract. + int32 version = 6; + string contract_version = 7; + // Indicates if the contract is the user contract. + bool is_user_contract = 8; +} + +message ContractDeploymentInput { + // The category of contract code(0: C#). + sint32 category = 1; + // The byte array of the contract code. + bytes code = 2; + // The parameter of contract constructor. + bytes parameter = 3; +} + +message SystemContractDeploymentInput { + message SystemTransactionMethodCall { + // The method name of system transaction. + string method_name = 1; + // The params of system transaction method. + bytes params = 2; + } + message SystemTransactionMethodCallList { + // The list of system transactions. + repeated SystemTransactionMethodCall value = 1; + } + // The category of contract code(0: C#). + sint32 category = 1; + // The byte array of the contract code. + bytes code = 2; + // The name of the contract. It has to be unique. + aelf.Hash name = 3; + // An initial list of transactions for the system contract, + // which is executed in sequence when the contract is deployed. + SystemTransactionMethodCallList transaction_method_call_list = 4; +} + +message ContractUpdateInput { + // The contract address that needs to be updated. + aelf.Address address = 1; + // The byte array of the new contract code. + bytes code = 2; +} + +message ContractCodeCheckInput{ + // The byte array of the contract code to be checked. + bytes contract_input = 1; + // Whether the input contract is to be deployed or updated. + bool is_contract_deployment = 2; + // Method to call after code check complete(DeploySmartContract or UpdateSmartContract). + string code_check_release_method = 3; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 4; + // The category of contract code(0: C#). + sint32 category = 5; + // Indicates if the contract is the system contract. + bool is_system_contract = 6; +} + +message ContractProposed +{ + option (aelf.is_event) = true; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 1; +} + +message ContractDeployed +{ + option (aelf.is_event) = true; + // The author of the contract, this is the person who deployed the contract. + aelf.Address author = 1 [(aelf.is_indexed) = true]; + // The hash of the contract code. + aelf.Hash code_hash = 2 [(aelf.is_indexed) = true]; + // The address of the contract. + aelf.Address address = 3; + // The version of the current contract. + int32 version = 4; + // The name of the contract. It has to be unique. + aelf.Hash Name = 5; + string contract_version = 6; +} + +message CodeCheckRequired +{ + option (aelf.is_event) = true; + // The byte array of the contract code. + bytes code = 1; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 2; + // The category of contract code(0: C#). + sint32 category = 3; + // Indicates if the contract is the system contract. + bool is_system_contract = 4; + // Indicates if the contract is the user contract. + bool is_user_contract = 5; +} + +message CodeUpdated +{ + option (aelf.is_event) = true; + // The address of the updated contract. + aelf.Address address = 1 [(aelf.is_indexed) = true]; + // The byte array of the old contract code. + aelf.Hash old_code_hash = 2; + // The byte array of the new contract code. + aelf.Hash new_code_hash = 3; + // The version of the current contract. + int32 version = 4; + string contract_version = 5; +} + +message AuthorUpdated +{ + option (aelf.is_event) = true; + // The address of the contract. + aelf.Address address = 1 [(aelf.is_indexed) = true]; + // The old author of the contract. + aelf.Address old_author = 2; + // The new author of the contract. + aelf.Address new_author = 3; +} + +message ValidateSystemContractAddressInput { + // The name hash of the contract. + aelf.Hash system_contract_hash_name = 1; + // The address of the contract. + aelf.Address address = 2; +} + +message ReleaseContractInput { + // The hash of the proposal. + aelf.Hash proposal_id = 1; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 2; +} + +message ContractCodeHashList { + repeated aelf.Hash value = 1; +} + +message ContractCodeHashMap { + map value = 1; +} + +message SetContractAuthorInput{ + aelf.Address contract_address = 1; + aelf.Address new_author = 2; +} + +message DeployUserSmartContractOutput{ + aelf.Hash code_hash = 1; +} \ No newline at end of file diff --git a/basics/timelock-contract/src/Protobuf/reference/token_contract.proto b/basics/timelock-contract/src/Protobuf/reference/token_contract.proto new file mode 100644 index 0000000..b418d31 --- /dev/null +++ b/basics/timelock-contract/src/Protobuf/reference/token_contract.proto @@ -0,0 +1,511 @@ +syntax = "proto3"; + +package token; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.MultiToken"; + +service TokenContract { + // Actions + + // Multiple Token + rpc Create (CreateInput) returns (google.protobuf.Empty) { + } + rpc Issue (IssueInput) returns (google.protobuf.Empty) { + } + rpc Transfer (TransferInput) returns (google.protobuf.Empty) { + } + rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) { + } + rpc Approve (ApproveInput) returns (google.protobuf.Empty) { + } + rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { + } + rpc Lock (LockInput) returns (google.protobuf.Empty) { + } + rpc Unlock (UnlockInput) returns (google.protobuf.Empty) { + } + rpc Burn (BurnInput) returns (google.protobuf.Empty) { + } + rpc ChangeTokenIssuer (ChangeTokenIssuerInput) returns (google.protobuf.Empty) { + } + + // Cross Chain Exchange + rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) { + } + rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) { + } + rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) { + } + rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) { + } + rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) { + } + + // ACS1 - Charge Transaction Fees + rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) { + } + rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) { + } + + // ACS5 - Contract Method Call Threshold + rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) { + } + + // ACS8 - Charge Resource Tokens + rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) { + } + rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) { + } + rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){ + } + rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + rpc InitializeAuthorizedController(google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + // Views + rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) { + option (aelf.is_view) = true; + } + rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) { + option (aelf.is_view) = true; + } + rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) { + option (aelf.is_view) = true; + } + rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } + rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) { + option (aelf.is_view) = true; + } + rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + // referenced by src + rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){ + option (aelf.is_view) = true; + } + rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} + +message TokenInfo { + string symbol = 1; + string token_name = 2; + int64 supply = 3; + int64 total_supply = 4; + int32 decimals = 5; + aelf.Address issuer = 6; + bool is_burnable = 7; + int32 issue_chain_id = 9; + int64 issued = 10; +} + +message CreateInput { + string symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + aelf.Address issuer = 5; + bool is_burnable = 6; + repeated aelf.Address lock_white_list = 7; + int32 issue_chain_id = 8; +} + +message RegisterNativeTokenInfoInput { + string symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + aelf.Address issuer = 5; + bool is_burnable = 6; + int32 issue_chain_id = 7; +} + +message SetPrimaryTokenSymbolInput { + string symbol = 1; +} + +message IssueInput { + string symbol = 1; + int64 amount = 2; + string memo = 3; + aelf.Address to = 4; +} + +message TransferInput { + aelf.Address to = 1; + string symbol = 2; + int64 amount = 3; + string memo = 4; +} + +message LockInput { + aelf.Address address = 1; // The one want to lock his token. + aelf.Hash lock_id = 2; + string symbol = 3; + string usage = 4; + int64 amount = 5; +} + +message UnlockInput { + aelf.Address address = 1; // The one want to lock his token. + aelf.Hash lock_id = 2; + string symbol = 3; + string usage = 4; + int64 amount = 5; +} + +message TransferFromInput { + aelf.Address from = 1; + aelf.Address to = 2; + string symbol = 3; + int64 amount = 4; + string memo = 5; +} + +message ApproveInput { + aelf.Address spender = 1; + string symbol = 2; + int64 amount = 3; +} + +message UnApproveInput { + aelf.Address spender = 1; + string symbol = 2; + int64 amount = 3; +} + +message BurnInput { + string symbol = 1; + int64 amount = 2; +} + +message ChargeResourceTokenInput { + map cost_dic = 1; + aelf.Address caller = 2; +} + +message TransactionFeeBill { + map fees_map = 1; +} + +message CheckThresholdInput { + aelf.Address sender = 1; + map symbol_to_threshold = 2; + bool is_check_allowance = 3; +} + +message GetTokenInfoInput { + string symbol = 1; +} + +message GetBalanceInput { + string symbol = 1; + aelf.Address owner = 2; +} + +message GetBalanceOutput { + string symbol = 1; + aelf.Address owner = 2; + int64 balance = 3; +} + +message GetAllowanceInput { + string symbol = 1; + aelf.Address owner = 2; + aelf.Address spender = 3; +} + +message GetAllowanceOutput { + string symbol = 1; + aelf.Address owner = 2; + aelf.Address spender = 3; + int64 allowance = 4; +} + +message CrossChainTransferInput { + aelf.Address to = 1; + string symbol = 2; + int64 amount = 3; + string memo = 4; + int32 to_chain_id = 5; + int32 issue_chain_id = 6; +} + +message CrossChainReceiveTokenInput { + int32 from_chain_id = 1; + int64 parent_chain_height = 2; + bytes transfer_transaction_bytes = 3; + aelf.MerklePath merkle_path = 4; +} + +message IsInWhiteListInput { + string symbol = 1; + aelf.Address address = 2; +} + +message SymbolToPayTxSizeFee{ + string token_symbol = 1; + int32 base_token_weight = 2; + int32 added_token_weight = 3; +} + +message SymbolListToPayTxSizeFee{ + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1; +} + +message ChargeTransactionFeesInput { + string method_name = 1; + aelf.Address contract_address = 2; + int64 transaction_size_fee = 3; + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4; +} + +message ChargeTransactionFeesOutput { + bool success = 1; + string charging_information = 2; +} + +message ExtraTokenListModified { + option (aelf.is_event) = true; + SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1; +} + +message ReturnTaxInput { + int64 balance_before_selling = 1; + aelf.Address return_tax_receiver_address = 2; +} + +message GetLockedAmountInput { + aelf.Address address = 1; + string symbol = 2; + aelf.Hash lock_id = 3; +} + +message GetLockedAmountOutput { + aelf.Address address = 1; + string symbol = 2; + aelf.Hash lock_id = 3; + int64 amount = 4; +} + +message TokenInfoList { + repeated TokenInfo value = 1; +} + +message GetCrossChainTransferTokenContractAddressInput { + int32 chainId = 1; +} + +message CrossChainCreateTokenInput { + int32 from_chain_id = 1; + int64 parent_chain_height = 2; + bytes transaction_bytes = 3; + aelf.MerklePath merkle_path = 4; +} + +message InitializeFromParentChainInput { + map resource_amount = 1; + map registered_other_token_contract_addresses = 2; + aelf.Address creator = 3; +} + +message UpdateCoefficientsInput { + repeated int32 piece_numbers = 1;// To specify pieces gonna update. + CalculateFeeCoefficients coefficients = 2; +} + +enum FeeTypeEnum { + READ = 0; + STORAGE = 1; + WRITE = 2; + TRAFFIC = 3; + TX = 4; +} + +// Coefficients of one single piece. +// The first char is its type: liner / power. +// The second char is its piece upper bound. +message CalculateFeePieceCoefficients { + repeated int32 value = 1; +} + +// Coefficients of one single type ot token, like READ, WRITE, etc. +message CalculateFeeCoefficients { + int32 fee_token_type = 1; + repeated CalculateFeePieceCoefficients piece_coefficients_list = 2; +} + +// To include coefficients of all tokens. +message AllCalculateFeeCoefficients { + repeated CalculateFeeCoefficients value = 1; +} + +message TotalTransactionFeesMap +{ + map value = 1; + aelf.Hash block_hash = 2; + int64 block_height = 3; +} + +message TotalResourceTokensMaps { + repeated ContractTotalResourceTokens value = 1; + aelf.Hash block_hash = 2; + int64 block_height = 3; +} + +message ContractTotalResourceTokens { + aelf.Address contract_address = 1; + TotalResourceTokensMap tokens_map = 2; +} + +message TotalResourceTokensMap +{ + map value = 1; +} + +message ChangeTokenIssuerInput +{ + string symbol = 1; + aelf.Address new_token_Issuer = 2; +} + +// Events + +message Transferred { + option (aelf.is_event) = true; + aelf.Address from = 1 [(aelf.is_indexed) = true]; + aelf.Address to = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 amount = 4; + string memo = 5; +} + +message Approved { + option (aelf.is_event) = true; + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 amount = 4; +} + +message UnApproved { + option (aelf.is_event) = true; + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 amount = 4; +} + +message Burned +{ + option (aelf.is_event) = true; + aelf.Address burner = 1 [(aelf.is_indexed) = true]; + string symbol = 2 [(aelf.is_indexed) = true]; + int64 amount = 3; +} + +message ChainPrimaryTokenSymbolSet { + option (aelf.is_event) = true; + string token_symbol = 1; +} + +message TransactionSizeFeeUnitPriceUpdated { + option (aelf.is_event) = true; + int64 unit_price = 1; +} + +// The modification of each fee type will fire one event. +message CalculateFeeAlgorithmUpdated { + option (aelf.is_event) = true; + AllCalculateFeeCoefficients all_type_fee_coefficients = 1; +} + +message RentalCharged { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; +} + +message RentalAccountBalanceInsufficient { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; +} + +message TokenCreated { + option (aelf.is_event) = true; + string symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + aelf.Address issuer = 5; + bool is_burnable = 6; + int32 issue_chain_id = 7; +} + +message Issued { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; + string memo = 3; + aelf.Address to = 4; +} + +message CrossChainTransferred { + option (aelf.is_event) = true; + aelf.Address from = 1; + aelf.Address to = 2; + string symbol = 3; + int64 amount = 4; + string memo = 5; + int32 to_chain_id = 6; + int32 issue_chain_id = 7; +} + +message CrossChainReceived { + option (aelf.is_event) = true; + aelf.Address from = 1; + aelf.Address to = 2; + string symbol = 3; + int64 amount = 4; + string memo = 5; + int32 from_chain_id = 6; + int32 issue_chain_id = 7; + int64 parent_chain_height = 8; +} \ No newline at end of file diff --git a/basics/timelock-contract/src/TimelockContract.cs b/basics/timelock-contract/src/TimelockContract.cs new file mode 100644 index 0000000..b37385b --- /dev/null +++ b/basics/timelock-contract/src/TimelockContract.cs @@ -0,0 +1,101 @@ +using AElf.CSharp.Core.Extension; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.Timelock +{ + public partial class TimelockContract : TimelockContractContainer.TimelockContractBase + { + public override Empty Initialize(InitializeInput input) + { + Assert(!State.Initialized.Value, "Already initialized."); + State.GenesisContract.Value = Context.GetZeroSmartContractAddress(); + var author = State.GenesisContract.GetContractAuthor.Call(Context.Self); + Assert(author == Context.Sender, "No permission."); + Assert(input.Delay <= TimelockContractConstants.MAX_DELAY, "Delay must not exceed maximum delay"); + Assert(input.Delay >= TimelockContractConstants.MIN_DELAY, "Delay must exceed minimum delay"); + State.Admin.Value = Context.Sender; + State.Delay.Value = input.Delay; + State.Initialized.Value = true; + return new Empty(); + } + + public override Empty ChangeAdmin(ChangeAdminInput input) + { + Assert(Context.Sender == Context.Self, "No permission"); + Assert(input.Admin != null, "NewAdmin must not be null"); + State.Admin.Value = input.Admin; + Context.Fire(new NewAdmin + { + Admin = State.Admin.Value + }); + return new Empty(); + } + + public override Empty SetDelay(SetDelayInput input) + { + Assert(Context.Sender == Context.Self, "No permission"); + Assert(input.Delay <= TimelockContractConstants.MAX_DELAY, "Delay must not exceed maximum delay"); + Assert(input.Delay >= TimelockContractConstants.MIN_DELAY, "Delay must exceed minimum delay"); + State.Delay.Value = input.Delay; + Context.Fire(new NewDelay + { + Delay = State.Delay.Value + }); + return new Empty(); + } + + public override Hash QueueTransaction(TransactionInput input) + { + Assert(Context.Sender == State.Admin.Value, "No permission"); + Assert(input.ExecuteTime >= Context.CurrentBlockTime.AddSeconds((long) State.Delay.Value), "Estimated execution block must satisfy delay"); + Hash txnHash = HashHelper.ComputeFrom(input); + State.TransactionQueue[txnHash] = true; + Context.Fire(new QueueTransaction + { + Target = input.Target, + Method = input.Method, + Data = input.Data, + ExecuteTime = input.ExecuteTime + }); + return txnHash; + } + + public override Empty CancelTransaction(TransactionInput input) + { + Assert(Context.Sender == State.Admin.Value, "No permission"); + Hash txnHash = HashHelper.ComputeFrom(input); + State.TransactionQueue[txnHash] = false; + Context.Fire(new CancelTransaction + { + Target = input.Target, + Method = input.Method, + Data = input.Data, + ExecuteTime = input.ExecuteTime + }); + return new Empty(); + } + + public override Empty ExecuteTransaction(TransactionInput input) + { + Assert(Context.Sender == State.Admin.Value, "No permission"); + Hash txnHash = HashHelper.ComputeFrom(input); + Assert(State.TransactionQueue[txnHash], "executeTransaction: Transaction hasn't been queued"); + Assert(Context.CurrentBlockTime >= input.ExecuteTime, "executeTransaction: Transaction hasn't surpassed time lock"); + Assert(Context.CurrentBlockTime <= input.ExecuteTime.AddSeconds(TimelockContractConstants.GRACE_PERIOD), "executeTransaction: Transaction is stale"); + + State.TransactionQueue[txnHash] = false; + Context.SendInline(input.Target, input.Method, input.Data); + Context.Fire(new ExecuteTransaction + { + Target = input.Target, + Method = input.Method, + Data = input.Data, + ExecuteTime = input.ExecuteTime + }); + return new Empty(); + } + } + +} \ No newline at end of file diff --git a/basics/timelock-contract/src/TimelockContractConstants.cs b/basics/timelock-contract/src/TimelockContractConstants.cs new file mode 100644 index 0000000..3b155b4 --- /dev/null +++ b/basics/timelock-contract/src/TimelockContractConstants.cs @@ -0,0 +1,11 @@ +namespace AElf.Contracts.Timelock +{ + public static class TimelockContractConstants + { + public const string SYMBOL = "ELF"; + // unit is second + public const long MIN_DELAY = 1 * 24 * 60 * 60; + public const long MAX_DELAY = 7 * 24 * 60 * 60; + public const long GRACE_PERIOD = 3 * 24 * 60 * 60; + } +} \ No newline at end of file diff --git a/basics/timelock-contract/src/TimelockContractHelper.cs b/basics/timelock-contract/src/TimelockContractHelper.cs new file mode 100644 index 0000000..2dd5d4c --- /dev/null +++ b/basics/timelock-contract/src/TimelockContractHelper.cs @@ -0,0 +1,31 @@ +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.Timelock +{ + public partial class TimelockContract + { + public override UInt64Value GetDelay(Empty input) + { + var delay = new UInt64Value + { + Value = State.Delay.Value + }; + return delay; + } + + public override Address GetAdmin(Empty input) + { + return State.Admin.Value; + } + + public override BoolValue GetTransaction(Hash input) + { + return new BoolValue + { + Value = State.TransactionQueue[input] + }; + } + } + +} \ No newline at end of file diff --git a/basics/timelock-contract/src/TimelockContractReferenceState.cs b/basics/timelock-contract/src/TimelockContractReferenceState.cs new file mode 100644 index 0000000..4c5ed85 --- /dev/null +++ b/basics/timelock-contract/src/TimelockContractReferenceState.cs @@ -0,0 +1,9 @@ +using AElf.Standards.ACS0; + +namespace AElf.Contracts.Timelock +{ + public partial class TimelockContractState + { + internal ACS0Container.ACS0ReferenceState GenesisContract { get; set; } + } +} \ No newline at end of file diff --git a/basics/timelock-contract/src/TimelockContractState.cs b/basics/timelock-contract/src/TimelockContractState.cs new file mode 100644 index 0000000..8891354 --- /dev/null +++ b/basics/timelock-contract/src/TimelockContractState.cs @@ -0,0 +1,13 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.Timelock +{ + public partial class TimelockContractState : ContractState + { + public BoolState Initialized { get; set; } + public SingletonState
Admin { get; set; } + public SingletonState Delay { get; set; } + public MappedState TransactionQueue { get; set; } + } +} \ No newline at end of file diff --git a/basics/timelock-contract/test/AElf.Contracts.TimelockContract.Tests.csproj b/basics/timelock-contract/test/AElf.Contracts.TimelockContract.Tests.csproj new file mode 100644 index 0000000..4ce450e --- /dev/null +++ b/basics/timelock-contract/test/AElf.Contracts.TimelockContract.Tests.csproj @@ -0,0 +1,56 @@ + + + + net6.0 + AElf.Contracts.Timelock + + + + 0436 + + + $(OutputPath)obj/$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + nocontract + + + nocontract + + + stub + + + + + + + + + + + + \ No newline at end of file diff --git a/basics/timelock-contract/test/Protobuf/base/acs1.proto b/basics/timelock-contract/test/Protobuf/base/acs1.proto new file mode 100644 index 0000000..74b3a98 --- /dev/null +++ b/basics/timelock-contract/test/Protobuf/base/acs1.proto @@ -0,0 +1,53 @@ +/** + * AElf Standards ACS1(Transaction Fee Standard) + * + * Used to manage the transaction fee. + */ +syntax = "proto3"; + +package acs1; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; +import "Protobuf/message/authority_info.proto"; + +option (aelf.identity) = "acs1"; +option csharp_namespace = "AElf.Standards.ACS1"; + +service MethodFeeProviderContract { + + // Set the method fees for the specified method. Note that this will override all fees of the method. + rpc SetMethodFee (MethodFees) returns (google.protobuf.Empty) { + } + + // Change the method fee controller, the default is parliament and default organization. + rpc ChangeMethodFeeController (AuthorityInfo) returns (google.protobuf.Empty) { + } + + // Query method fee information by method name. + rpc GetMethodFee (google.protobuf.StringValue) returns (MethodFees) { + option (aelf.is_view) = true; + } + + // Query the method fee controller. + rpc GetMethodFeeController (google.protobuf.Empty) returns (AuthorityInfo) { + option (aelf.is_view) = true; + } +} + +message MethodFees { + // The name of the method to be charged. + string method_name = 1; + // List of fees to be charged. + repeated MethodFee fees = 2; + bool is_size_fee_free = 3;// Optional based on the implementation of SetMethodFee method. +} + +message MethodFee { + // The token symbol of the method fee. + string symbol = 1; + // The amount of fees to be charged. + int64 basic_fee = 2; +} \ No newline at end of file diff --git a/basics/timelock-contract/test/Protobuf/message/authority_info.proto b/basics/timelock-contract/test/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..1b7bc25 --- /dev/null +++ b/basics/timelock-contract/test/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.Timelock"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/basics/timelock-contract/test/Protobuf/stub/acs0.proto b/basics/timelock-contract/test/Protobuf/stub/acs0.proto new file mode 100644 index 0000000..d55d01a --- /dev/null +++ b/basics/timelock-contract/test/Protobuf/stub/acs0.proto @@ -0,0 +1,287 @@ +/** + * AElf Standards ACS0(Contract Deployment Standard) + * + * Used to manage the deployment and update of contracts. + */ +syntax = "proto3"; + +package acs0; +option csharp_namespace = "AElf.Standards.ACS0"; + +import public "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +service ACS0 { + // Deploy a system smart contract on chain and return the address of the system contract deployed. + rpc DeploySystemSmartContract (SystemContractDeploymentInput) returns (aelf.Address) { + } + + // Deploy a smart contract on chain and return the address of the contract deployed. + rpc DeploySmartContract (ContractDeploymentInput) returns (aelf.Address) { + } + + // Update a smart contract on chain. + rpc UpdateSmartContract (ContractUpdateInput) returns (aelf.Address) { + } + + // Create a proposal to deploy a new contract and returns the id of the proposed contract. + rpc ProposeNewContract (ContractDeploymentInput) returns (aelf.Hash) { + } + + // Create a proposal to check the code of a contract and return the id of the proposed contract. + rpc ProposeContractCodeCheck (ContractCodeCheckInput) returns (aelf.Hash) { + } + + // Create a proposal to update the specified contract + // and return the id of the proposed contract. + rpc ProposeUpdateContract (ContractUpdateInput) returns (aelf.Hash) { + } + + // Release the contract proposal which has been approved. + rpc ReleaseApprovedContract (ReleaseContractInput) returns (google.protobuf.Empty) { + } + + // Release the proposal which has passed the code check. + rpc ReleaseCodeCheckedContract (ReleaseContractInput) returns (google.protobuf.Empty) { + } + + // Deploy a user smart contract on chain and return the hash of the contract code. + rpc DeployUserSmartContract (ContractDeploymentInput) returns (DeployUserSmartContractOutput) { + } + + // Update a user smart contract on chain. + rpc UpdateUserSmartContract (ContractUpdateInput) returns (google.protobuf.Empty) { + } + + // Release the proposal which has passed the code check. + rpc ReleaseApprovedUserSmartContract(ReleaseContractInput) returns (google.protobuf.Empty) { + } + + // Perform user contract deployment. + rpc PerformDeployUserSmartContract(ContractDeploymentInput) returns (aelf.Address) { + } + + // Perform user contract update. + rpc PerformUpdateUserSmartContract(ContractUpdateInput) returns (google.protobuf.Empty) { + } + + // Set author of the specified contract. + rpc SetContractAuthor (SetContractAuthorInput) returns (google.protobuf.Empty) { + } + + // Validate whether the input system contract exists. + rpc ValidateSystemContractAddress(ValidateSystemContractAddressInput) returns (google.protobuf.Empty){ + } + + // Set authority of contract deployment. + rpc SetContractProposerRequiredState (google.protobuf.BoolValue) returns (google.protobuf.Empty) { + } + + // Get the current serial number of genesis contract + // (corresponds to the serial number that will be given to the next deployed contract). + rpc CurrentContractSerialNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + + // Get detailed information about the specified contract. + rpc GetContractInfo (aelf.Address) returns (ContractInfo) { + option (aelf.is_view) = true; + } + + // Get author of the specified contract. + rpc GetContractAuthor (aelf.Address) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Get the code hash of the contract about the specified address. + rpc GetContractHash (aelf.Address) returns (aelf.Hash) { + option (aelf.is_view) = true; + } + + // Get the address of a system contract by its name. + rpc GetContractAddressByName (aelf.Hash) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Get the registration of a smart contract by its address. + rpc GetSmartContractRegistrationByAddress (aelf.Address) returns (aelf.SmartContractRegistration) { + option (aelf.is_view) = true; + } + + // Get the registration of a smart contract by code hash. + rpc GetSmartContractRegistrationByCodeHash (aelf.Hash) returns (aelf.SmartContractRegistration) { + option (aelf.is_view) = true; + } + + rpc GetContractCodeHashListByDeployingBlockHeight (google.protobuf.Int64Value) returns (ContractCodeHashList) { + option (aelf.is_view) = true; + } +} + +message ContractInfo +{ + // The serial number of the contract. + int64 serial_number = 1; + // The author of the contract, this is the person who deployed the contract. + aelf.Address author = 2; + // The category of contract code(0: C#). + sint32 category = 3; + // The hash of the contract code. + aelf.Hash code_hash = 4; + // Whether it is a system contract. + bool is_system_contract = 5; + // The version of the current contract. + int32 version = 6; + string contract_version = 7; + // Indicates if the contract is the user contract. + bool is_user_contract = 8; +} + +message ContractDeploymentInput { + // The category of contract code(0: C#). + sint32 category = 1; + // The byte array of the contract code. + bytes code = 2; + // The parameter of contract constructor. + bytes parameter = 3; +} + +message SystemContractDeploymentInput { + message SystemTransactionMethodCall { + // The method name of system transaction. + string method_name = 1; + // The params of system transaction method. + bytes params = 2; + } + message SystemTransactionMethodCallList { + // The list of system transactions. + repeated SystemTransactionMethodCall value = 1; + } + // The category of contract code(0: C#). + sint32 category = 1; + // The byte array of the contract code. + bytes code = 2; + // The name of the contract. It has to be unique. + aelf.Hash name = 3; + // An initial list of transactions for the system contract, + // which is executed in sequence when the contract is deployed. + SystemTransactionMethodCallList transaction_method_call_list = 4; +} + +message ContractUpdateInput { + // The contract address that needs to be updated. + aelf.Address address = 1; + // The byte array of the new contract code. + bytes code = 2; +} + +message ContractCodeCheckInput{ + // The byte array of the contract code to be checked. + bytes contract_input = 1; + // Whether the input contract is to be deployed or updated. + bool is_contract_deployment = 2; + // Method to call after code check complete(DeploySmartContract or UpdateSmartContract). + string code_check_release_method = 3; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 4; + // The category of contract code(0: C#). + sint32 category = 5; + // Indicates if the contract is the system contract. + bool is_system_contract = 6; +} + +message ContractProposed +{ + option (aelf.is_event) = true; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 1; +} + +message ContractDeployed +{ + option (aelf.is_event) = true; + // The author of the contract, this is the person who deployed the contract. + aelf.Address author = 1 [(aelf.is_indexed) = true]; + // The hash of the contract code. + aelf.Hash code_hash = 2 [(aelf.is_indexed) = true]; + // The address of the contract. + aelf.Address address = 3; + // The version of the current contract. + int32 version = 4; + // The name of the contract. It has to be unique. + aelf.Hash Name = 5; + string contract_version = 6; +} + +message CodeCheckRequired +{ + option (aelf.is_event) = true; + // The byte array of the contract code. + bytes code = 1; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 2; + // The category of contract code(0: C#). + sint32 category = 3; + // Indicates if the contract is the system contract. + bool is_system_contract = 4; + // Indicates if the contract is the user contract. + bool is_user_contract = 5; +} + +message CodeUpdated +{ + option (aelf.is_event) = true; + // The address of the updated contract. + aelf.Address address = 1 [(aelf.is_indexed) = true]; + // The byte array of the old contract code. + aelf.Hash old_code_hash = 2; + // The byte array of the new contract code. + aelf.Hash new_code_hash = 3; + // The version of the current contract. + int32 version = 4; + string contract_version = 5; +} + +message AuthorUpdated +{ + option (aelf.is_event) = true; + // The address of the contract. + aelf.Address address = 1 [(aelf.is_indexed) = true]; + // The old author of the contract. + aelf.Address old_author = 2; + // The new author of the contract. + aelf.Address new_author = 3; +} + +message ValidateSystemContractAddressInput { + // The name hash of the contract. + aelf.Hash system_contract_hash_name = 1; + // The address of the contract. + aelf.Address address = 2; +} + +message ReleaseContractInput { + // The hash of the proposal. + aelf.Hash proposal_id = 1; + // The id of the proposed contract. + aelf.Hash proposed_contract_input_hash = 2; +} + +message ContractCodeHashList { + repeated aelf.Hash value = 1; +} + +message ContractCodeHashMap { + map value = 1; +} + +message SetContractAuthorInput{ + aelf.Address contract_address = 1; + aelf.Address new_author = 2; +} + +message DeployUserSmartContractOutput{ + aelf.Hash code_hash = 1; +} \ No newline at end of file diff --git a/basics/timelock-contract/test/Protobuf/stub/timelock_contract.proto b/basics/timelock-contract/test/Protobuf/stub/timelock_contract.proto new file mode 100644 index 0000000..09dfd6c --- /dev/null +++ b/basics/timelock-contract/test/Protobuf/stub/timelock_contract.proto @@ -0,0 +1,93 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "AElf.Contracts.Timelock"; + +service TimelockContract { + option (aelf.csharp_state) = "AElf.Contracts.Timelock.TimelockContractState"; + + // Actions + rpc Initialize (InitializeInput) returns (google.protobuf.Empty) { + } + rpc SetDelay (SetDelayInput) returns (google.protobuf.Empty) { + } + rpc ChangeAdmin (ChangeAdminInput) returns (google.protobuf.Empty) { + } + rpc QueueTransaction (TransactionInput) returns (aelf.Hash) { + } + rpc ExecuteTransaction (TransactionInput) returns (google.protobuf.Empty) { + } + rpc CancelTransaction (TransactionInput) returns (google.protobuf.Empty) { + } + + // View + rpc GetDelay (google.protobuf.Empty) returns (google.protobuf.UInt64Value) { + option (aelf.is_view) = true; + } + rpc GetAdmin (google.protobuf.Empty) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetTransaction(aelf.Hash) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} + +// Params +message InitializeInput { + uint64 delay = 1; +} + +message SetDelayInput { + uint64 delay = 1; +} + +message ChangeAdminInput { + aelf.Address admin = 1; +} + +message TransactionInput { + aelf.Address target = 1; + string method = 2; + bytes data = 3; + google.protobuf.Timestamp execute_time = 4; +} + +// Events +message NewAdmin { + option (aelf.is_event) = true; + aelf.Address admin = 1; +} + +message NewDelay { + option (aelf.is_event) = true; + uint64 delay = 1; +} + +message QueueTransaction { + option (aelf.is_event) = true; + aelf.Address target = 1; + string method = 2; + bytes data = 3; // data equivalent to ConvertToByteString(IMessage) + google.protobuf.Timestamp execute_time = 4; +} + +message ExecuteTransaction { + option (aelf.is_event) = true; + aelf.Address target = 1; + string method = 2; + bytes data = 3; + google.protobuf.Timestamp execute_time = 4; +} + +message CancelTransaction { + option (aelf.is_event) = true; + aelf.Address target = 1; + string method = 2; + bytes data = 3; + google.protobuf.Timestamp execute_time = 4; +} \ No newline at end of file diff --git a/basics/timelock-contract/test/Protobuf/stub/token_contract.proto b/basics/timelock-contract/test/Protobuf/stub/token_contract.proto new file mode 100644 index 0000000..31313c8 --- /dev/null +++ b/basics/timelock-contract/test/Protobuf/stub/token_contract.proto @@ -0,0 +1,511 @@ +syntax = "proto3"; + +package token; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.MultiToken"; + +service TokenContract { + // Actions + + // Multiple Token + rpc Create (CreateInput) returns (google.protobuf.Empty) { + } + rpc Issue (IssueInput) returns (google.protobuf.Empty) { + } + rpc Transfer (TransferInput) returns (google.protobuf.Empty) { + } + rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) { + } + rpc Approve (ApproveInput) returns (google.protobuf.Empty) { + } + rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { + } + rpc Lock (LockInput) returns (google.protobuf.Empty) { + } + rpc Unlock (UnlockInput) returns (google.protobuf.Empty) { + } + rpc Burn (BurnInput) returns (google.protobuf.Empty) { + } + rpc ChangeTokenIssuer (ChangeTokenIssuerInput) returns (google.protobuf.Empty) { + } + + // Cross Chain Exchange + rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) { + } + rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) { + } + rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) { + } + rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) { + } + rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) { + } + + // ACS1 - Charge Transaction Fees + rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) { + } + rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) { + } + + // ACS5 - Contract Method Call Threshold + rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) { + } + + // ACS8 - Charge Resource Tokens + rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) { + } + rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) { + } + rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){ + } + rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + rpc InitializeAuthorizedController(google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + // Views + rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) { + option (aelf.is_view) = true; + } + rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) { + option (aelf.is_view) = true; + } + rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) { + option (aelf.is_view) = true; + } + rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } + rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) { + option (aelf.is_view) = true; + } + rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) { + option (aelf.is_view) = true; + } + rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + // referenced by src + rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){ + option (aelf.is_view) = true; + } + rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} + +message TokenInfo { + string symbol = 1; + string token_name = 2; + int64 supply = 3; + int64 total_supply = 4; + int32 decimals = 5; + aelf.Address issuer = 6; + bool is_burnable = 7; + int32 issue_chain_id = 9; + int64 issued = 10; +} + +message CreateInput { + string symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + aelf.Address issuer = 5; + bool is_burnable = 6; + repeated aelf.Address lock_white_list = 7; + int32 issue_chain_id = 8; +} + +message RegisterNativeTokenInfoInput { + string symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + aelf.Address issuer = 5; + bool is_burnable = 6; + int32 issue_chain_id = 7; +} + +message SetPrimaryTokenSymbolInput { + string symbol = 1; +} + +message IssueInput { + string symbol = 1; + int64 amount = 2; + string memo = 3; + aelf.Address to = 4; +} + +message TransferInput { + aelf.Address to = 1; + string symbol = 2; + int64 amount = 3; + string memo = 4; +} + +message LockInput { + aelf.Address address = 1; // The one want to lock his token. + aelf.Hash lock_id = 2; + string symbol = 3; + string usage = 4; + int64 amount = 5; +} + +message UnlockInput { + aelf.Address address = 1; // The one want to lock his token. + aelf.Hash lock_id = 2; + string symbol = 3; + string usage = 4; + int64 amount = 5; +} + +message TransferFromInput { + aelf.Address from = 1; + aelf.Address to = 2; + string symbol = 3; + int64 amount = 4; + string memo = 5; +} + +message ApproveInput { + aelf.Address spender = 1; + string symbol = 2; + int64 amount = 3; +} + +message UnApproveInput { + aelf.Address spender = 1; + string symbol = 2; + int64 amount = 3; +} + +message BurnInput { + string symbol = 1; + int64 amount = 2; +} + +message ChargeResourceTokenInput { + map cost_dic = 1; + aelf.Address caller = 2; +} + +message TransactionFeeBill { + map fees_map = 1; +} + +message CheckThresholdInput { + aelf.Address sender = 1; + map symbol_to_threshold = 2; + bool is_check_allowance = 3; +} + +message GetTokenInfoInput { + string symbol = 1; +} + +message GetBalanceInput { + string symbol = 1; + aelf.Address owner = 2; +} + +message GetBalanceOutput { + string symbol = 1; + aelf.Address owner = 2; + int64 balance = 3; +} + +message GetAllowanceInput { + string symbol = 1; + aelf.Address owner = 2; + aelf.Address spender = 3; +} + +message GetAllowanceOutput { + string symbol = 1; + aelf.Address owner = 2; + aelf.Address spender = 3; + int64 allowance = 4; +} + +message CrossChainTransferInput { + aelf.Address to = 1; + string symbol = 2; + int64 amount = 3; + string memo = 4; + int32 to_chain_id = 5; + int32 issue_chain_id = 6; +} + +message CrossChainReceiveTokenInput { + int32 from_chain_id = 1; + int64 parent_chain_height = 2; + bytes transfer_transaction_bytes = 3; + aelf.MerklePath merkle_path = 4; +} + +message IsInWhiteListInput { + string symbol = 1; + aelf.Address address = 2; +} + +message SymbolToPayTxSizeFee{ + string token_symbol = 1; + int32 base_token_weight = 2; + int32 added_token_weight = 3; +} + +message SymbolListToPayTxSizeFee{ + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1; +} + +message ChargeTransactionFeesInput { + string method_name = 1; + aelf.Address contract_address = 2; + int64 transaction_size_fee = 3; + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4; +} + +message ChargeTransactionFeesOutput { + bool success = 1; + string charging_information = 2; +} + +message ExtraTokenListModified { + option (aelf.is_event) = true; + SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1; +} + +message ReturnTaxInput { + int64 balance_before_selling = 1; + aelf.Address return_tax_receiver_address = 2; +} + +message GetLockedAmountInput { + aelf.Address address = 1; + string symbol = 2; + aelf.Hash lock_id = 3; +} + +message GetLockedAmountOutput { + aelf.Address address = 1; + string symbol = 2; + aelf.Hash lock_id = 3; + int64 amount = 4; +} + +message TokenInfoList { + repeated TokenInfo value = 1; +} + +message GetCrossChainTransferTokenContractAddressInput { + int32 chainId = 1; +} + +message CrossChainCreateTokenInput { + int32 from_chain_id = 1; + int64 parent_chain_height = 2; + bytes transaction_bytes = 3; + aelf.MerklePath merkle_path = 4; +} + +message InitializeFromParentChainInput { + map resource_amount = 1; + map registered_other_token_contract_addresses = 2; + aelf.Address creator = 3; +} + +message UpdateCoefficientsInput { + repeated int32 piece_numbers = 1;// To specify pieces gonna update. + CalculateFeeCoefficients coefficients = 2; +} + +enum FeeTypeEnum { + READ = 0; + STORAGE = 1; + WRITE = 2; + TRAFFIC = 3; + TX = 4; +} + +// Coefficients of one single piece. +// The first char is its type: liner / power. +// The second char is its piece upper bound. +message CalculateFeePieceCoefficients { + repeated int32 value = 1; +} + +// Coefficients of one single type ot token, like READ, WRITE, etc. +message CalculateFeeCoefficients { + int32 fee_token_type = 1; + repeated CalculateFeePieceCoefficients piece_coefficients_list = 2; +} + +// To include coefficients of all tokens. +message AllCalculateFeeCoefficients { + repeated CalculateFeeCoefficients value = 1; +} + +message TotalTransactionFeesMap +{ + map value = 1; + aelf.Hash block_hash = 2; + int64 block_height = 3; +} + +message TotalResourceTokensMaps { + repeated ContractTotalResourceTokens value = 1; + aelf.Hash block_hash = 2; + int64 block_height = 3; +} + +message ContractTotalResourceTokens { + aelf.Address contract_address = 1; + TotalResourceTokensMap tokens_map = 2; +} + +message TotalResourceTokensMap +{ + map value = 1; +} + +message ChangeTokenIssuerInput +{ + string symbol = 1; + aelf.Address new_token_Issuer = 2; +} + +// Events + +message Transferred { + option (aelf.is_event) = true; + aelf.Address from = 1 [(aelf.is_indexed) = true]; + aelf.Address to = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 amount = 4; + string memo = 5; +} + +message Approved { + option (aelf.is_event) = true; + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 amount = 4; +} + +message UnApproved { + option (aelf.is_event) = true; + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + int64 amount = 4; +} + +message Burned +{ + option (aelf.is_event) = true; + aelf.Address burner = 1 [(aelf.is_indexed) = true]; + string symbol = 2 [(aelf.is_indexed) = true]; + int64 amount = 3; +} + +message ChainPrimaryTokenSymbolSet { + option (aelf.is_event) = true; + string token_symbol = 1; +} + +message TransactionSizeFeeUnitPriceUpdated { + option (aelf.is_event) = true; + int64 unit_price = 1; +} + +// The modification of each fee type will fire one event. +message CalculateFeeAlgorithmUpdated { + option (aelf.is_event) = true; + AllCalculateFeeCoefficients all_type_fee_coefficients = 1; +} + +message RentalCharged { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; +} + +message RentalAccountBalanceInsufficient { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; +} + +message TokenCreated { + option (aelf.is_event) = true; + string symbol = 1; + string token_name = 2; + int64 total_supply = 3; + int32 decimals = 4; + aelf.Address issuer = 5; + bool is_burnable = 6; + int32 issue_chain_id = 7; +} + +message Issued { + option (aelf.is_event) = true; + string symbol = 1; + int64 amount = 2; + string memo = 3; + aelf.Address to = 4; +} + +message CrossChainTransferred { + option (aelf.is_event) = true; + aelf.Address from = 1; + aelf.Address to = 2; + string symbol = 3; + int64 amount = 4; + string memo = 5; + int32 to_chain_id = 6; + int32 issue_chain_id = 7; +} + +message CrossChainReceived { + option (aelf.is_event) = true; + aelf.Address from = 1; + aelf.Address to = 2; + string symbol = 3; + int64 amount = 4; + string memo = 5; + int32 from_chain_id = 6; + int32 issue_chain_id = 7; + int64 parent_chain_height = 8; +} \ No newline at end of file diff --git a/basics/timelock-contract/test/TimelockContractTests.cs b/basics/timelock-contract/test/TimelockContractTests.cs new file mode 100644 index 0000000..01a7cbb --- /dev/null +++ b/basics/timelock-contract/test/TimelockContractTests.cs @@ -0,0 +1,640 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using AElf.Contracts.MultiToken; +using AElf.CSharp.Core; +using AElf.CSharp.Core.Extension; +using AElf.Kernel; +using AElf.Types; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; + +namespace AElf.Contracts.Timelock +{ + public partial class TimelockContractTests : TestBase + { + private readonly Timestamp _currentTime = TimestampHelper.GetUtcNow(); + + [Fact] + public async Task InitializeTests() + { + ulong delay = 2 * 24 * 60 * 60; + InitializeInput input = new InitializeInput + { + Delay = delay + }; + await TimelockContractStub.Initialize.SendAsync(input); + var getDelay = await TimelockContractStub.GetDelay.CallAsync(new Empty()); + delay.ShouldBe(getDelay.Value); + } + + [Fact] + public async Task InitializeDuplicateTests() + { + InitializeInput input = new InitializeInput + { + Delay = 2 * 24 * 60 * 60 + }; + try + { + await TimelockContractStub.Initialize.SendAsync(input); + await TimelockContractStub.Initialize.SendAsync(input); + } + catch (Exception e) + { + e.Message.ShouldContain("Already initialized."); + } + } + + [Fact] + public async Task ChangeAdminTests() + { + await InitializeTests(); + var address = await TimelockContractStub.GetAdmin.CallAsync(new Empty()); + address.ShouldBe(DefaultAddress); + ChangeAdminInput changeAdminInput = new ChangeAdminInput() + { + Admin = UserAddress + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TimelockContractAddress, + Method = "ChangeAdmin", + Data = changeAdminInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + var txnHash = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(3 * 24 * 60 * 60)); + var txRes = await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + + var addressAfter = await TimelockContractStub.GetAdmin.CallAsync(new Empty()); + addressAfter.ShouldBe(UserAddress); + + var newAdminEvent = NewAdmin.Parser + .ParseFrom(txRes.TransactionResult.Logs.First(l => l.Name.Contains(nameof(NewAdmin))) + .NonIndexed); + newAdminEvent.Admin.ShouldBe(changeAdminInput.Admin); + } + + [Fact] + public async Task ChangeAdminTests_NoPermission() + { + await InitializeTests(); + ChangeAdminInput changeAdminInput = new ChangeAdminInput() + { + Admin = UserAddress + }; + try + { + await TimelockContractStub.ChangeAdmin.SendAsync(changeAdminInput); + } + catch (Exception e) + { + e.Message.ShouldContain("No permission"); + } + var addressAfter = await TimelockContractStub.GetAdmin.CallAsync(new Empty()); + addressAfter.ShouldBe(DefaultAddress); + } + + [Fact] + public async Task ChangeAdminTests_InputNull() + { + await InitializeTests(); + var address = await TimelockContractStub.GetAdmin.CallAsync(new Empty()); + address.ShouldBe(DefaultAddress); + ChangeAdminInput changeAdminInput = new ChangeAdminInput() + { + Admin = null + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TimelockContractAddress, + Method = "ChangeAdmin", + Data = changeAdminInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + var txnHash = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(3 * 24 * 60 * 60)); + + try + { + await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("NewAdmin must not be null"); + } + var addressAfter = await TimelockContractStub.GetAdmin.CallAsync(new Empty()); + addressAfter.ShouldBe(DefaultAddress); + } + + [Fact] + public async Task SetDelayTests() + { + await InitializeTests(); + UInt64Value delay = await TimelockContractStub.GetDelay.CallAsync(new Empty()); + delay.Value.ShouldBe((uint)(2 * 24 * 60 * 60)); + SetDelayInput setDelayInput = new SetDelayInput + { + Delay = 4 * 24 * 60 * 60 + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TimelockContractAddress, + Method = "SetDelay", + Data = setDelayInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + var txnHash = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(3 * 24 * 60 * 60)); + var txRes = await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + + UInt64Value delayAfter = await TimelockContractStub.GetDelay.CallAsync(new Empty()); + delayAfter.Value.ShouldBe((uint)(4 * 24 * 60 * 60)); + + var newDelayEvent = NewDelay.Parser + .ParseFrom(txRes.TransactionResult.Logs.First(l => l.Name.Contains(nameof(NewDelay))) + .NonIndexed); + newDelayEvent.Delay.ShouldBe(setDelayInput.Delay); + } + + [Fact] + public async Task SetDelayTests_NoPermission() + { + await InitializeTests(); + UInt64Value delay = await TimelockContractStub.GetDelay.CallAsync(new Empty()); + delay.Value.ShouldBe((uint)(2 * 24 * 60 * 60)); + SetDelayInput setDelayInput = new SetDelayInput + { + Delay = 4 * 24 * 60 * 60 + }; + try + { + await TimelockContractStub.SetDelay.SendAsync(setDelayInput); + } + catch (Exception e) + { + e.Message.ShouldContain("No Permission"); + } + } + + [Fact] + public async Task QueueTransactionTests() + { + await InitializeTests(); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + Hash txnHash = HashHelper.ComputeFrom(transactionInput); + var txRes = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash); + var expectResult = new BoolValue + { + Value = true + }; + result.ShouldBe(expectResult); + + var queueTransactionEvent = QueueTransaction.Parser + .ParseFrom(txRes.TransactionResult.Logs.First(l => l.Name.Contains(nameof(QueueTransaction))) + .NonIndexed); + queueTransactionEvent.Target.ShouldBe(transactionInput.Target); + queueTransactionEvent.Method.ShouldBe(transactionInput.Method); + queueTransactionEvent.Data.ShouldBe(transactionInput.Data); + queueTransactionEvent.ExecuteTime.ShouldBe(transactionInput.ExecuteTime); + } + + [Fact] + public async Task QueueTransactionTests_NoPermission() + { + await ChangeAdminTests(); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + Hash txnHash = HashHelper.ComputeFrom(transactionInput); + try + { + await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("No Permission"); + } + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash); + var expectResult = new BoolValue + { + Value = false + }; + result.ShouldBe(expectResult); + } + + [Fact] + public async Task QueueTransactionTests_TimeLimit() + { + await InitializeTests(); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(-(1 * 24 * 60 * 60)) + }; + Hash txnHash = HashHelper.ComputeFrom(transactionInput); + try + { + await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("Estimated execution block must satisfy delay"); + } + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash); + var expectResult = new BoolValue + { + Value = false + }; + result.ShouldBe(expectResult); + } + + [Fact] + public async Task CancelTransactionTests() + { + await InitializeTests(); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + Hash txnHash = HashHelper.ComputeFrom(transactionInput); + await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + var txRes = await TimelockContractStub.CancelTransaction.SendAsync(transactionInput); + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash); + var expectResult = new BoolValue + { + Value = false + }; + result.ShouldBe(expectResult); + + var cancelTransactionEvent = CancelTransaction.Parser + .ParseFrom(txRes.TransactionResult.Logs.First(l => l.Name.Contains(nameof(CancelTransaction))) + .NonIndexed); + cancelTransactionEvent.Target.ShouldBe(transactionInput.Target); + cancelTransactionEvent.Method.ShouldBe(transactionInput.Method); + cancelTransactionEvent.Data.ShouldBe(transactionInput.Data); + cancelTransactionEvent.ExecuteTime.ShouldBe(transactionInput.ExecuteTime); + } + + [Fact] + public async Task CancelTransactionTests_NoPermission() + { + await InitializeTests(); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + Hash txnHash = HashHelper.ComputeFrom(transactionInput); + await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + + await ChangeAdminForTestPermission(); + + try + { + await TimelockContractStub.CancelTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("No Permission"); + } + + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash); + var expectResult = new BoolValue + { + Value = true + }; + result.ShouldBe(expectResult); + } + + /** + * Because of time lock limit, need to comment out the assert of the ExecuteTransaction method. + */ + [Fact] + public async Task ExecuteTransactionTests() + { + await InitializeTests(); + await InitializeAsync(); + var balance1 = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ELF" + }); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + var txnHash = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(3 * 24 * 60 * 60)); + + var txRes = await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + + var balance2 = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = UserAddress, + Symbol = "ELF" + }); + var balance3 = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ELF" + }); + balance2.Balance.ShouldBe(502); + balance3.Balance.ShouldBe(balance1.Balance.Sub(502)); + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash.Output); + var expectResult = new BoolValue + { + Value = false + }; + result.ShouldBe(expectResult); + + var executeTransactionEvent = ExecuteTransaction.Parser + .ParseFrom(txRes.TransactionResult.Logs.First(l => l.Name.Contains(nameof(ExecuteTransaction))) + .NonIndexed); + executeTransactionEvent.Target.ShouldBe(transactionInput.Target); + executeTransactionEvent.Method.ShouldBe(transactionInput.Method); + executeTransactionEvent.Data.ShouldBe(transactionInput.Data); + executeTransactionEvent.ExecuteTime.ShouldBe(transactionInput.ExecuteTime); + } + + [Fact] + public async Task ExecuteTransactionTests_NoPermission() + { + await InitializeTests(); + await InitializeAsync(); + var balance1 = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ELF" + }); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + var txnHash = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + + await ChangeAdminForTestPermission(); + + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(3 * 24 * 60 * 60)); + + try + { + await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("No Permission"); + } + + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash.Output); + var expectResult = new BoolValue + { + Value = true + }; + result.ShouldBe(expectResult); + } + + [Fact] + public async Task ExecuteTransactionTests_NotInQueue() + { + await InitializeTests(); + await InitializeAsync(); + var balance1 = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ELF" + }); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(3 * 24 * 60 * 60)); + + try + { + await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("executeTransaction: Transaction hasn't been queued"); + } + } + + [Fact] + public async Task ExecuteTransactionTests_NotReachExecuteTime() + { + await InitializeTests(); + await InitializeAsync(); + var balance1 = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ELF" + }); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + var txnHash = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(2 * 24 * 60 * 60)); + + try + { + await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("executeTransaction: Transaction hasn't surpassed time lock"); + } + + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash.Output); + var expectResult = new BoolValue + { + Value = true + }; + result.ShouldBe(expectResult); + } + + [Fact] + public async Task ExecuteTransactionTests_ExceedGracePeriod() + { + await InitializeTests(); + await InitializeAsync(); + var balance1 = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = DefaultAddress, + Symbol = "ELF" + }); + TransferFromInput transferFromInput = new TransferFromInput + { + From = DefaultAddress, + To = UserAddress, + Amount = 502, + Symbol = "ELF", + Memo = "TEST" + }; + TransactionInput transactionInput = new TransactionInput + { + Target = TokenContractAddress, + Method = "TransferFrom", + Data = transferFromInput.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + var txnHash = await TimelockContractStub.QueueTransaction.SendAsync(transactionInput); + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(8 * 24 * 60 * 60)); + + try + { + await TimelockContractStub.ExecuteTransaction.SendAsync(transactionInput); + } + catch (Exception e) + { + e.Message.ShouldContain("executeTransaction: Transaction is stale"); + } + + var result = await TimelockContractStub.GetTransaction.CallAsync(txnHash.Output); + var expectResult = new BoolValue + { + Value = true + }; + result.ShouldBe(expectResult); + } + + private async Task InitializeAsync() + { + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + To = TimelockContractAddress, + Symbol = "ELF", + Amount = 1000_00000000 + }); + await TokenContractStub.Approve.SendAsync(new ApproveInput + { + Spender = TimelockContractAddress, + Symbol = "ELF", + Amount = 1000_00000000 + }); + } + + private async Task ChangeAdminForTestPermission() + { + TransactionInput changeAdminTransactionInput = new TransactionInput + { + Target = TimelockContractAddress, + Method = "ChangeAdmin", + Data = new ChangeAdminInput() {Admin = UserAddress}.ToByteString(), + ExecuteTime = _currentTime.AddSeconds(3 * 24 * 60 * 60) + }; + await TimelockContractStub.QueueTransaction.SendAsync(changeAdminTransactionInput); + BlockTimeProvider.SetBlockTime(_currentTime.AddSeconds(3 * 24 * 60 * 60)); + await TimelockContractStub.ExecuteTransaction.SendAsync(changeAdminTransactionInput); + } + + } + +} \ No newline at end of file diff --git a/basics/timelock-contract/test/_Setup.cs b/basics/timelock-contract/test/_Setup.cs new file mode 100644 index 0000000..e8cf478 --- /dev/null +++ b/basics/timelock-contract/test/_Setup.cs @@ -0,0 +1,78 @@ +using System.IO; +using AElf.Contracts.MultiToken; +using AElf.ContractTestBase.ContractTestKit; +using AElf.Cryptography.ECDSA; +using AElf.Kernel; +using AElf.Kernel.SmartContract; +using AElf.Standards.ACS0; +using AElf.Types; +using Google.Protobuf; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace AElf.Contracts.Timelock +{ + // This class is used to load the context required for unit testing. + public class Module : Testing.TestBase.ContractTestModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddSingleton(); + Configure(o => o.ContractDeploymentAuthorityRequired = false); + } + } + + // The TestBase class inherit ContractTestBase class, which is used to define and get stub classes required for unit testing. + public class TestBase : Testing.TestBase.ContractTestBase + { + // You can get address of any contract via GetAddress method, for example: + // internal Address ContractAddress => GetAddress(SmartContractAddressNameProvider.StringName); + // Using the address and key to get stub, Like this: + // TokenContractContainer.TokenContractStub stub = GetTester(TokenContractAddress, keyPair); + + // private readonly ECKeyPair KeyPair; + internal TimelockContractContainer.TimelockContractStub TimelockContractStub; + internal TokenContractContainer.TokenContractStub TokenContractStub; + internal ACS0Container.ACS0Stub ZeroContractStub; + + protected ECKeyPair DefaultKeyPair => Accounts[0].KeyPair; + protected Address DefaultAddress => Accounts[0].Address; + protected Address UserAddress => Accounts[1].Address; + protected Address TimelockContractAddress; + protected IBlockTimeProvider BlockTimeProvider => + Application.ServiceProvider.GetRequiredService(); + + public TestBase() + { + ZeroContractStub = GetContractZeroTester(DefaultKeyPair); + var result = AsyncHelper.RunSync(async () =>await ZeroContractStub.DeploySmartContract.SendAsync(new ContractDeploymentInput + { + Category = KernelConstants.CodeCoverageRunnerCategory, + Code = ByteString.CopyFrom( + File.ReadAllBytes(typeof(TimelockContract).Assembly.Location)) + })); + + TimelockContractAddress = Address.Parser.ParseFrom(result.TransactionResult.ReturnValue); + // KeyPair = SampleAccount.Accounts.First().KeyPair; + TimelockContractStub = GetTimelockContractStub(DefaultKeyPair); + TokenContractStub = GetTokenContractStub(DefaultKeyPair); + } + + private TimelockContractContainer.TimelockContractStub GetTimelockContractStub(ECKeyPair senderKeyPair) + { + return GetTester(TimelockContractAddress, senderKeyPair); + } + + private TokenContractContainer.TokenContractStub GetTokenContractStub(ECKeyPair senderKeyPair) + { + return GetTester(TokenContractAddress, senderKeyPair); + } + + private ACS0Container.ACS0Stub GetContractZeroTester(ECKeyPair keyPair) + { + return GetTester(BasicContractZeroAddress, keyPair); + } + } + +} \ No newline at end of file diff --git a/build.cake b/build.cake new file mode 100644 index 0000000..cfc3109 --- /dev/null +++ b/build.cake @@ -0,0 +1,78 @@ +#tool nuget:?package=Codecov +#addin nuget:?package=Cake.Codecov&version=0.8.0 + +var target = Argument("target", "Default"); +var configuration = Argument("configuration", "Debug"); +var rootPath = "./"; +var srcPath = rootPath + "**/src/"; +var testPath = rootPath + "**/test/"; +var solution = rootPath + "ExampleContract.sln"; + +Task("Clean") + .Description("clean up project cache") + .Does(() => +{ + CleanDirectories(srcPath + "**/bin"); + CleanDirectories(srcPath + "**/obj"); + CleanDirectories(testPath + "**/bin"); + CleanDirectories(testPath + "**/obj"); +}); + +Task("Restore") + .Description("restore project dependencies") + .Does(() => +{ + DotNetCoreRestore(solution, new DotNetCoreRestoreSettings + { + Verbosity = DotNetCoreVerbosity.Quiet, + Sources = new [] { "https://www.myget.org/F/aelf-project-dev/api/v3/index.json", "https://api.nuget.org/v3/index.json" } + }); +}); +Task("Build") + .Description("Compilation project") + .IsDependentOn("Clean") + .IsDependentOn("Restore") + .Does(() => +{ + var buildSetting = new DotNetCoreBuildSettings{ + NoRestore = true, + Configuration = configuration, + ArgumentCustomization = args => { + return args.Append("/clp:ErrorsOnly") + .Append("-v quiet");} + }; + + DotNetCoreBuild(solution, buildSetting); +}); + +Task("Test-with-Codecov") + .Description("operation tes") + .IsDependentOn("Build") + .Does(() => +{ + var testSetting = new DotNetCoreTestSettings{ + Configuration = configuration, + NoRestore = true, + NoBuild = true, + ArgumentCustomization = args => { + return args + .Append("--logger trx") + .Append("--settings CodeCoverage.runsettings") + .Append("--collect:\"XPlat Code Coverage\""); + } + }; + var testProjects = GetFiles("./**/test/*.csproj"); + var testProjectList = testProjects.OrderBy(p=>p.FullPath).ToList(); + foreach(var testProject in testProjectList) + { + DotNetCoreTest(testProject.FullPath, testSetting); + } +}); + +Task("Upload-Coverage-Azure") + .Does(() => +{ + Codecov("./CodeCoverage/Cobertura.xml",EnvironmentVariable("CODECOV_TOKEN")); +}); + +RunTarget(target); diff --git a/build.config b/build.config new file mode 100644 index 0000000..6b20136 --- /dev/null +++ b/build.config @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +CAKE_VERSION=0.37.0 +DOTNET_VERSION=7.0.100 \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..e48d09a --- /dev/null +++ b/build.ps1 @@ -0,0 +1,154 @@ +#!/usr/bin/env pwsh +$DotNetInstallerUri = 'https://dot.net/v1/dotnet-install.ps1'; +$DotNetUnixInstallerUri = 'https://dot.net/v1/dotnet-install.sh' +$DotNetChannel = 'LTS' +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent + +[string] $CakeVersion = '' +[string] $DotNetVersion= '' +foreach($line in Get-Content (Join-Path $PSScriptRoot 'build.config')) +{ + if ($line -like 'CAKE_VERSION=*') { + $CakeVersion = $line.SubString(13) + } + elseif ($line -like 'DOTNET_VERSION=*') { + $DotNetVersion =$line.SubString(15) + } +} + + +if ([string]::IsNullOrEmpty($CakeVersion) -or [string]::IsNullOrEmpty($DotNetVersion)) { + 'Failed to parse Cake / .NET Core SDK Version' + exit 1 +} + +# Make sure tools folder exists +$ToolPath = Join-Path $PSScriptRoot "tools" +if (!(Test-Path $ToolPath)) { + Write-Verbose "Creating tools directory..." + New-Item -Path $ToolPath -Type Directory -Force | out-null +} + + +if ($PSVersionTable.PSEdition -ne 'Core') { + # Attempt to set highest encryption available for SecurityProtocol. + # PowerShell will not set this by default (until maybe .NET 4.6.x). This + # will typically produce a message for PowerShell v2 (just an info + # message though) + try { + # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48) + # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't + # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is + # installed (.NET 4.5 is an in-place upgrade). + [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48 + } catch { + Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3' + } +} + +########################################################################### +# INSTALL .NET CORE CLI +########################################################################### + +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT=1 +$env:DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 + + +Function Remove-PathVariable([string]$VariableToRemove) +{ + $SplitChar = ';' + if ($IsMacOS -or $IsLinux) { + $SplitChar = ':' + } + + $path = [Environment]::GetEnvironmentVariable("PATH", "User") + if ($path -ne $null) + { + $newItems = $path.Split($SplitChar, [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } + [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join($SplitChar, $newItems), "User") + } + + $path = [Environment]::GetEnvironmentVariable("PATH", "Process") + if ($path -ne $null) + { + $newItems = $path.Split($SplitChar, [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } + [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join($SplitChar, $newItems), "Process") + } +} + +# Get .NET Core CLI path if installed. +$FoundDotNetCliVersion = $null; +if (Get-Command dotnet -ErrorAction SilentlyContinue) { + $FoundDotNetCliVersion = dotnet --version; +} + +if($FoundDotNetCliVersion -ne $DotNetVersion) { + $InstallPath = Join-Path $PSScriptRoot ".dotnet" + if (!(Test-Path $InstallPath)) { + New-Item -Path $InstallPath -ItemType Directory -Force | Out-Null; + } + + if ($IsMacOS -or $IsLinux) { + $ScriptPath = Join-Path $InstallPath 'dotnet-install.sh' + (New-Object System.Net.WebClient).DownloadFile($DotNetUnixInstallerUri, $ScriptPath); + & bash $ScriptPath --version "$DotNetVersion" --install-dir "$InstallPath" --channel "$DotNetChannel" --no-path + + Remove-PathVariable "$InstallPath" + $env:PATH = "$($InstallPath):$env:PATH" + } + else { + $ScriptPath = Join-Path $InstallPath 'dotnet-install.ps1' + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallerUri, $ScriptPath); + & $ScriptPath -Channel $DotNetChannel -Version $DotNetVersion -InstallDir $InstallPath; + + Remove-PathVariable "$InstallPath" + $env:PATH = "$InstallPath;$env:PATH" + } + $env:DOTNET_ROOT=$InstallPath +} + +########################################################################### +# INSTALL CAKE +########################################################################### + +# Make sure Cake has been installed. +[string] $CakeExePath = '' +[string] $CakeInstalledVersion = Get-Command dotnet-cake -ErrorAction SilentlyContinue | % {&$_.Source --version} + +if ($CakeInstalledVersion -eq $CakeVersion) { + # Cake found locally + $CakeExePath = (Get-Command dotnet-cake).Source +} +else { + $CakePath = [System.IO.Path]::Combine($ToolPath,'.store', 'cake.tool', $CakeVersion) # Old PowerShell versions Join-Path only supports one child path + + $CakeExePath = (Get-ChildItem -Path $ToolPath -Filter "dotnet-cake*" -File| ForEach-Object FullName | Select-Object -First 1) + + + if ((!(Test-Path -Path $CakePath -PathType Container)) -or (!(Test-Path $CakeExePath -PathType Leaf))) { + + if ((![string]::IsNullOrEmpty($CakeExePath)) -and (Test-Path $CakeExePath -PathType Leaf)) + { + & dotnet tool uninstall --tool-path $ToolPath Cake.Tool + } + + & dotnet tool install --tool-path $ToolPath --version $CakeVersion Cake.Tool + if ($LASTEXITCODE -ne 0) + { + 'Failed to install cake' + exit 1 + } + $CakeExePath = (Get-ChildItem -Path $ToolPath -Filter "dotnet-cake*" -File| ForEach-Object FullName | Select-Object -First 1) + } +} + +########################################################################### +# RUN BUILD SCRIPT +########################################################################### +& "$CakeExePath" ./build.cake --bootstrap +if ($LASTEXITCODE -eq 0) +{ + & "$CakeExePath" ./build.cake $args +} +exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..06bcee8 --- /dev/null +++ b/build.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Define varibles +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +source $SCRIPT_DIR/build.config +TOOLS_DIR=$SCRIPT_DIR/tools +CAKE_EXE=$TOOLS_DIR/dotnet-cake +CAKE_PATH=$TOOLS_DIR/.store/cake.tool/$CAKE_VERSION + +if [ "$CAKE_VERSION" = "" ] || [ "$DOTNET_VERSION" = "" ]; then + echo "An error occured while parsing Cake / .NET Core SDK version." + exit 1 +fi + +# Make sure the tools folder exist. +if [ ! -d "$TOOLS_DIR" ]; then + mkdir "$TOOLS_DIR" +fi + +########################################################################### +# INSTALL .NET CORE CLI +########################################################################### + +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 +export DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 + +DOTNET_INSTALLED_VERSION=$(dotnet --version 2>&1) + +if [ "$DOTNET_VERSION" != "$DOTNET_INSTALLED_VERSION" ]; then + echo "Installing .NET CLI..." + if [ ! -d "$SCRIPT_DIR/.dotnet" ]; then + mkdir "$SCRIPT_DIR/.dotnet" + fi + curl -Lsfo "$SCRIPT_DIR/.dotnet/dotnet-install.sh" https://dot.net/v1/dotnet-install.sh + bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --version $DOTNET_VERSION --install-dir .dotnet --no-path + export PATH="$SCRIPT_DIR/.dotnet":$PATH + export DOTNET_ROOT="$SCRIPT_DIR/.dotnet" +fi + +########################################################################### +# INSTALL CAKE +########################################################################### + +CAKE_INSTALLED_VERSION=$(dotnet-cake --version 2>&1) + +if [ "$CAKE_VERSION" != "$CAKE_INSTALLED_VERSION" ]; then + if [ ! -f "$CAKE_EXE" ] || [ ! -d "$CAKE_PATH" ]; then + if [ -f "$CAKE_EXE" ]; then + dotnet tool uninstall --tool-path $TOOLS_DIR Cake.Tool + fi + + echo "Installing Cake $CAKE_VERSION..." + dotnet tool install --tool-path $TOOLS_DIR --version $CAKE_VERSION Cake.Tool + if [ $? -ne 0 ]; then + echo "An error occured while installing Cake." + exit 1 + fi + fi + + # Make sure that Cake has been installed. + if [ ! -f "$CAKE_EXE" ]; then + echo "Could not find Cake.exe at '$CAKE_EXE'." + exit 1 + fi +else + CAKE_EXE="dotnet-cake" +fi + +########################################################################### +# RUN BUILD SCRIPT +########################################################################### + +# Start Cake +(exec "$CAKE_EXE" build.cake --bootstrap) && (exec "$CAKE_EXE" build.cake "$@") \ No newline at end of file diff --git a/codecov.sh b/codecov.sh new file mode 100644 index 0000000..1d6b417 --- /dev/null +++ b/codecov.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +TOKEN=$1 +rm -r ./**/test/TestResults +rm -r CodeCoverage +for name in `ls ./**/test/*.csproj | awk '{print $NF}'`; +do + echo ${name} + dotnet test ${name} --logger trx --settings CodeCoverage.runsettings --collect:"XPlat Code Coverage" +done +reportgenerator /**/test/TestResults/*/coverage.cobertura.xml -reports:./**/test/TestResults/*/coverage.cobertura.xml -targetdir:./CodeCoverage -reporttypes:Cobertura -assemblyfilters:-xunit* +codecov -f ./CodeCoverage/Cobertura.xml -t ${TOKEN} \ No newline at end of file diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..23be405 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,3 @@ +codecov: + notify: + after_n_builds: 1 \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..cb448ea --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file