diff --git a/.gitignore b/.gitignore index 2e8df20..d25eaab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ temp +.DS_Store # Rider .idea/ diff --git a/templates/ExpenseTrackerContract/.template.config/template.json b/templates/ExpenseTrackerContract/.template.config/template.json new file mode 100644 index 0000000..3735f92 --- /dev/null +++ b/templates/ExpenseTrackerContract/.template.config/template.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "AElf", + "classifications": [ + "AElf/SmartContract" + ], + "identity": "AElf.Contract.ExpenseTracker.Template", + "name": "AElf Contract ExpenseTracker Template", + "shortName": "aelf-expense-tracker", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "ExpenseTracker", + "symbols": { + "NamespacePath": { + "type": "parameter", + "replaces": "AElf.Contracts.ExpenseTracker" + } + } +} \ No newline at end of file diff --git a/templates/ExpenseTrackerContract/src/ExpenseTracker.cs b/templates/ExpenseTrackerContract/src/ExpenseTracker.cs new file mode 100644 index 0000000..1f5a585 --- /dev/null +++ b/templates/ExpenseTrackerContract/src/ExpenseTracker.cs @@ -0,0 +1,117 @@ +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; + +namespace AElf.Contracts.ExpenseTracker +{ + public class ExpenseTracker : ExpenseTrackerContainer.ExpenseTrackerBase + { + public override Empty Initialize(Empty input) + { + if (State.Initialized.Value) + { + return new Empty(); + } + State.Initialized.Value = true; + State.Owner.Value = Context.Sender; + State.ExpenseIds.Value = ""; + State.ExpenseCounter.Value = 0; + return new Empty(); + } + + public override StringValue AddExpense(ExpenseInput input) + { + if (!State.Initialized.Value) + { + return new StringValue { Value = "Contract not initialized." }; + } + var expenseId = (State.ExpenseCounter.Value + 1).ToString(); + State.ExpenseCounter.Value++; + var timestamp = Context.CurrentBlockTime.Seconds; + State.Expenses[expenseId] = new Expense + { + ExpenseId = expenseId, + Description = input.Description, + Category = input.Category, + Amount = input.Amount, // Now using int64 for amount + Currency = input.Currency, + CreatedAt = timestamp, + UpdatedAt = timestamp, + Owner = Context.Sender.ToString().Trim('"'), + }; + State.ExpenseExistence[expenseId] = true; + + var existingExpenseIds = State.ExpenseIds.Value; + existingExpenseIds += string.IsNullOrEmpty(existingExpenseIds) ? expenseId : $",{expenseId}"; + State.ExpenseIds.Value = existingExpenseIds; + + return new StringValue { Value = expenseId }; + } + + public override Empty UpdateExpense(ExpenseUpdateInput input) + { + var expense = State.Expenses[input.ExpenseId]; + if (expense == null) + { + return new Empty(); // Handle case if expense doesn't exist + } + expense.Description = input.Description ?? expense.Description; + expense.Category = input.Category ?? expense.Category; + expense.Amount = input.Amount != 0 ? input.Amount : expense.Amount; // Now using int64 for amount + expense.Currency = input.Currency ?? expense.Currency; + expense.UpdatedAt = Context.CurrentBlockTime.Seconds; + + State.Expenses[input.ExpenseId] = expense; + return new Empty(); + } + + public override Empty DeleteExpense(StringValue input) + { + State.Expenses.Remove(input.Value); + State.ExpenseExistence.Remove(input.Value); + + var existingExpenseIds = State.ExpenseIds.Value.Split(','); + var newExpenseIds = new List(existingExpenseIds.Length); + foreach (var expenseId in existingExpenseIds) + { + if (expenseId != input.Value) + { + newExpenseIds.Add(expenseId); + } + } + State.ExpenseIds.Value = string.Join(",", newExpenseIds); + + return new Empty(); + } + + public override ExpenseList ListExpenses(StringValue input) + { + var owner = input.Value; // Get the owner value from the input + var expenseList = new ExpenseList(); + var expenseIds = State.ExpenseIds.Value.Split(','); + foreach (var expenseId in expenseIds) + { + var expense = State.Expenses[expenseId]; + if (expense != null && expense.Owner == owner) // Filter expenses by owner + { + expenseList.Expenses.Add(expense); + } + } + return expenseList; + } + + public override Expense GetExpense(StringValue input) + { + var expense = State.Expenses[input.Value]; + if (expense == null) + { + return new Expense { ExpenseId = input.Value, Description = "Expense not found." }; + } + return expense; + } + + public override BoolValue GetInitialStatus(Empty input) + { + return new BoolValue { Value = State.Initialized.Value }; + } + } +} diff --git a/templates/ExpenseTrackerContract/src/ExpenseTracker.csproj b/templates/ExpenseTrackerContract/src/ExpenseTracker.csproj new file mode 100644 index 0000000..7adf3d8 --- /dev/null +++ b/templates/ExpenseTrackerContract/src/ExpenseTracker.csproj @@ -0,0 +1,27 @@ + + + net6.0 + AElf.Contracts.ExpenseTracker + true + true + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + \ No newline at end of file diff --git a/templates/ExpenseTrackerContract/src/ExpenseTrackerState.cs b/templates/ExpenseTrackerContract/src/ExpenseTrackerState.cs new file mode 100644 index 0000000..42f849c --- /dev/null +++ b/templates/ExpenseTrackerContract/src/ExpenseTrackerState.cs @@ -0,0 +1,15 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.ExpenseTracker +{ + public class ExpenseTrackerState : ContractState + { + public BoolState Initialized { get; set; } + public SingletonState
Owner { get; set; } + public MappedState Expenses { get; set; } // Mapping of expense ID to Expense + public MappedState ExpenseExistence { get; set; } // Mapping to track expense existence + public StringState ExpenseIds { get; set; } // Concatenated string of expense IDs + public Int32State ExpenseCounter { get; set; } // Counter for generating unique IDs + } +} diff --git a/templates/ExpenseTrackerContract/src/Protobuf/contract/expense_tracker.proto b/templates/ExpenseTrackerContract/src/Protobuf/contract/expense_tracker.proto new file mode 100644 index 0000000..9fbbf18 --- /dev/null +++ b/templates/ExpenseTrackerContract/src/Protobuf/contract/expense_tracker.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.ExpenseTracker"; +service ExpenseTracker { + option (aelf.csharp_state) = "AElf.Contracts.ExpenseTracker.ExpenseTrackerState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc AddExpense (ExpenseInput) returns (google.protobuf.StringValue) { + } + rpc UpdateExpense (ExpenseUpdateInput) returns (google.protobuf.Empty) { + } + rpc DeleteExpense (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + rpc ListExpenses (google.protobuf.StringValue) returns (ExpenseList) { + option (aelf.is_view) = true; + } + rpc GetExpense (google.protobuf.StringValue) returns (Expense) { + option (aelf.is_view) = true; + } + rpc GetInitialStatus (google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} + +message Expense { + string expense_id = 1; + string description = 2; + string category = 3; + int64 amount = 4; // Store as cents + string currency = 5; + string owner = 6; + int64 created_at = 7; + int64 updated_at = 8; +} + +message ExpenseInput { + string description = 1; + string category = 2; + int64 amount = 3; // Store as cents + string currency = 4; +} + +message ExpenseUpdateInput { + string expense_id = 1; + string description = 2; + string category = 3; + int64 amount = 4; // Store as cents + string currency = 5; +} + +message ExpenseList { + repeated Expense expenses = 1; +} diff --git a/templates/ExpenseTrackerContract/src/Protobuf/message/authority_info.proto b/templates/ExpenseTrackerContract/src/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..eac0006 --- /dev/null +++ b/templates/ExpenseTrackerContract/src/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.ExpenseTracker"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/ExpenseTrackerContract/src/Protobuf/reference/acs12.proto b/templates/ExpenseTrackerContract/src/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/ExpenseTrackerContract/src/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/ExpenseTrackerContract/test/ExpenseTracker.Tests.csproj b/templates/ExpenseTrackerContract/test/ExpenseTracker.Tests.csproj new file mode 100644 index 0000000..2c1c762 --- /dev/null +++ b/templates/ExpenseTrackerContract/test/ExpenseTracker.Tests.csproj @@ -0,0 +1,51 @@ + + + net6.0 + AElf.Contracts.ExpenseTracker + + + + 0436;CS2002 + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + nocontract + + + nocontract + + + stub + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/ExpenseTrackerContract/test/ExpenseTrackerTests.cs b/templates/ExpenseTrackerContract/test/ExpenseTrackerTests.cs new file mode 100644 index 0000000..4713327 --- /dev/null +++ b/templates/ExpenseTrackerContract/test/ExpenseTrackerTests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; + +namespace AElf.Contracts.ExpenseTracker +{ + // This class is unit test class, and it inherit TestBase. Write your unit test code inside it + public class ExpenseTrackerTests : TestBase + { + [Fact] + public async Task Update_ShouldUpdateMessageAndFireEvent() + { + // Arrange + var inputValue = "Hello, World!"; + var input = new StringValue { Value = inputValue }; + + // Act + await ExpenseTrackerStub.Update.SendAsync(input); + + // Assert + var updatedMessage = await ExpenseTrackerStub.Read.CallAsync(new Empty()); + updatedMessage.Value.ShouldBe(inputValue); + } + } + +} \ No newline at end of file diff --git a/templates/ExpenseTrackerContract/test/Protobuf/message/authority_info.proto b/templates/ExpenseTrackerContract/test/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..eac0006 --- /dev/null +++ b/templates/ExpenseTrackerContract/test/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.ExpenseTracker"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/ExpenseTrackerContract/test/Protobuf/reference/acs12.proto b/templates/ExpenseTrackerContract/test/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/ExpenseTrackerContract/test/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/ExpenseTrackerContract/test/Protobuf/stub/hello_world_contract.proto b/templates/ExpenseTrackerContract/test/Protobuf/stub/hello_world_contract.proto new file mode 100644 index 0000000..12125e5 --- /dev/null +++ b/templates/ExpenseTrackerContract/test/Protobuf/stub/hello_world_contract.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.ExpenseTracker"; + +service ExpenseTracker { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.ExpenseTracker.ExpenseTrackerState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + // Actions (methods that modify contract state) + // Stores the value in contract state + rpc Update (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + // Views (methods that don't modify contract state) + // Get the value stored from contract state + rpc Read (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +// An event that will be emitted from contract method call +message UpdatedMessage { + option (aelf.is_event) = true; + string value = 1; +} \ No newline at end of file diff --git a/templates/ExpenseTrackerContract/test/_Setup.cs b/templates/ExpenseTrackerContract/test/_Setup.cs new file mode 100644 index 0000000..4c27c41 --- /dev/null +++ b/templates/ExpenseTrackerContract/test/_Setup.cs @@ -0,0 +1,31 @@ +using AElf.Cryptography.ECDSA; +using AElf.Testing.TestBase; + +namespace AElf.Contracts.ExpenseTracker +{ + // The Module class load the context required for unit testing + public class Module : ContractTestModule + { + + } + + // The TestBase class inherit ContractTestBase class, it defines Stub classes and gets instances required for unit testing + public class TestBase : ContractTestBase + { + // The Stub class for unit testing + internal readonly ExpenseTrackerContainer.ExpenseTrackerStub ExpenseTrackerStub; + // A key pair that can be used to interact with the contract instance + private ECKeyPair DefaultKeyPair => Accounts[0].KeyPair; + + public TestBase() + { + ExpenseTrackerStub = GetExpenseTrackerContractStub(DefaultKeyPair); + } + + private ExpenseTrackerContainer.ExpenseTrackerStub GetExpenseTrackerContractStub(ECKeyPair senderKeyPair) + { + return GetTester(ContractAddress, senderKeyPair); + } + } + +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/.template.config/template.json b/templates/SinglePoolStakingContract/.template.config/template.json new file mode 100644 index 0000000..9a3d751 --- /dev/null +++ b/templates/SinglePoolStakingContract/.template.config/template.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "AElf", + "classifications": [ + "AElf/SmartContract" + ], + "identity": "AElf.Contract.SinglePoolStaking.Template", + "name": "AElf Contract SinglePoolStaking Template", + "shortName": "aelf-single-pool-staking", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "SinglePoolStaking", + "symbols": { + "NamespacePath": { + "type": "parameter", + "replaces": "AElf.Contracts.SinglePoolStaking" + } + } +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/src/Protobuf/contract/single_pool_staking.proto b/templates/SinglePoolStakingContract/src/Protobuf/contract/single_pool_staking.proto new file mode 100644 index 0000000..7b6a63f --- /dev/null +++ b/templates/SinglePoolStakingContract/src/Protobuf/contract/single_pool_staking.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "Protobuf/reference/acs12.proto"; +import "google/protobuf/wrappers.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.SinglePoolStaking"; + +service SinglePoolStaking { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.SinglePoolStaking.SinglePoolStakingState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + rpc Initialize (InitializeInput) returns (google.protobuf.Empty); + rpc Deposit (DepositInput) returns (google.protobuf.StringValue); + rpc Withdraw (WithdrawInput) returns (google.protobuf.Empty); + rpc ForceWithdraw (google.protobuf.StringValue) returns (google.protobuf.Empty); + + rpc GetReward (google.protobuf.StringValue) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + rpc GetDeposits (google.protobuf.StringValue) returns (DepositList) { + option (aelf.is_view) = true; + } + + // New functions + rpc IfInitialized (google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetTotalStakedAmount (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } +} + +message DepositInput { + string token_symbol = 1; + int64 amount = 2; + int64 lock_time = 3; +} + +message InitializeInput { + aelf.Address token_contract_address = 1; +} + +message WithdrawInput { + string deposit_id = 1; +} + + +message StringList { + repeated string values = 1; +} + +message Deposit { + string deposit_id = 1; + aelf.Address to = 2; // Store the Address directly + string token_symbol = 3; + int64 amount = 4; + int64 lock_time = 5; + int64 deposit_time = 6; +} + +message DepositList { + repeated Deposit deposits = 1; +} diff --git a/templates/SinglePoolStakingContract/src/Protobuf/message/authority_info.proto b/templates/SinglePoolStakingContract/src/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..a42fd49 --- /dev/null +++ b/templates/SinglePoolStakingContract/src/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.SinglePoolStaking"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/src/Protobuf/reference/acs12.proto b/templates/SinglePoolStakingContract/src/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/SinglePoolStakingContract/src/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/SinglePoolStakingContract/src/Protobuf/reference/token_contract.proto b/templates/SinglePoolStakingContract/src/Protobuf/reference/token_contract.proto new file mode 100644 index 0000000..fed220a --- /dev/null +++ b/templates/SinglePoolStakingContract/src/Protobuf/reference/token_contract.proto @@ -0,0 +1,855 @@ +/** + * MultiToken contract. + */ +syntax = "proto3"; + +package token; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.MultiToken"; + +service TokenContract { + // Create a new token. + rpc Create (CreateInput) returns (google.protobuf.Empty) { + } + + // Issuing some amount of tokens to an address is the action of increasing that addresses balance + // for the given token. The total amount of issued tokens must not exceed the total supply of the token + // and only the issuer (creator) of the token can issue tokens. + // Issuing tokens effectively increases the circulating supply. + rpc Issue (IssueInput) returns (google.protobuf.Empty) { + } + + // Transferring tokens simply is the action of transferring a given amount of tokens from one address to another. + // The origin or source address is the signer of the transaction. + // The balance of the sender must be higher than the amount that is transferred. + rpc Transfer (TransferInput) returns (google.protobuf.Empty) { + } + + // The TransferFrom action will transfer a specified amount of tokens from one address to another. + // For this operation to succeed the from address needs to have approved (see allowances) enough tokens + // to Sender of this transaction. If successful the amount will be removed from the allowance. + rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) { + } + + // The approve action increases the allowance from the Sender to the Spender address, + // enabling the Spender to call TransferFrom. + rpc Approve (ApproveInput) returns (google.protobuf.Empty) { + } + + // This is the reverse operation for Approve, it will decrease the allowance. + rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { + } + + // This method can be used to lock tokens. + rpc Lock (LockInput) returns (google.protobuf.Empty) { + } + + // This is the reverse operation of locking, it un-locks some previously locked tokens. + rpc Unlock (UnlockInput) returns (google.protobuf.Empty) { + } + + // This action will burn the specified amount of tokens, removing them from the tokens Supply. + rpc Burn (BurnInput) returns (google.protobuf.Empty) { + } + + // Set the primary token of side chain. + rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) { + } + + // This interface is used for cross-chain transfer. + rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) { + } + + // This method is used to receive cross-chain transfers. + rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) { + } + + // The side chain creates tokens. + rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) { + } + + // When the side chain is started, the side chain is initialized with the parent chain information. + rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) { + } + + // Handle the transaction fees charged by ChargeTransactionFees. + rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) { + } + + // Used to collect transaction fees. + rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) { + } + + rpc ChargeUserContractTransactionFees(ChargeTransactionFeesInput) returns(ChargeTransactionFeesOutput){ + + } + + // Check the token threshold. + rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) { + } + + // Initialize coefficients of every type of tokens supporting charging fee. + rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + // Processing resource token received. + rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) { + } + + // A transaction resource fee is charged to implement the ACS8 standards. + rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) { + } + + // Verify that the resource token are sufficient. + rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + // Set the list of tokens to pay transaction fees. + rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){ + } + + // Update the coefficient of the transaction fee calculation formula. + rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + + // Update the coefficient of the transaction fee calculation formula. + rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + + // This method is used to initialize the governance organization for some functions, + // including: the coefficient of the user transaction fee calculation formula, + // the coefficient of the contract developer resource fee calculation formula, and the side chain rental fee. + rpc InitializeAuthorizedController (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + rpc AddAddressToCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) { + } + rpc RemoveAddressFromCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) { + } + + rpc SetTransactionFeeDelegations (SetTransactionFeeDelegationsInput) returns (SetTransactionFeeDelegationsOutput){ + } + + rpc RemoveTransactionFeeDelegator (RemoveTransactionFeeDelegatorInput) returns (google.protobuf.Empty){ + } + + rpc RemoveTransactionFeeDelegatee (RemoveTransactionFeeDelegateeInput) returns (google.protobuf.Empty){ + } + + // Get all delegatees' address of delegator from input + rpc GetTransactionFeeDelegatees (GetTransactionFeeDelegateesInput) returns (GetTransactionFeeDelegateesOutput) { + option (aelf.is_view) = true; + } + + // Query token information. + rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) { + option (aelf.is_view) = true; + } + + // Query native token information. + rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) { + option (aelf.is_view) = true; + } + + // Query resource token information. + rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) { + option (aelf.is_view) = true; + } + + // Query the balance at the specified address. + rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } + + // Query the account's allowance for other addresses + rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + + // Check whether the token is in the whitelist of an address, + // which can be called TransferFrom to transfer the token under the condition of not being credited. + rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + + // Query the information for a lock. + rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) { + option (aelf.is_view) = true; + } + + // Query the address of receiving token in cross-chain transfer. + rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Query the name of the primary Token. + rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + + // Query the coefficient of the transaction fee calculation formula. + rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + + // Query the coefficient of the transaction fee calculation formula. + rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + + // Query tokens that can pay transaction fees. + rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){ + option (aelf.is_view) = true; + } + + // Query the hash of the last input of ClaimTransactionFees. + rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + + // Query the hash of the last input of DonateResourceToken. + rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetReservedExternalInfoKeyList (google.protobuf.Empty) returns (StringList) { + option (aelf.is_view) = true; + } + + rpc GetTransactionFeeDelegationsOfADelegatee(GetTransactionFeeDelegationsOfADelegateeInput) returns(TransactionFeeDelegations){ + option (aelf.is_view) = true; + } +} + +message TokenInfo { + // The symbol of the token.f + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The current supply of the token. + int64 supply = 3; + // The total supply of the token. + int64 total_supply = 4; + // The precision of the token. + int32 decimals = 5; + // The address that has permission to issue the token. + aelf.Address issuer = 6; + // A flag indicating if this token is burnable. + bool is_burnable = 7; + // The chain id of the token. + int32 issue_chain_id = 8; + // The amount of issued tokens. + int64 issued = 9; + // The external information of the token. + ExternalInfo external_info = 10; + // The address that owns the token. + aelf.Address owner = 11; +} + +message ExternalInfo { + map value = 1; +} + +message CreateInput { + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token + int32 decimals = 4; + // The address that has permission to issue the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // A whitelist address list used to lock tokens. + repeated aelf.Address lock_white_list = 7; + // The chain id of the token. + int32 issue_chain_id = 8; + // The external information of the token. + ExternalInfo external_info = 9; + // The address that owns the token. + aelf.Address owner = 10; +} + +message SetPrimaryTokenSymbolInput { + // The symbol of the token. + string symbol = 1; +} + +message IssueInput { + // The token symbol to issue. + string symbol = 1; + // The token amount to issue. + int64 amount = 2; + // The memo. + string memo = 3; + // The target address to issue. + aelf.Address to = 4; +} + +message TransferInput { + // The receiver of the token. + aelf.Address to = 1; + // The token symbol to transfer. + string symbol = 2; + // The amount to to transfer. + int64 amount = 3; + // The memo. + string memo = 4; +} + +message LockInput { + // The one want to lock his token. + aelf.Address address = 1; + // Id of the lock. + aelf.Hash lock_id = 2; + // The symbol of the token to lock. + string symbol = 3; + // a memo. + string usage = 4; + // The amount of tokens to lock. + int64 amount = 5; +} + +message UnlockInput { + // The one want to un-lock his token. + aelf.Address address = 1; + // Id of the lock. + aelf.Hash lock_id = 2; + // The symbol of the token to un-lock. + string symbol = 3; + // a memo. + string usage = 4; + // The amount of tokens to un-lock. + int64 amount = 5; +} + +message TransferFromInput { + // The source address of the token. + aelf.Address from = 1; + // The destination address of the token. + aelf.Address to = 2; + // The symbol of the token to transfer. + string symbol = 3; + // The amount to transfer. + int64 amount = 4; + // The memo. + string memo = 5; +} + +message ApproveInput { + // The address that allowance will be increased. + aelf.Address spender = 1; + // The symbol of token to approve. + string symbol = 2; + // The amount of token to approve. + int64 amount = 3; +} + +message UnApproveInput { + // The address that allowance will be decreased. + aelf.Address spender = 1; + // The symbol of token to un-approve. + string symbol = 2; + // The amount of token to un-approve. + int64 amount = 3; +} + +message BurnInput { + // The symbol of token to burn. + string symbol = 1; + // The amount of token to burn. + int64 amount = 2; +} + +message ChargeResourceTokenInput { + // Collection of charge resource token, Symbol->Amount. + map cost_dic = 1; + // The sender of the transaction. + aelf.Address caller = 2; +} + +message TransactionFeeBill { + // The transaction fee dictionary, Symbol->fee. + map fees_map = 1; +} + +message TransactionFreeFeeAllowanceBill { + // The transaction free fee allowance dictionary, Symbol->fee. + map free_fee_allowances_map = 1; +} + +message CheckThresholdInput { + // The sender of the transaction. + aelf.Address sender = 1; + // The threshold to set, Symbol->Threshold. + map symbol_to_threshold = 2; + // Whether to check the allowance. + bool is_check_allowance = 3; +} + +message GetTokenInfoInput { + // The symbol of token. + string symbol = 1; +} + +message GetBalanceInput { + // The symbol of token. + string symbol = 1; + // The target address of the query. + aelf.Address owner = 2; +} + +message GetBalanceOutput { + // The symbol of token. + string symbol = 1; + // The target address of the query. + aelf.Address owner = 2; + // The balance of the owner. + int64 balance = 3; +} + +message GetAllowanceInput { + // The symbol of token. + string symbol = 1; + // The address of the token owner. + aelf.Address owner = 2; + // The address of the spender. + aelf.Address spender = 3; +} + +message GetAllowanceOutput { + // The symbol of token. + string symbol = 1; + // The address of the token owner. + aelf.Address owner = 2; + // The address of the spender. + aelf.Address spender = 3; + // The amount of allowance. + int64 allowance = 4; +} + +message CrossChainTransferInput { + // The receiver of transfer. + aelf.Address to = 1; + // The symbol of token. + string symbol = 2; + // The amount of token to transfer. + int64 amount = 3; + // The memo. + string memo = 4; + // The destination chain id. + int32 to_chain_id = 5; + // The chain id of the token. + int32 issue_chain_id = 6; +} + +message CrossChainReceiveTokenInput { + // The source chain id. + int32 from_chain_id = 1; + // The height of the transfer transaction. + int64 parent_chain_height = 2; + // The raw bytes of the transfer transaction. + bytes transfer_transaction_bytes = 3; + // The merkle path created from the transfer transaction. + aelf.MerklePath merkle_path = 4; +} + +message IsInWhiteListInput { + // The symbol of token. + string symbol = 1; + // The address to check. + aelf.Address address = 2; +} + +message SymbolToPayTxSizeFee{ + // The symbol of token. + string token_symbol = 1; + // The charge weight of primary token. + int32 base_token_weight = 2; + // The new added token charge weight. For example, the charge weight of primary Token is set to 1. + // The newly added token charge weight is set to 10. If the transaction requires 1 unit of primary token, + // the user can also pay for 10 newly added tokens. + int32 added_token_weight = 3; +} + +message SymbolListToPayTxSizeFee{ + // Transaction fee token information. + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1; +} + +message ChargeTransactionFeesInput { + // The method name of transaction. + string method_name = 1; + // The contract address of transaction. + aelf.Address contract_address = 2; + // The amount of transaction size fee. + int64 transaction_size_fee = 3; + // Transaction fee token information. + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4; +} + +message ChargeTransactionFeesOutput { + // Whether the charge was successful. + bool success = 1; + // The charging information. + string charging_information = 2; +} + +message CallbackInfo { + aelf.Address contract_address = 1; + string method_name = 2; +} + +message ExtraTokenListModified { + option (aelf.is_event) = true; + // Transaction fee token information. + SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1; +} + +message GetLockedAmountInput { + // The address of the lock. + aelf.Address address = 1; + // The token symbol. + string symbol = 2; + // The id of the lock. + aelf.Hash lock_id = 3; +} + +message GetLockedAmountOutput { + // The address of the lock. + aelf.Address address = 1; + // The token symbol. + string symbol = 2; + // The id of the lock. + aelf.Hash lock_id = 3; + // The locked amount. + int64 amount = 4; +} + +message TokenInfoList { + // List of token information. + repeated TokenInfo value = 1; +} + +message GetCrossChainTransferTokenContractAddressInput { + // The chain id. + int32 chainId = 1; +} + +message CrossChainCreateTokenInput { + // The chain id of the chain on which the token was created. + int32 from_chain_id = 1; + // The height of the transaction that created the token. + int64 parent_chain_height = 2; + // The transaction that created the token. + bytes transaction_bytes = 3; + // The merkle path created from the transaction that created the transaction. + aelf.MerklePath merkle_path = 4; +} + +message InitializeFromParentChainInput { + // The amount of resource. + map resource_amount = 1; + // The token contract addresses. + map registered_other_token_contract_addresses = 2; + // The creator the side chain. + aelf.Address creator = 3; +} + +message UpdateCoefficientsInput { + // The specify pieces gonna update. + repeated int32 piece_numbers = 1; + // Coefficients of one single type. + CalculateFeeCoefficients coefficients = 2; +} + +enum FeeTypeEnum { + READ = 0; + STORAGE = 1; + WRITE = 2; + TRAFFIC = 3; + TX = 4; +} + +message CalculateFeePieceCoefficients { + // Coefficients of one single piece. + // The first char is its type: liner / power. + // The second char is its piece upper bound. + repeated int32 value = 1; +} + +message CalculateFeeCoefficients { + // The resource fee type, like READ, WRITE, etc. + int32 fee_token_type = 1; + // Coefficients of one single piece. + repeated CalculateFeePieceCoefficients piece_coefficients_list = 2; +} + +message AllCalculateFeeCoefficients { + // The coefficients of fee Calculation. + repeated CalculateFeeCoefficients value = 1; +} + +message TotalTransactionFeesMap +{ + // Token dictionary that charge transaction fee, Symbol->Amount. + map value = 1; + // The hash of the block processing the transaction. + aelf.Hash block_hash = 2; + // The height of the block processing the transaction. + int64 block_height = 3; +} + +message TotalResourceTokensMaps { + // Resource tokens to charge. + repeated ContractTotalResourceTokens value = 1; + // The hash of the block processing the transaction. + aelf.Hash block_hash = 2; + // The height of the block processing the transaction. + int64 block_height = 3; +} + +message ContractTotalResourceTokens { + // The contract address. + aelf.Address contract_address = 1; + // Resource tokens to charge. + TotalResourceTokensMap tokens_map = 2; +} + +message TotalResourceTokensMap +{ + // Resource token dictionary, Symbol->Amount. + map value = 1; +} + +message StringList { + repeated string value = 1; +} + +message TransactionFeeDelegations{ + // delegation, symbols and its' amount + map delegations = 1; + // height when added + int64 block_height = 2; + //Whether to pay transaction fee continuously + bool isUnlimitedDelegate = 3; +} + +message TransactionFeeDelegatees{ + map delegatees = 1; +} + +message SetTransactionFeeDelegationsInput { + // the delegator address + aelf.Address delegator_address = 1; + // delegation, symbols and its' amount + map delegations = 2; +} + +message SetTransactionFeeDelegationsOutput { + bool success = 1; +} + +message RemoveTransactionFeeDelegatorInput{ + // the delegator address + aelf.Address delegator_address = 1; +} + +message RemoveTransactionFeeDelegateeInput { + // the delegatee address + aelf.Address delegatee_address = 1; +} + +message GetTransactionFeeDelegationsOfADelegateeInput { + aelf.Address delegatee_address = 1; + aelf.Address delegator_address = 2; +} + +message GetTransactionFeeDelegateesInput { + aelf.Address delegator_address = 1; +} + +message GetTransactionFeeDelegateesOutput { + repeated aelf.Address delegatee_addresses = 1; +} + +// Events + +message Transferred { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1 [(aelf.is_indexed) = true]; + // The destination address of the transferred token. + aelf.Address to = 2 [(aelf.is_indexed) = true]; + // The symbol of the transferred token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of the transferred token. + int64 amount = 4; + // The memo. + string memo = 5; +} + +message Approved { + option (aelf.is_event) = true; + // The address of the token owner. + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + // The address that allowance be increased. + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + // The symbol of approved token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of approved token. + int64 amount = 4; +} + +message UnApproved { + option (aelf.is_event) = true; + // The address of the token owner. + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + // The address that allowance be decreased. + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + // The symbol of un-approved token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of un-approved token. + int64 amount = 4; +} + +message Burned +{ + option (aelf.is_event) = true; + // The address who wants to burn token. + aelf.Address burner = 1 [(aelf.is_indexed) = true]; + // The symbol of burned token. + string symbol = 2 [(aelf.is_indexed) = true]; + // The amount of burned token. + int64 amount = 3; +} + +message ChainPrimaryTokenSymbolSet { + option (aelf.is_event) = true; + // The symbol of token. + string token_symbol = 1; +} + +message CalculateFeeAlgorithmUpdated { + option (aelf.is_event) = true; + // All calculate fee coefficients after modification. + AllCalculateFeeCoefficients all_type_fee_coefficients = 1; +} + +message RentalCharged { + option (aelf.is_event) = true; + // The symbol of rental fee charged. + string symbol = 1; + // The amount of rental fee charged. + int64 amount = 2; + // The payer of rental fee. + aelf.Address payer = 3; + // The receiver of rental fee. + aelf.Address receiver = 4; +} + +message RentalAccountBalanceInsufficient { + option (aelf.is_event) = true; + // The symbol of insufficient rental account balance. + string symbol = 1; + // The balance of the account. + int64 amount = 2; +} + +message TokenCreated { + option (aelf.is_event) = true; + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token. + int32 decimals = 4; + // The address that has permission to issue the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // The chain id of the token. + int32 issue_chain_id = 7; + // The external information of the token. + ExternalInfo external_info = 8; + // The address that owns the token. + aelf.Address owner = 9; +} + +message Issued { + option (aelf.is_event) = true; + // The symbol of issued token. + string symbol = 1; + // The amount of issued token. + int64 amount = 2; + // The memo. + string memo = 3; + // The issued target address. + aelf.Address to = 4; +} + +message CrossChainTransferred { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1; + // The destination address of the transferred token. + aelf.Address to = 2; + // The symbol of the transferred token. + string symbol = 3; + // The amount of the transferred token. + int64 amount = 4; + // The memo. + string memo = 5; + // The destination chain id. + int32 to_chain_id = 6; + // The chain id of the token. + int32 issue_chain_id = 7; +} + +message CrossChainReceived { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1; + // The destination address of the transferred token. + aelf.Address to = 2; + // The symbol of the received token. + string symbol = 3; + // The amount of the received token. + int64 amount = 4; + // The memo. + string memo = 5; + // The destination chain id. + int32 from_chain_id = 6; + // The chain id of the token. + int32 issue_chain_id = 7; + // The parent chain height of the transfer transaction. + int64 parent_chain_height = 8; + // The id of transfer transaction. + aelf.Hash transfer_transaction_id =9; +} + +message TransactionFeeDelegationAdded { + option (aelf.is_event) = true; + aelf.Address delegator = 1 [(aelf.is_indexed) = true]; + aelf.Address delegatee = 2 [(aelf.is_indexed) = true]; + aelf.Address caller = 3 [(aelf.is_indexed) = true]; +} + +message TransactionFeeDelegationCancelled { + option (aelf.is_event) = true; + aelf.Address delegator = 1 [(aelf.is_indexed) = true]; + aelf.Address delegatee = 2 [(aelf.is_indexed) = true]; + aelf.Address caller = 3 [(aelf.is_indexed) = true]; +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/src/SinglePoolStaking.cs b/templates/SinglePoolStakingContract/src/SinglePoolStaking.cs new file mode 100644 index 0000000..251147f --- /dev/null +++ b/templates/SinglePoolStakingContract/src/SinglePoolStaking.cs @@ -0,0 +1,164 @@ +using Google.Protobuf.WellKnownTypes; +using AElf.Types; +using AElf.Contracts.MultiToken; + +namespace AElf.Contracts.SinglePoolStaking +{ + // Contract class must inherit the base class generated from the proto file + public class SinglePoolStaking : SinglePoolStakingContainer.SinglePoolStakingBase + { + // A method that modifies the contract state + private const int RewardRate = 10; // 10% reward + + public override Empty Initialize(InitializeInput input) + { + if (State.Initialized.Value) + return new Empty(); + + State.Initialized.Value = true; + State.Owner.Value = Context.Sender; + State.DepositCounter.Value = 0; + State.TotalStakedAmount.Value = 0; // Initialize total staked amount + + State.TokenContract.Value = input.TokenContractAddress; + + return new Empty(); + } + + public override StringValue Deposit(DepositInput input) + { + var depositId = (State.DepositCounter.Value + 1).ToString(); + State.DepositCounter.Value++; + + var deposit = new Deposit + { + DepositId = depositId, + To = Context.Sender, + TokenSymbol = input.TokenSymbol, + Amount = input.Amount, + LockTime = input.LockTime, + DepositTime = Context.CurrentBlockTime.Seconds + }; + + State.Deposits[depositId] = deposit; + + var userDeposits = State.UserDeposits[Context.Sender] ?? new StringList(); + userDeposits.Values.Add(depositId); + + State.UserDeposits[Context.Sender] = userDeposits; + + State.TotalStakedAmount.Value += input.Amount; // Update total staked amount + + return new StringValue { Value = depositId }; + } + + public override Empty Withdraw(WithdrawInput input) + { + var deposit = State.Deposits[input.DepositId]; + Assert(deposit != null, "Deposit not found."); + Assert(Context.CurrentBlockTime.Seconds >= deposit.DepositTime + deposit.LockTime, "Lock period not over."); + + var depositorAddress = deposit.To; + + var reward = CalculateReward(deposit.Amount); + + TransferFromContract(deposit.TokenSymbol, depositorAddress, deposit.Amount + reward); + + State.TotalStakedAmount.Value -= deposit.Amount; // Update total staked amount + + RemoveDeposit(deposit.DepositId); + return new Empty(); + } + + public override Empty ForceWithdraw(StringValue input) + { + var deposit = State.Deposits[input.Value]; + Assert(deposit != null, "Deposit not found."); + + var depositorAddress = deposit.To; + + TransferFromContract(deposit.TokenSymbol, depositorAddress, deposit.Amount); + + State.TotalStakedAmount.Value -= deposit.Amount; // Update total staked amount + + RemoveDeposit(deposit.DepositId); + return new Empty(); + } + + public override Int64Value GetReward(StringValue input) + { + var deposit = State.Deposits[input.Value]; + Assert(deposit != null, "Deposit not found."); + return new Int64Value { Value = CalculateReward(deposit.Amount) }; + } + + public override DepositList GetDeposits(StringValue input) + { + var deposits = State.UserDeposits[Address.FromBase58(input.Value)]; + var depositList = new DepositList(); + + foreach (var depositId in deposits.Values) + { + var deposit = State.Deposits[depositId]; + if (deposit != null) + { + depositList.Deposits.Add(deposit); + } + } + + return depositList; + } + + // New function to check if initialized + public override BoolValue IfInitialized(Empty input) + { + return new BoolValue { Value = State.Initialized.Value }; + } + + // New function to get the total staked amount + public override Int64Value GetTotalStakedAmount(Empty input) + { + return new Int64Value { Value = State.TotalStakedAmount.Value }; + } + + private long CalculateReward(long amount) + { + return (amount * RewardRate) / 100; + } + + private void TransferFromContract(string symbol, Address to, long amount) + { + var virtualAddressHash = GetVirtualAddressHash(Context.Self, symbol); + + State.TokenContract.Transfer.Send( + new TransferInput + { + To = to, + Symbol = symbol, + Amount = amount + }); + } + + private static Hash GetVirtualAddressHash(Address contractAddress, string symbol) + { + return HashHelper.ConcatAndCompute(HashHelper.ComputeFrom(contractAddress), HashHelper.ComputeFrom(symbol)); + } + + private Address GetVirtualAddress(Hash virtualAddressHash) + { + return Context.ConvertVirtualAddressToContractAddress(virtualAddressHash); + } + + private void RemoveDeposit(string depositId) +{ + var deposit = State.Deposits[depositId]; + State.Deposits.Remove(depositId); + + var userDeposits = State.UserDeposits[deposit.To]; // Use deposit.To directly + userDeposits.Values.Remove(depositId); + + State.UserDeposits[deposit.To] = userDeposits; // Use deposit.To directly +} + } + +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/src/SinglePoolStaking.csproj b/templates/SinglePoolStakingContract/src/SinglePoolStaking.csproj new file mode 100644 index 0000000..55b099d --- /dev/null +++ b/templates/SinglePoolStakingContract/src/SinglePoolStaking.csproj @@ -0,0 +1,27 @@ + + + net6.0 + AElf.Contracts.SinglePoolStaking + true + true + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/src/SinglePoolStakingState.cs b/templates/SinglePoolStakingContract/src/SinglePoolStakingState.cs new file mode 100644 index 0000000..9ef365a --- /dev/null +++ b/templates/SinglePoolStakingContract/src/SinglePoolStakingState.cs @@ -0,0 +1,19 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; +using AElf.Contracts.MultiToken; + +namespace AElf.Contracts.SinglePoolStaking +{ + // The state class is access the blockchain state + public class SinglePoolStakingState : ContractState + { + public BoolState Initialized { get; set; } + public SingletonState
Owner { get; set; } + public MappedState Deposits { get; set; } // Mapping from deposit ID to Deposit + public MappedState UserDeposits { get; set; } // User to deposit IDs + public Int32State DepositCounter { get; set; } + public Int64State TotalStakedAmount { get; set; } // New state to track total staked amount + + internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; } + } +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/test/Protobuf/message/authority_info.proto b/templates/SinglePoolStakingContract/test/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..a42fd49 --- /dev/null +++ b/templates/SinglePoolStakingContract/test/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.SinglePoolStaking"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/test/Protobuf/reference/acs12.proto b/templates/SinglePoolStakingContract/test/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/SinglePoolStakingContract/test/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/SinglePoolStakingContract/test/Protobuf/stub/hello_world_contract.proto b/templates/SinglePoolStakingContract/test/Protobuf/stub/hello_world_contract.proto new file mode 100644 index 0000000..5ae9003 --- /dev/null +++ b/templates/SinglePoolStakingContract/test/Protobuf/stub/hello_world_contract.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.SinglePoolStaking"; + +service SinglePoolStaking { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.SinglePoolStaking.SinglePoolStakingState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + // Actions (methods that modify contract state) + // Stores the value in contract state + rpc Update (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + // Views (methods that don't modify contract state) + // Get the value stored from contract state + rpc Read (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +// An event that will be emitted from contract method call +message UpdatedMessage { + option (aelf.is_event) = true; + string value = 1; +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/test/SinglePoolStaking.Tests.csproj b/templates/SinglePoolStakingContract/test/SinglePoolStaking.Tests.csproj new file mode 100644 index 0000000..363106f --- /dev/null +++ b/templates/SinglePoolStakingContract/test/SinglePoolStaking.Tests.csproj @@ -0,0 +1,51 @@ + + + net6.0 + AElf.Contracts.SinglePoolStaking + + + + 0436;CS2002 + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + nocontract + + + nocontract + + + stub + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/test/SinglePoolStakingTests.cs b/templates/SinglePoolStakingContract/test/SinglePoolStakingTests.cs new file mode 100644 index 0000000..40f7602 --- /dev/null +++ b/templates/SinglePoolStakingContract/test/SinglePoolStakingTests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; + +namespace AElf.Contracts.SinglePoolStaking +{ + // This class is unit test class, and it inherit TestBase. Write your unit test code inside it + public class SinglePoolStakingTests : TestBase + { + [Fact] + public async Task Update_ShouldUpdateMessageAndFireEvent() + { + // Arrange + var inputValue = "Hello, World!"; + var input = new StringValue { Value = inputValue }; + + // Act + await SinglePoolStakingStub.Update.SendAsync(input); + + // Assert + var updatedMessage = await SinglePoolStakingStub.Read.CallAsync(new Empty()); + updatedMessage.Value.ShouldBe(inputValue); + } + } + +} \ No newline at end of file diff --git a/templates/SinglePoolStakingContract/test/_Setup.cs b/templates/SinglePoolStakingContract/test/_Setup.cs new file mode 100644 index 0000000..d2bf302 --- /dev/null +++ b/templates/SinglePoolStakingContract/test/_Setup.cs @@ -0,0 +1,31 @@ +using AElf.Cryptography.ECDSA; +using AElf.Testing.TestBase; + +namespace AElf.Contracts.SinglePoolStaking +{ + // The Module class load the context required for unit testing + public class Module : ContractTestModule + { + + } + + // The TestBase class inherit ContractTestBase class, it defines Stub classes and gets instances required for unit testing + public class TestBase : ContractTestBase + { + // The Stub class for unit testing + internal readonly SinglePoolStakingContainer.SinglePoolStakingStub SinglePoolStakingStub; + // A key pair that can be used to interact with the contract instance + private ECKeyPair DefaultKeyPair => Accounts[0].KeyPair; + + public TestBase() + { + SinglePoolStakingStub = GetSinglePoolStakingContractStub(DefaultKeyPair); + } + + private SinglePoolStakingContainer.SinglePoolStakingStub GetSinglePoolStakingContractStub(ECKeyPair senderKeyPair) + { + return GetTester(ContractAddress, senderKeyPair); + } + } + +} \ No newline at end of file diff --git a/templates/TicTacToeContract/.template.config/template.json b/templates/TicTacToeContract/.template.config/template.json new file mode 100644 index 0000000..dccef92 --- /dev/null +++ b/templates/TicTacToeContract/.template.config/template.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "AElf", + "classifications": [ + "AElf/SmartContract" + ], + "identity": "AElf.Contract.TicTacToe.Template", + "name": "AElf Contract TicTacToe Template", + "shortName": "aelf-tic-tac-toe", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "TicTacToe", + "symbols": { + "NamespacePath": { + "type": "parameter", + "replaces": "AElf.Contracts.TicTacToe" + } + } +} \ No newline at end of file diff --git a/templates/TicTacToeContract/src/Protobuf/contract/tic_tac_toe.proto b/templates/TicTacToeContract/src/Protobuf/contract/tic_tac_toe.proto new file mode 100644 index 0000000..626e2e5 --- /dev/null +++ b/templates/TicTacToeContract/src/Protobuf/contract/tic_tac_toe.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.TicTacToe"; + +service TicTacToe { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.TicTacToe.TicTacToeState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) {} + + rpc StartGame (google.protobuf.Empty) returns (google.protobuf.StringValue) {} + + rpc MakeMove (MoveInput) returns (google.protobuf.StringValue) {} + + rpc GetBoard (google.protobuf.Empty) returns (Board) { + option (aelf.is_view) = true; + } + + rpc GetGameStatus (google.protobuf.Empty) returns (GameStatus) { + option (aelf.is_view) = true; + } + rpc GetInitialStatus(google.protobuf.Empty) returns(google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } +} + +// Input for making a move +message MoveInput { + int32 x = 1; + int32 y = 2; +} + +// A message to represent the game board +message Board { + repeated string rows = 1; +} + +// A message to represent the game status +message GameStatus { + string status = 1; + string winner = 2; +} \ No newline at end of file diff --git a/templates/TicTacToeContract/src/Protobuf/message/authority_info.proto b/templates/TicTacToeContract/src/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..9a1309b --- /dev/null +++ b/templates/TicTacToeContract/src/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.TicTacToe"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/TicTacToeContract/src/Protobuf/reference/acs12.proto b/templates/TicTacToeContract/src/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/TicTacToeContract/src/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/TicTacToeContract/src/TicTacToe.cs b/templates/TicTacToeContract/src/TicTacToe.cs new file mode 100644 index 0000000..eab6672 --- /dev/null +++ b/templates/TicTacToeContract/src/TicTacToe.cs @@ -0,0 +1,170 @@ +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; + +namespace AElf.Contracts.TicTacToe +{ + // Contract class must inherit the base class generated from the proto file + public class TicTacToe : TicTacToeContainer.TicTacToeBase + { + private const int BoardSize = 3; + + public override Empty Initialize(Empty input) + { + if (State.Initialized.Value) + { + return new Empty(); + } + State.Initialized.Value = true; + State.Owner.Value = Context.Sender; + ResetBoard(); + return new Empty(); + } + + public override StringValue StartGame(Empty input) + { + if (!State.Initialized.Value) + { + return new StringValue { Value = "Contract not initialized." }; + } + + ResetBoard(); + State.CurrentPlayer.Value = "X"; + State.GameStatus.Value = "ongoing"; + State.Winner.Value = ""; + return new StringValue { Value = "Game started. Player X's turn." }; + } + + public override StringValue MakeMove(MoveInput input) + { + if (State.GameStatus.Value != "ongoing") + { + return new StringValue { Value = "Game is not ongoing." }; + } + + var board = GetBoardArray(); + if (board[input.X, input.Y] != "") + { + return new StringValue { Value = "Invalid move. Cell is already occupied." }; + } + + board[input.X, input.Y] = State.CurrentPlayer.Value; + SaveBoard(board); + + if (CheckWinner()) + { + State.GameStatus.Value = "finished"; + State.Winner.Value = State.CurrentPlayer.Value; + return new StringValue { Value = $"Player {State.CurrentPlayer.Value} wins!" }; + } + + if (IsBoardFull(board)) + { + State.GameStatus.Value = "draw"; + return new StringValue { Value = "It's a draw!" }; + } + + State.CurrentPlayer.Value = State.CurrentPlayer.Value == "X" ? "O" : "X"; + return new StringValue { Value = $"Player {State.CurrentPlayer.Value}'s turn." }; + } + + public override Board GetBoard(Empty input) + { + var board = GetBoardArray(); + var boardMessage = new Board(); + + for (var i = 0; i < 3; i++) // Adjusted to 3 for a 3x3 Tic-Tac-Toe board + { + var row = new List(); + for (var j = 0; j < 3; j++) + { + row.Add(board[i, j]); + } + boardMessage.Rows.Add(string.Join(",", row)); + } + + return boardMessage; + } + + public override GameStatus GetGameStatus(Empty input) + { + return new GameStatus + { + Status = State.GameStatus.Value, + Winner = State.Winner.Value + }; + } + + public override BoolValue GetInitialStatus(Empty input){ + return new BoolValue { Value = State.Initialized.Value }; + } + + private void ResetBoard() + { + var emptyBoard = new string[BoardSize, BoardSize]; + for (var i = 0; i < BoardSize; i++) + { + for (var j = 0; j < BoardSize; j++) + { + emptyBoard[i, j] = ""; + } + } + SaveBoard(emptyBoard); + } + + private string[,] GetBoardArray() + { + var boardString = State.Board.Value; + var rows = boardString.Split(';'); + var board = new string[BoardSize, BoardSize]; + for (var i = 0; i < BoardSize; i++) + { + var cells = rows[i].Split(','); + for (var j = 0; j < BoardSize; j++) + { + board[i, j] = cells[j]; + } + } + return board; + } + + private void SaveBoard(string[,] board) + { + var rows = new string[BoardSize]; + for (var i = 0; i < BoardSize; i++) + { + rows[i] = string.Join(",", board[i, 0], board[i, 1], board[i, 2]); + } + State.Board.Value = string.Join(";", rows); + } + + private bool CheckWinner() + { + var board = GetBoardArray(); + var player = State.CurrentPlayer.Value; + + for (var i = 0; i < BoardSize; i++) + { + if (board[i, 0] == player && board[i, 1] == player && board[i, 2] == player) return true; + if (board[0, i] == player && board[1, i] == player && board[2, i] == player) return true; + } + + if (board[0, 0] == player && board[1, 1] == player && board[2, 2] == player) return true; + if (board[0, 2] == player && board[1, 1] == player && board[2, 0] == player) return true; + + return false; + } + + private bool IsBoardFull(string[,] board) + { + for (var i = 0; i < BoardSize; i++) + { + for (var j = 0; j < BoardSize; j++) + { + if (board[i, j] == "") return false; + } + } + return true; + } + } + +} \ No newline at end of file diff --git a/templates/TicTacToeContract/src/TicTacToe.csproj b/templates/TicTacToeContract/src/TicTacToe.csproj new file mode 100644 index 0000000..dcadc44 --- /dev/null +++ b/templates/TicTacToeContract/src/TicTacToe.csproj @@ -0,0 +1,27 @@ + + + net6.0 + AElf.Contracts.TicTacToe + true + true + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + \ No newline at end of file diff --git a/templates/TicTacToeContract/src/TicTacToeState.cs b/templates/TicTacToeContract/src/TicTacToeState.cs new file mode 100644 index 0000000..6712a99 --- /dev/null +++ b/templates/TicTacToeContract/src/TicTacToeState.cs @@ -0,0 +1,17 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.TicTacToe +{ + // The state class is access the blockchain state + public partial class TicTacToeState : ContractState + { + // A state to check if contract is initialized + public BoolState Initialized { get; set; } + public SingletonState
Owner { get; set; } + public StringState Board { get; set; } // Board state as a concatenated string + public StringState CurrentPlayer { get; set; } // X or O + public StringState GameStatus { get; set; } // ongoing, finished, draw + public StringState Winner { get; set; } // X or O + } +} \ No newline at end of file diff --git a/templates/TicTacToeContract/test/Protobuf/message/authority_info.proto b/templates/TicTacToeContract/test/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..9a1309b --- /dev/null +++ b/templates/TicTacToeContract/test/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.TicTacToe"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/TicTacToeContract/test/Protobuf/reference/acs12.proto b/templates/TicTacToeContract/test/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/TicTacToeContract/test/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/TicTacToeContract/test/Protobuf/stub/hello_world_contract.proto b/templates/TicTacToeContract/test/Protobuf/stub/hello_world_contract.proto new file mode 100644 index 0000000..0d1d3d5 --- /dev/null +++ b/templates/TicTacToeContract/test/Protobuf/stub/hello_world_contract.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.TicTacToe"; + +service TicTacToe { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.TicTacToe.TicTacToeState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + // Actions (methods that modify contract state) + // Stores the value in contract state + rpc Update (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + // Views (methods that don't modify contract state) + // Get the value stored from contract state + rpc Read (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +// An event that will be emitted from contract method call +message UpdatedMessage { + option (aelf.is_event) = true; + string value = 1; +} \ No newline at end of file diff --git a/templates/TicTacToeContract/test/TicTacToe.Tests.csproj b/templates/TicTacToeContract/test/TicTacToe.Tests.csproj new file mode 100644 index 0000000..2ea8f66 --- /dev/null +++ b/templates/TicTacToeContract/test/TicTacToe.Tests.csproj @@ -0,0 +1,51 @@ + + + net6.0 + AElf.Contracts.TicTacToe + + + + 0436;CS2002 + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + nocontract + + + nocontract + + + stub + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/TicTacToeContract/test/TicTacToeTests.cs b/templates/TicTacToeContract/test/TicTacToeTests.cs new file mode 100644 index 0000000..98a814e --- /dev/null +++ b/templates/TicTacToeContract/test/TicTacToeTests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; + +namespace AElf.Contracts.TicTacToe +{ + // This class is unit test class, and it inherit TestBase. Write your unit test code inside it + public class TicTacToeTests : TestBase + { + [Fact] + public async Task Update_ShouldUpdateMessageAndFireEvent() + { + // Arrange + var inputValue = "Hello, World!"; + var input = new StringValue { Value = inputValue }; + + // Act + await TicTacToeStub.Update.SendAsync(input); + + // Assert + var updatedMessage = await TicTacToeStub.Read.CallAsync(new Empty()); + updatedMessage.Value.ShouldBe(inputValue); + } + } + +} \ No newline at end of file diff --git a/templates/TicTacToeContract/test/_Setup.cs b/templates/TicTacToeContract/test/_Setup.cs new file mode 100644 index 0000000..afd128f --- /dev/null +++ b/templates/TicTacToeContract/test/_Setup.cs @@ -0,0 +1,31 @@ +using AElf.Cryptography.ECDSA; +using AElf.Testing.TestBase; + +namespace AElf.Contracts.TicTacToe +{ + // The Module class load the context required for unit testing + public class Module : ContractTestModule + { + + } + + // The TestBase class inherit ContractTestBase class, it defines Stub classes and gets instances required for unit testing + public class TestBase : ContractTestBase + { + // The Stub class for unit testing + internal readonly TicTacToeContainer.TicTacToeStub TicTacToeStub; + // A key pair that can be used to interact with the contract instance + private ECKeyPair DefaultKeyPair => Accounts[0].KeyPair; + + public TestBase() + { + TicTacToeStub = GetTicTacToeContractStub(DefaultKeyPair); + } + + private TicTacToeContainer.TicTacToeStub GetTicTacToeContractStub(ECKeyPair senderKeyPair) + { + return GetTester(ContractAddress, senderKeyPair); + } + } + +} \ No newline at end of file diff --git a/templates/ToDoContract/.template.config/template.json b/templates/ToDoContract/.template.config/template.json new file mode 100644 index 0000000..769e38c --- /dev/null +++ b/templates/ToDoContract/.template.config/template.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "AElf", + "classifications": [ + "AElf/SmartContract" + ], + "identity": "AElf.Contract.ToDo.Template", + "name": "AElf Contract ToDo Template", + "shortName": "aelf-todo", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "ToDo", + "symbols": { + "NamespacePath": { + "type": "parameter", + "replaces": "AElf.Contracts.ToDo" + } + } +} \ No newline at end of file diff --git a/templates/ToDoContract/src/Protobuf/contract/todo_app.proto b/templates/ToDoContract/src/Protobuf/contract/todo_app.proto new file mode 100644 index 0000000..5c1fd90 --- /dev/null +++ b/templates/ToDoContract/src/Protobuf/contract/todo_app.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.ToDo"; +service ToDo { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.ToDo.ToDoState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc CreateTask (TaskInput) returns (google.protobuf.StringValue) { + } + rpc UpdateTask (TaskUpdateInput) returns (google.protobuf.Empty) { + } + rpc DeleteTask (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + rpc ListTasks (google.protobuf.StringValue) returns (TaskList) { + option (aelf.is_view) = true; + } + rpc GetTask (google.protobuf.StringValue) returns (Task) { + option (aelf.is_view) = true; + } + rpc GetInitialStatus (google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} +// A message to represent a task +message Task { + string task_id = 1; + string name = 2; + string description = 3; + string category = 4; + string status = 5; + string owner = 6; + int64 created_at = 7; + int64 updated_at = 8; +} +// Input for creating a task +message TaskInput { + string name = 1; + string description = 2; + string category = 3; +} +// Input for updating a task +message TaskUpdateInput { + string task_id = 1; + string name = 2; + string description = 3; + string category = 4; + string status = 5; +} +// List of tasks +message TaskList { + repeated Task tasks = 1; +} \ No newline at end of file diff --git a/templates/ToDoContract/src/Protobuf/message/authority_info.proto b/templates/ToDoContract/src/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..610d44d --- /dev/null +++ b/templates/ToDoContract/src/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.ToDo"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/ToDoContract/src/Protobuf/reference/acs12.proto b/templates/ToDoContract/src/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/ToDoContract/src/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/ToDoContract/src/ToDo.cs b/templates/ToDoContract/src/ToDo.cs new file mode 100644 index 0000000..9234605 --- /dev/null +++ b/templates/ToDoContract/src/ToDo.cs @@ -0,0 +1,108 @@ +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; +namespace AElf.Contracts.ToDo +{ + public class ToDo : ToDoContainer.ToDoBase + { + public override Empty Initialize(Empty input) + { + if (State.Initialized.Value) + { + return new Empty(); + } + State.Initialized.Value = true; + State.Owner.Value = Context.Sender; + State.TaskIds.Value = ""; + State.TaskCounter.Value = 0; + return new Empty(); + } + public override StringValue CreateTask(TaskInput input) + { + if (!State.Initialized.Value) + { + return new StringValue { Value = "Contract not initialized." }; + } + var taskId = (State.TaskCounter.Value + 1).ToString(); + State.TaskCounter.Value++; + var timestamp = Context.CurrentBlockTime.Seconds; + // Create task dictionary entry directly in ToDo class + State.Tasks[taskId] = new Task + { + TaskId = taskId, + Name = input.Name, + Description = input.Description, + Category = input.Category, + Status = "pending", + CreatedAt = timestamp, + UpdatedAt = timestamp, + Owner = Context.Sender.ToString().Trim('"'), + }; + State.TaskExistence[taskId] = true; + // Append task ID to the list of IDs + var existingTaskIds = State.TaskIds.Value; + existingTaskIds += string.IsNullOrEmpty(existingTaskIds) ? taskId : $",{taskId}"; + State.TaskIds.Value = existingTaskIds; + return new StringValue { Value = taskId }; + } + public override Empty UpdateTask(TaskUpdateInput input) + { + var task = State.Tasks[input.TaskId]; + if (task == null) + { + return new Empty(); // Handle case if task doesn't exist + } + task.Name = input.Name ?? task.Name; + task.Description = input.Description ?? task.Description; + task.Category = input.Category ?? task.Category; + task.Status = input.Status ?? task.Status; + task.UpdatedAt = Context.CurrentBlockTime.Seconds; + State.Tasks[input.TaskId] = task; + return new Empty(); + } + public override Empty DeleteTask(StringValue input) + { + State.Tasks.Remove(input.Value); + State.TaskExistence.Remove(input.Value); + // Remove task ID from the list of IDs + var existingTaskIds = State.TaskIds.Value.Split(','); + var newTaskIds = new List(existingTaskIds.Length); + foreach (var taskId in existingTaskIds) + { + if (taskId != input.Value) + { + newTaskIds.Add(taskId); + } + } + State.TaskIds.Value = string.Join(",", newTaskIds); + return new Empty(); + } + public override TaskList ListTasks(StringValue input) + { + var owner = input.Value; // Get the owner value from the input + var taskList = new TaskList(); + var taskIds = State.TaskIds.Value.Split(','); + foreach (var taskId in taskIds) + { + var task = State.Tasks[taskId]; + if (task != null && task.Owner == owner) // Filter tasks by owner + { + taskList.Tasks.Add(task); + } + } + return taskList; + } + public override Task GetTask(StringValue input) + { + var task = State.Tasks[input.Value]; + if (task == null) + { + return new Task { TaskId = input.Value, Name = "Task not found." }; + } + return task; + } + public override BoolValue GetInitialStatus(Empty input) + { + return new BoolValue { Value = State.Initialized.Value }; + } + } +} \ No newline at end of file diff --git a/templates/ToDoContract/src/ToDo.csproj b/templates/ToDoContract/src/ToDo.csproj new file mode 100644 index 0000000..da00f53 --- /dev/null +++ b/templates/ToDoContract/src/ToDo.csproj @@ -0,0 +1,27 @@ + + + net6.0 + AElf.Contracts.ToDo + true + true + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + \ No newline at end of file diff --git a/templates/ToDoContract/src/ToDoState.cs b/templates/ToDoContract/src/ToDoState.cs new file mode 100644 index 0000000..986f9e5 --- /dev/null +++ b/templates/ToDoContract/src/ToDoState.cs @@ -0,0 +1,15 @@ +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.ToDo +{ + public class ToDoState : ContractState + { + public BoolState Initialized { get; set; } + public SingletonState
Owner { get; set; } + public MappedState Tasks { get; set; } // Mapping of task ID to Task + public MappedState TaskExistence { get; set; } // Mapping to track task existence + public StringState TaskIds { get; set; } // Concatenated string of task IDs + public Int32State TaskCounter { get; set; } // Counter for generating unique IDs + } +} \ No newline at end of file diff --git a/templates/ToDoContract/test/Protobuf/message/authority_info.proto b/templates/ToDoContract/test/Protobuf/message/authority_info.proto new file mode 100644 index 0000000..610d44d --- /dev/null +++ b/templates/ToDoContract/test/Protobuf/message/authority_info.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "aelf/core.proto"; + +option csharp_namespace = "AElf.Contracts.ToDo"; + +message AuthorityInfo { + aelf.Address contract_address = 1; + aelf.Address owner_address = 2; +} \ No newline at end of file diff --git a/templates/ToDoContract/test/Protobuf/reference/acs12.proto b/templates/ToDoContract/test/Protobuf/reference/acs12.proto new file mode 100644 index 0000000..e6ead4b --- /dev/null +++ b/templates/ToDoContract/test/Protobuf/reference/acs12.proto @@ -0,0 +1,35 @@ +/** + * AElf Standards ACS12(User Contract Standard) + * + * Used to manage user contract. + */ +syntax = "proto3"; + +package acs12; + +import public "aelf/options.proto"; +import public "google/protobuf/empty.proto"; +import public "google/protobuf/wrappers.proto"; +import "aelf/core.proto"; + +option (aelf.identity) = "acs12"; +option csharp_namespace = "AElf.Standards.ACS12"; + +service UserContract{ + +} + +//Specified method fee for user contract. +message UserContractMethodFees { + // List of fees to be charged. + repeated UserContractMethodFee fees = 2; + // Optional based on the implementation of SetConfiguration method. + bool is_size_fee_free = 3; +} + +message UserContractMethodFee { + // 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/templates/ToDoContract/test/Protobuf/stub/hello_world_contract.proto b/templates/ToDoContract/test/Protobuf/stub/hello_world_contract.proto new file mode 100644 index 0000000..5e8cf06 --- /dev/null +++ b/templates/ToDoContract/test/Protobuf/stub/hello_world_contract.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.ToDo"; + +service ToDo { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.ToDo.ToDoState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + // Actions (methods that modify contract state) + // Stores the value in contract state + rpc Update (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + + // Views (methods that don't modify contract state) + // Get the value stored from contract state + rpc Read (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +// An event that will be emitted from contract method call +message UpdatedMessage { + option (aelf.is_event) = true; + string value = 1; +} \ No newline at end of file diff --git a/templates/ToDoContract/test/ToDo.Tests.csproj b/templates/ToDoContract/test/ToDo.Tests.csproj new file mode 100644 index 0000000..fbda0cb --- /dev/null +++ b/templates/ToDoContract/test/ToDo.Tests.csproj @@ -0,0 +1,51 @@ + + + net6.0 + AElf.Contracts.ToDo + + + + 0436;CS2002 + + + $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/ + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + nocontract + + + nocontract + + + stub + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/ToDoContract/test/ToDoTests.cs b/templates/ToDoContract/test/ToDoTests.cs new file mode 100644 index 0000000..3b16d56 --- /dev/null +++ b/templates/ToDoContract/test/ToDoTests.cs @@ -0,0 +1,137 @@ +using AElf.Contracts.TestKit; +using AElf.Kernel; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; +using Shouldly; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace AElf.Contracts.ToDo.Tests +{ + public class ToDoTests : ToDoContractTestBase + { + private async Task InitializeContractAsync() + { + var result = await ToDoContractStub.Initialize.SendAsync(new Empty()); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + } + + [Fact] + public async Task Initialize_Contract_Test() + { + await InitializeContractAsync(); + + var status = await ToDoContractStub.GetInitialStatus.CallAsync(new Empty()); + status.Value.ShouldBeTrue(); + } + + [Fact] + public async Task CreateTask_Test() + { + await InitializeContractAsync(); + + var taskInput = new TaskInput + { + Name = "Test Task", + Description = "This is a test task.", + Category = "General" + }; + + var result = await ToDoContractStub.CreateTask.SendAsync(taskInput); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var taskId = result.Output.Value; + var task = await ToDoContractStub.GetTask.CallAsync(new StringValue { Value = taskId }); + + task.TaskId.ShouldBe(taskId); + task.Name.ShouldBe(taskInput.Name); + task.Description.ShouldBe(taskInput.Description); + task.Category.ShouldBe(taskInput.Category); + task.Status.ShouldBe("pending"); + } + + [Fact] + public async Task UpdateTask_Test() + { + await InitializeContractAsync(); + + var taskInput = new TaskInput + { + Name = "Task to Update", + Description = "Update this task.", + Category = "General" + }; + + var createResult = await ToDoContractStub.CreateTask.SendAsync(taskInput); + var taskId = createResult.Output.Value; + + var updateInput = new TaskUpdateInput + { + TaskId = taskId, + Name = "Updated Task", + Description = "Task has been updated.", + Category = "Work", + Status = "completed" + }; + + var updateResult = await ToDoContractStub.UpdateTask.SendAsync(updateInput); + updateResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var updatedTask = await ToDoContractStub.GetTask.CallAsync(new StringValue { Value = taskId }); + + updatedTask.Name.ShouldBe(updateInput.Name); + updatedTask.Description.ShouldBe(updateInput.Description); + updatedTask.Category.ShouldBe(updateInput.Category); + updatedTask.Status.ShouldBe(updateInput.Status); + } + + [Fact] + public async Task DeleteTask_Test() + { + await InitializeContractAsync(); + + var taskInput = new TaskInput + { + Name = "Task to Delete", + Description = "This task will be deleted.", + Category = "General" + }; + + var createResult = await ToDoContractStub.CreateTask.SendAsync(taskInput); + var taskId = createResult.Output.Value; + + var deleteResult = await ToDoContractStub.DeleteTask.SendAsync(new StringValue { Value = taskId }); + deleteResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var deletedTask = await ToDoContractStub.GetTask.CallAsync(new StringValue { Value = taskId }); + deletedTask.Name.ShouldBe("Task not found."); + } + + [Fact] + public async Task ListTasks_Test() + { + await InitializeContractAsync(); + + var taskInput1 = new TaskInput + { + Name = "Task 1", + Description = "First task.", + Category = "General" + }; + + var taskInput2 = new TaskInput + { + Name = "Task 2", + Description = "Second task.", + Category = "Work" + }; + + await ToDoContractStub.CreateTask.SendAsync(taskInput1); + await ToDoContractStub.CreateTask.SendAsync(taskInput2); + + var taskList = await ToDoContractStub.ListTasks.CallAsync(new StringValue { Value = DefaultSender.ToString() }); + + taskList.Tasks.Count.ShouldBe(2); + } + } +} diff --git a/templates/ToDoContract/test/_Setup.cs b/templates/ToDoContract/test/_Setup.cs new file mode 100644 index 0000000..cd2a87c --- /dev/null +++ b/templates/ToDoContract/test/_Setup.cs @@ -0,0 +1,31 @@ +using AElf.Cryptography.ECDSA; +using AElf.Testing.TestBase; + +namespace AElf.Contracts.ToDo +{ + // The Module class load the context required for unit testing + public class Module : ContractTestModule + { + + } + + // The TestBase class inherit ContractTestBase class, it defines Stub classes and gets instances required for unit testing + public class TestBase : ContractTestBase + { + // The Stub class for unit testing + internal readonly ToDoContainer.ToDoStub ToDoStub; + // A key pair that can be used to interact with the contract instance + private ECKeyPair DefaultKeyPair => Accounts[0].KeyPair; + + public TestBase() + { + ToDoStub = GetToDoContractStub(DefaultKeyPair); + } + + private ToDoContainer.ToDoStub GetToDoContractStub(ECKeyPair senderKeyPair) + { + return GetTester(ContractAddress, senderKeyPair); + } + } + +} \ No newline at end of file