From ff5d6cf7b05421a475f88f1aa48f69d396b26098 Mon Sep 17 00:00:00 2001 From: vasmohi Date: Fri, 27 Sep 2024 12:06:58 +0530 Subject: [PATCH 1/5] feat: Added aelf-samples submodule --- .gitmodules | 3 +++ aelf-samples | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 aelf-samples diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..58c4711 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "aelf-samples"] + path = aelf-samples + url = https://github.com/AElfProject/aelf-samples.git diff --git a/aelf-samples b/aelf-samples new file mode 160000 index 0000000..6e83558 --- /dev/null +++ b/aelf-samples @@ -0,0 +1 @@ +Subproject commit 6e83558cde5c2b045d894b7aa8cc76e6e4af908e From 3dd98ba46fffc62b328c164bffe5600982a1d79e Mon Sep 17 00:00:00 2001 From: vasmohi Date: Fri, 27 Sep 2024 12:17:23 +0530 Subject: [PATCH 2/5] feat: Added ToDo, TicTacToe & ExpenseTracker contracts to playground --- .DS_Store | Bin 0 -> 6148 bytes templates/.DS_Store | Bin 0 -> 10244 bytes templates/AElf.Contract.Template.csproj | 3 + templates/ExpenseTrackerContract/.DS_Store | Bin 0 -> 6148 bytes .../ExpenseTrackerContract/src/.DS_Store | Bin 0 -> 6148 bytes .../src/ExpenseTracker.cs | 117 ++++++++++++ .../src/ExpenseTracker.csproj | 27 +++ .../src/ExpenseTrackerState.cs | 15 ++ .../Protobuf/contract/expense_tracker.proto | 58 ++++++ .../src/Protobuf/message/authority_info.proto | 10 ++ .../src/Protobuf/reference/acs12.proto | 35 ++++ .../test/ExpenseTracker.Tests.csproj | 51 ++++++ .../test/ExpenseTrackerTests.cs | 27 +++ .../Protobuf/message/authority_info.proto | 10 ++ .../test/Protobuf/reference/acs12.proto | 35 ++++ .../Protobuf/stub/hello_world_contract.proto | 31 ++++ .../ExpenseTrackerContract/test/_Setup.cs | 31 ++++ .../contract/hello_world_contract.proto | 48 +++++ .../src/Protobuf/message/authority_info.proto | 10 ++ .../src/Protobuf/reference/acs12.proto | 35 ++++ templates/TicTacToeContract/src/TicTacToe.cs | 170 ++++++++++++++++++ .../TicTacToeContract/src/TicTacToe.csproj | 27 +++ .../TicTacToeContract/src/TicTacToeState.cs | 17 ++ .../Protobuf/message/authority_info.proto | 10 ++ .../test/Protobuf/reference/acs12.proto | 35 ++++ .../Protobuf/stub/hello_world_contract.proto | 31 ++++ .../test/TicTacToe.Tests.csproj | 51 ++++++ .../TicTacToeContract/test/TicTacToeTests.cs | 27 +++ templates/TicTacToeContract/test/_Setup.cs | 31 ++++ .../contract/hello_world_contract.proto | 58 ++++++ .../src/Protobuf/message/authority_info.proto | 10 ++ .../src/Protobuf/reference/acs12.proto | 35 ++++ templates/ToDoContract/src/ToDo.cs | 108 +++++++++++ templates/ToDoContract/src/ToDo.csproj | 27 +++ templates/ToDoContract/src/ToDoState.cs | 15 ++ .../Protobuf/message/authority_info.proto | 10 ++ .../test/Protobuf/reference/acs12.proto | 35 ++++ .../Protobuf/stub/hello_world_contract.proto | 31 ++++ templates/ToDoContract/test/ToDo.Tests.csproj | 51 ++++++ templates/ToDoContract/test/ToDoTests.cs | 137 ++++++++++++++ templates/ToDoContract/test/_Setup.cs | 31 ++++ 41 files changed, 1490 insertions(+) create mode 100644 .DS_Store create mode 100644 templates/.DS_Store create mode 100644 templates/ExpenseTrackerContract/.DS_Store create mode 100644 templates/ExpenseTrackerContract/src/.DS_Store create mode 100644 templates/ExpenseTrackerContract/src/ExpenseTracker.cs create mode 100644 templates/ExpenseTrackerContract/src/ExpenseTracker.csproj create mode 100644 templates/ExpenseTrackerContract/src/ExpenseTrackerState.cs create mode 100644 templates/ExpenseTrackerContract/src/Protobuf/contract/expense_tracker.proto create mode 100644 templates/ExpenseTrackerContract/src/Protobuf/message/authority_info.proto create mode 100644 templates/ExpenseTrackerContract/src/Protobuf/reference/acs12.proto create mode 100644 templates/ExpenseTrackerContract/test/ExpenseTracker.Tests.csproj create mode 100644 templates/ExpenseTrackerContract/test/ExpenseTrackerTests.cs create mode 100644 templates/ExpenseTrackerContract/test/Protobuf/message/authority_info.proto create mode 100644 templates/ExpenseTrackerContract/test/Protobuf/reference/acs12.proto create mode 100644 templates/ExpenseTrackerContract/test/Protobuf/stub/hello_world_contract.proto create mode 100644 templates/ExpenseTrackerContract/test/_Setup.cs create mode 100644 templates/TicTacToeContract/src/Protobuf/contract/hello_world_contract.proto create mode 100644 templates/TicTacToeContract/src/Protobuf/message/authority_info.proto create mode 100644 templates/TicTacToeContract/src/Protobuf/reference/acs12.proto create mode 100644 templates/TicTacToeContract/src/TicTacToe.cs create mode 100644 templates/TicTacToeContract/src/TicTacToe.csproj create mode 100644 templates/TicTacToeContract/src/TicTacToeState.cs create mode 100644 templates/TicTacToeContract/test/Protobuf/message/authority_info.proto create mode 100644 templates/TicTacToeContract/test/Protobuf/reference/acs12.proto create mode 100644 templates/TicTacToeContract/test/Protobuf/stub/hello_world_contract.proto create mode 100644 templates/TicTacToeContract/test/TicTacToe.Tests.csproj create mode 100644 templates/TicTacToeContract/test/TicTacToeTests.cs create mode 100644 templates/TicTacToeContract/test/_Setup.cs create mode 100644 templates/ToDoContract/src/Protobuf/contract/hello_world_contract.proto create mode 100644 templates/ToDoContract/src/Protobuf/message/authority_info.proto create mode 100644 templates/ToDoContract/src/Protobuf/reference/acs12.proto create mode 100644 templates/ToDoContract/src/ToDo.cs create mode 100644 templates/ToDoContract/src/ToDo.csproj create mode 100644 templates/ToDoContract/src/ToDoState.cs create mode 100644 templates/ToDoContract/test/Protobuf/message/authority_info.proto create mode 100644 templates/ToDoContract/test/Protobuf/reference/acs12.proto create mode 100644 templates/ToDoContract/test/Protobuf/stub/hello_world_contract.proto create mode 100644 templates/ToDoContract/test/ToDo.Tests.csproj create mode 100644 templates/ToDoContract/test/ToDoTests.cs create mode 100644 templates/ToDoContract/test/_Setup.cs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ebc09f7bce822038f8ee5c087ec24c39553e4624 GIT binary patch literal 6148 zcmeHKL2uJA6n^dsO*hzjVA3u~k+`;{rPGAOrIhZ#fy;{E0H`Eu=pxd%YEpWbCiM*e zfxp0&KLY=S6MWBhswN$9nGhhq%KjYteSUtQ)4C=i(VLIjL^UFEP#DYGs4g)c=dxiX z)3XZ{Jjdfmp8ApW@yruivkF)R{%!^MyIZCvjVYwG@cn&=vF3MWDv909k3358nStZe z9Ib$o0{2saTUl|>!0_|l;&#D%d*vNsWid&_3JRPL1cDG64H`Nqg6d-f46 zDJnp`KOfijXM1f;99bfh%l2iRvj-oEReQk3Dqcfrjy&>fSNpWiuF?q|V;y~Z0&GIP zQGvm`lxJ{GT4jEVHr^byL%(78i)3ofna}<-3X`0;mFb-yl5RL-4ynG(UBf03wc|1xKcc2HHs#6`- z4$jYy4(o2Gb9hmA&)co`{knV9xp#3*IfL()2jp%_1O$BPIFjov=ZVp1zG2S))OO2XNLPf?rCbBR$6k#qN!d31hx*Bb5 z6|f5A71+{`H9r53e}4bZlkAgKz$)-xDIm)ILBEHT%-On;9G|r=%4-xh_FHOH6jbIo hRt-Li4^Wh0%;g5KYjCL%Juv%6K*?Y$tH2*s;0Ic=|8M{R literal 0 HcmV?d00001 diff --git a/templates/.DS_Store b/templates/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..baa7aaac2e3ff37c3a52d491d9056f6316541566 GIT binary patch literal 10244 zcmeHM&1(}u6n~pmyRj{{e&MCC;Kf6Ui7iF&lBOvt_yOI}9#m>R)^_b?Cu~xqltRvW z@+^qr#hWL=zrg=M{};XJZ$4VKo84wZLE17CX5Q@V`@Pxu?R%5F2@x5k?M8vfA|ex& z%c%)8F@@jfwXbB<$PA@&dk2Md+UqLMu+#nypFqyUhk7#0P6DOpsqC#8VGrpz7;1vONPVMsXc zTU;HosANw{2`59s$xt&JDnl{E?7&$0Dr!DD_^Va%)T^{cg2*XgcLU0Py(r|SAnAOLQ|fy$FL*Ke?H ziEaChw!qr*hM6_9>r+>Eb{6Jmt+~1R-C1j=kT2YtwHD@X?(SyI%hzt)URmGrnm&8O zsmJD~HbI({*Zb{T^oClqw^6X`tl8SPFeJXo%4Nlw`R9H0-Ln0Id&*GH&}5Ax+h}bb zZ5=P|!1tKHfoJF3UZA`1HV+?0YvMf1qs9Q=0T`qr;y# z^Rv%F1s3XvTfQ4qz`>w3Ux$T{p{Sh(YJi1P600dz8WW)@PziCF3K<)XDUdNSm;x1v zyb0kkWq8e@aYEnVGoM2~lZZPdo|3q}82?DrP4J3C^kaP?Q9s555_Kc*eUetOPXzeJ zck%9h>{asDLW|l5cP#Nsd-9mka$_v-)Nhu&`#Bn^NIF82!KaS^lJ=e{1$qV{L zC3{k01m(Z~8PKg9kF&ZI`Eg8smjacL>7_!(=6orTG1*@VR3r~f%oihBpw9o*X?u)+ JByt$`ihoI#*dYJ_ literal 0 HcmV?d00001 diff --git a/templates/AElf.Contract.Template.csproj b/templates/AElf.Contract.Template.csproj index eea7148..6a53838 100644 --- a/templates/AElf.Contract.Template.csproj +++ b/templates/AElf.Contract.Template.csproj @@ -20,6 +20,9 @@ + + + diff --git a/templates/ExpenseTrackerContract/.DS_Store b/templates/ExpenseTrackerContract/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..98be69445560e8fc3d2a0696d7e4f76e42fb3280 GIT binary patch literal 6148 zcmeHKyH3L}6t&v|sVbxnAi)-iwStuAz!C}!tOUUTs5G>Kh!CSDr9)LIJNyeXKf>?8 zxweZaj}fGzd!_4R--lBlSGKPZLNZ&S>7Cu>=kP9$(#-Q-WT9A^nx1iHoQiWE zT&sSNwX$B?Xmw9%bfi=qj{2?eIO_D9)wx}jWUVOabag`1?jYpsBud(<-%!1zo$B1c zIGnOmZdMlugEeoX=B{q74{PqA?$y_8?&hX99G0E=rIqc&=1qK?s0YKm#Gg{jti=&L zqGOkHA9RygC0DRebV+2Q13I!#WIVz5kqiu3m;q*h8JJuK?D6PSCU*(^%b5XY;FlPn z^TCA@`WAD8`shHTO8_7}BekHfAOAp?3_#yvZV(oTG^s$7Ds71&ZS5dUI{dzk&kdS% zA@!FrkNvW=6^gW_hjewgkiJ1~nE_^CoPnYlR_XrV|GEAjPvQ|XzzqB=24rb3*lS=( z_HO;MIJ#?H=mnI7;&OxUDd^~{7_oE}FF>`x?otQnTg(l@0^x^%rhyw~;7=KN1KJc` A=Kufz literal 0 HcmV?d00001 diff --git a/templates/ExpenseTrackerContract/src/.DS_Store b/templates/ExpenseTrackerContract/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..207e02696297b56457082a9bf812bca895701e06 GIT binary patch literal 6148 zcmeHKO>fgc5S?ue*|b6m5=G^Jv=Y~-I8e%giwW(4TZJon02J&bfT{6DwnLDjNWQoK zrDy&K|AiBM`w>K(77j=VMLW{$8_&+ndY`qsULs=M@t{N0A|eh|=xw06L{z`hC0Vnc z29W6*85K07Bg!dSigpgifMei)V?eFln{+^5XoQvf>-z1}2|PLNP@jtDsqDw8j1jH2 zil@+>A6j4$ZT-0d>gOcsW3AG8ttP#kAvhcH3fa&!5ejeCyu*Cog&@>8DJpNkSMlXe~D^&fyHk&xwBy^DLFwd-&$@ z^Eihq$AJCBd~@Yy<|gDAa18t{18RQ|P=$fT%Anpl(C8}w(1TkGZ1sbna)iaeVr39L z5N1+=CY9MM1~cjK3mX?$tPGlTV)pXE?8waCP?#Pa^@R>678rD`W56-6$iTXJY^n4A z;OhJTVv>7u3^)c>iUH>Bhx=Vj$)2rClT&A{fWCz)QE`<)or1<5$J$Uw@itTo`h+ro VfyK%oJP`LIplNW8W8j}MZ~;~+mx}-Z literal 0 HcmV?d00001 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/TicTacToeContract/src/Protobuf/contract/hello_world_contract.proto b/templates/TicTacToeContract/src/Protobuf/contract/hello_world_contract.proto new file mode 100644 index 0000000..626e2e5 --- /dev/null +++ b/templates/TicTacToeContract/src/Protobuf/contract/hello_world_contract.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/src/Protobuf/contract/hello_world_contract.proto b/templates/ToDoContract/src/Protobuf/contract/hello_world_contract.proto new file mode 100644 index 0000000..5c1fd90 --- /dev/null +++ b/templates/ToDoContract/src/Protobuf/contract/hello_world_contract.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 From f608fe86c1f4ae07f06752b057fb75b58f2dd39d Mon Sep 17 00:00:00 2001 From: vasmohi Date: Mon, 30 Sep 2024 09:00:15 +0530 Subject: [PATCH 3/5] Added templates configs for ToDo, TicTacToe & ExpenseTracker --- .../.template.config/template.json | 21 +++++++++++++++++++ .../.template.config/template.json | 21 +++++++++++++++++++ .../.template.config/template.json | 21 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 templates/ExpenseTrackerContract/.template.config/template.json create mode 100644 templates/TicTacToeContract/.template.config/template.json create mode 100644 templates/ToDoContract/.template.config/template.json 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/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/ToDoContract/.template.config/template.json b/templates/ToDoContract/.template.config/template.json new file mode 100644 index 0000000..53593e3 --- /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.ToDoApp.Template", + "name": "AElf Contract ToDoApp Template", + "shortName": "aelf-todo-app", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "ToDoApp", + "symbols": { + "NamespacePath": { + "type": "parameter", + "replaces": "AElf.Contracts.ToDoApp" + } + } +} \ No newline at end of file From 92780ac5f3c1dc17954d58b21bc40408037735fa Mon Sep 17 00:00:00 2001 From: vasmohi Date: Mon, 30 Sep 2024 09:15:34 +0530 Subject: [PATCH 4/5] feat: Added workflows for staging & prod to publish aelf-samples --- .../workflows/publish-aelf-samples-prod.yml | 60 +++++++++++++++++++ .../publish-aelf-samples-staging.yml | 34 +++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/publish-aelf-samples-prod.yml create mode 100644 .github/workflows/publish-aelf-samples-staging.yml diff --git a/.github/workflows/publish-aelf-samples-prod.yml b/.github/workflows/publish-aelf-samples-prod.yml new file mode 100644 index 0000000..b90d818 --- /dev/null +++ b/.github/workflows/publish-aelf-samples-prod.yml @@ -0,0 +1,60 @@ +on: + push: + tags: + - 'aelf-samples-v*' + +jobs: + publish: + runs-on: ubuntu-latest + env: + WORKING_DIRECTORY: aelf-samples + environment: prod + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 # Ensure the full history is fetched so we can check the commit history + + - name: Verify tag is on master branch + id: verify_tag + run: | + # Get the commit SHA of the tag + TAG_COMMIT=$(git rev-list -n 1 $GITHUB_REF) + # Check if the commit exists on the master branch + if git merge-base --is-ancestor $TAG_COMMIT origin/master; then + echo "Tag commit is on master branch." + echo "IS_ON_MASTER=true" >> $GITHUB_ENV + else + echo "Tag commit is not on master branch." + echo "IS_ON_MASTER=false" >> $GITHUB_ENV + fi + + - name: Stop if not on master + if: env.IS_ON_MASTER != 'true' + run: | + echo "This tag was not created from the master branch. Exiting." + exit 1 + + - name: Setup .NET + if: env.IS_ON_MASTER == 'true' + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '7.0.*' # Change this to the .NET version you're using + + - name: Read version from Version.props + if: env.IS_ON_MASTER == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + run: | + version=$(grep -oP '\K[^<]+' Version.props) + echo "VERSION=$version" >> $GITHUB_ENV + + - name: Pack + if: env.IS_ON_MASTER == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + run: dotnet pack --configuration Release --output nupkgs /p:Version=$VERSION + + - name: Publish NuGet packages + if: env.IS_ON_MASTER == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + run: | + dotnet nuget push "nupkgs/*.nupkg" --api-key ${{ secrets.AELF_SAMPLES_NUGET_API_KEY }} --source ${{ vars.AELF_SAMPLES_NUGET_SOURCE_URL }} diff --git a/.github/workflows/publish-aelf-samples-staging.yml b/.github/workflows/publish-aelf-samples-staging.yml new file mode 100644 index 0000000..4dc65e4 --- /dev/null +++ b/.github/workflows/publish-aelf-samples-staging.yml @@ -0,0 +1,34 @@ +on: + push: + branches: + - release/aelf-samples/* + +jobs: + publish: + runs-on: ubuntu-latest + env: + WORKING_DIRECTORY: aelf-samples + environment: staging + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '7.0.*' # Change this to the .NET version you're using + + - name: Read version from Version.props + working-directory: ${{ env.WORKING_DIRECTORY }} + run: | + version=$(grep -oP '\K[^<]+' Version.props) + echo "VERSION=$version-rc.${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV + + - name: Pack + working-directory: ${{ env.WORKING_DIRECTORY }} + run: dotnet pack --configuration Release --output nupkgs /p:Version=$VERSION + + - name: Publish NuGet packages + working-directory: ${{ env.WORKING_DIRECTORY }} + run: | + dotnet nuget push "nupkgs/*.nupkg" --api-key ${{ secrets.TEST_AELF_SAMPLES_NUGET_API_KEY }} --source ${{ vars.TEST_AELF_SAMPLES_NUGET_SOURCE_URL }} From a653202e96905f6e5303dc00661f009330d38f81 Mon Sep 17 00:00:00 2001 From: vasmohi Date: Wed, 30 Oct 2024 01:45:31 +0530 Subject: [PATCH 5/5] fix: Changed source of templates to aelf-sample and removed aelf-samples prod workflow --- .DS_Store | Bin 6148 -> 6148 bytes .../workflows/publish-aelf-samples-prod.yml | 60 ------------------ templates/.DS_Store | Bin 10244 -> 10244 bytes templates/AElf.Contract.Template.csproj | 13 +++- 4 files changed, 11 insertions(+), 62 deletions(-) delete mode 100644 .github/workflows/publish-aelf-samples-prod.yml diff --git a/.DS_Store b/.DS_Store index ebc09f7bce822038f8ee5c087ec24c39553e4624..dba1cc52845b96340c4f6ce5e7cf7fa30abb7678 100644 GIT binary patch delta 95 zcmZoMXffDunvHSi?CbhMk9HPql)Y3ca-n5yW<1aq|lIj#` delta 68 zcmZoMXffDunvHSCJrt}W+plc#>OVKIttZ>28I?o3TDPewY8iaqRRT#LGjr+ Yxq10rlT+E{8M`O9vNvsJ> $GITHUB_ENV - else - echo "Tag commit is not on master branch." - echo "IS_ON_MASTER=false" >> $GITHUB_ENV - fi - - - name: Stop if not on master - if: env.IS_ON_MASTER != 'true' - run: | - echo "This tag was not created from the master branch. Exiting." - exit 1 - - - name: Setup .NET - if: env.IS_ON_MASTER == 'true' - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '7.0.*' # Change this to the .NET version you're using - - - name: Read version from Version.props - if: env.IS_ON_MASTER == 'true' - working-directory: ${{ env.WORKING_DIRECTORY }} - run: | - version=$(grep -oP '\K[^<]+' Version.props) - echo "VERSION=$version" >> $GITHUB_ENV - - - name: Pack - if: env.IS_ON_MASTER == 'true' - working-directory: ${{ env.WORKING_DIRECTORY }} - run: dotnet pack --configuration Release --output nupkgs /p:Version=$VERSION - - - name: Publish NuGet packages - if: env.IS_ON_MASTER == 'true' - working-directory: ${{ env.WORKING_DIRECTORY }} - run: | - dotnet nuget push "nupkgs/*.nupkg" --api-key ${{ secrets.AELF_SAMPLES_NUGET_API_KEY }} --source ${{ vars.AELF_SAMPLES_NUGET_SOURCE_URL }} diff --git a/templates/.DS_Store b/templates/.DS_Store index baa7aaac2e3ff37c3a52d491d9056f6316541566..68a61b448c1797debaab1ec547cfbe8bbd3460ec 100644 GIT binary patch delta 475 zcmZn(XbG6$gHU^hRb@@5`^Nao3k!m5+sh;cLSoP18CP(!l1+R)roN5RCxtX4;% z+R(tzLPx>a(xkSQlS5Ql-#REhJ0~|UzkBioae2m`$#TLPlY7MYC#MR#0kJyBAmN=! z1v#0;B?bo97@3$^SlQS)I3_<3iPqudj29r$_#|O1u<@&fw@j85QKYu<5+Z6~}DQ;Nao3BMARn>i1AO}BO);Qqv)2&EE0+!{^n2Oq8u9= Vl9@NNEBs=a+$Yts`He(169C - + + + + + + + + + +