diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2e17dcc67..36656a4e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: .NET Core Test and Publish on: push: - branches: master + branches: [master] pull_request: env: @@ -14,20 +14,35 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Build + run: dotnet build ./neo-devpack-dotnet.sln - name: Check format run: | - dotnet format --verify-no-changes --verbosity diagnostic + dotnet format --no-restore --verify-no-changes --verbosity diagnostic - name: Add package coverlet.msbuild run: find tests -name *.csproj | xargs -I % dotnet add % package coverlet.msbuild - name: Test Neo.Compiler.CSharp.UnitTests - run: dotnet test tests/Neo.Compiler.CSharp.UnitTests /p:CollectCoverage=true /p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/ + run: | + dotnet test ./tests/Neo.Compiler.CSharp.UnitTests \ + --no-build \ + -p:CollectCoverage=true \ + -p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/ - name: Test Neo.SmartContract.Framework.UnitTests - run: dotnet test tests/Neo.SmartContract.Framework.UnitTests /p:CollectCoverage=true /p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/lcov /p:MergeWith=${GITHUB_WORKSPACE}/coverage/coverage.json /p:Exclude=\"[Neo.Compiler.CSharp.UnitTests]*\" /p:CoverletOutputFormat=lcov + run: | + dotnet test ./tests/Neo.SmartContract.Framework.UnitTests \ + --no-build \ + -p:CollectCoverage=true \ + -p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/lcov \ + -p:MergeWith=${GITHUB_WORKSPACE}/coverage/coverage.json \ + -p:Exclude=\"[Neo.Compiler.CSharp.UnitTests]*\" \ + -p:CoverletOutputFormat=lcov - name: Coveralls uses: coverallsapp/github-action@master with: @@ -40,21 +55,23 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Setup NuGet.exe for use with actions uses: NuGet/setup-nuget@v1 + with: + nuget-version: latest - name: Pack with dotnet run: git rev-list --count HEAD | xargs printf "CI%05d" | xargs dotnet pack src/Neo.SmartContract.Framework -c Debug -o out --include-source --version-suffix - name: Publish to Github Packages run: | - nuget source Add -Name "GitHub" -Source "https://nuget.pkg.github.com/neo-project/index.json" -UserName neo-project -Password ${GITHUB_TOKEN} + nuget source Add -Name "GitHub" -Source "https://nuget.pkg.github.com/neo-project/index.json" -UserName neo-project -Password "${{ secrets.GITHUB_TOKEN }}" nuget push out/*.nupkg -Source "GitHub" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PublishMyGet: if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/') @@ -62,19 +79,32 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Pack with dotnet - run: git rev-list --count HEAD |xargs printf "CI%05d" |xargs dotnet pack src/Neo.SmartContract.Framework -c Debug -o out --include-source --version-suffix + - name: Set Version + run: git rev-list --count HEAD | xargs printf 'CI%05d' | xargs -I{} echo 'VERSION_SUFFIX={}' >> $GITHUB_ENV + - name: Pack Package(s) + run: | + dotnet pack ./src/Neo.SmartContract.Framework \ + --configuration Debug \ + --output ./out \ + --version-suffix ${{ env.VERSION_SUFFIX }} - name: Publish to MyGet - run: dotnet nuget push out/*.nupkg -s https://www.myget.org/F/neo/api/v2/package -k ${MYGET_TOKEN} -ss https://www.myget.org/F/neo/symbols/api/v2/package -sk ${MYGET_TOKEN} - env: - MYGET_TOKEN: ${{ secrets.MYGET_TOKEN }} + working-directory: ./out + run: | + for filename in *.nupkg; do + dotnet nuget push "${filename}" \ + --source https://www.myget.org/F/neo/api/v3/index.json \ + --api-key "${{ secrets.MYGET_TOKEN }}" \ + --disable-buffering \ + --no-service-endpoint; + done; + shell: bash Release: if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/') @@ -82,7 +112,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Get version id: get_version run: | @@ -103,7 +135,7 @@ jobs: prerelease: ${{ contains(steps.get_version.outputs.version, '-') }} - name: Setup .NET Core if: steps.check_tag.outputs.statusCode == '404' - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Publish to NuGet diff --git a/global.json b/global.json new file mode 100644 index 000000000..943dd9e46 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "7.0.404", + "rollForward": "latestFeature", + "allowPrerelease": false + } +} diff --git a/src/Neo.Compiler.CSharp/MethodConvert.cs b/src/Neo.Compiler.CSharp/MethodConvert.cs index e189ea9f7..b9103ed2a 100644 --- a/src/Neo.Compiler.CSharp/MethodConvert.cs +++ b/src/Neo.Compiler.CSharp/MethodConvert.cs @@ -446,48 +446,186 @@ private void ConvertExtern() private void ConvertNoBody(AccessorDeclarationSyntax syntax) { - _inline = true; _callingConvention = CallingConvention.Cdecl; IPropertySymbol property = (IPropertySymbol)Symbol.AssociatedSymbol!; - INamedTypeSymbol type = property.ContainingType; - IFieldSymbol[] fields = type.GetAllMembers().OfType().ToArray(); + AttributeData? attribute = property.GetAttributes().FirstOrDefault(p => p.AttributeClass!.Name == nameof(StorageBackedAttribute)); using (InsertSequencePoint(syntax)) { + _inline = attribute is null; + ConvertFieldBackedProperty(property); + if (attribute is not null) + ConvertStorageBackedProperty(property, attribute); + } + } + + private void ConvertFieldBackedProperty(IPropertySymbol property) + { + IFieldSymbol[] fields = property.ContainingType.GetAllMembers().OfType().ToArray(); + if (Symbol.IsStatic) + { + IFieldSymbol backingField = Array.Find(fields, p => SymbolEqualityComparer.Default.Equals(p.AssociatedSymbol, property))!; + byte backingFieldIndex = context.AddStaticField(backingField); + switch (Symbol.MethodKind) + { + case MethodKind.PropertyGet: + AccessSlot(OpCode.LDSFLD, backingFieldIndex); + break; + case MethodKind.PropertySet: + if (!_inline) AccessSlot(OpCode.LDARG, 0); + AccessSlot(OpCode.STSFLD, backingFieldIndex); + break; + default: + throw new CompilationException(Symbol, DiagnosticId.SyntaxNotSupported, $"Unsupported accessor: {Symbol}"); + } + } + else + { + fields = fields.Where(p => !p.IsStatic).ToArray(); + int backingFieldIndex = Array.FindIndex(fields, p => SymbolEqualityComparer.Default.Equals(p.AssociatedSymbol, property)); + switch (Symbol.MethodKind) + { + case MethodKind.PropertyGet: + if (!_inline) AccessSlot(OpCode.LDARG, 0); + Push(backingFieldIndex); + AddInstruction(OpCode.PICKITEM); + break; + case MethodKind.PropertySet: + if (_inline) + { + Push(backingFieldIndex); + AddInstruction(OpCode.ROT); + } + else + { + AccessSlot(OpCode.LDARG, 0); + Push(backingFieldIndex); + AccessSlot(OpCode.LDARG, 1); + } + AddInstruction(OpCode.SETITEM); + break; + default: + throw new CompilationException(Symbol, DiagnosticId.SyntaxNotSupported, $"Unsupported accessor: {Symbol}"); + } + } + } + + private byte[] GetStorageBackedKey(IPropertySymbol property, AttributeData attribute) + { + byte[] key; + + if (attribute.ConstructorArguments.Length == 0) + { + key = Utility.StrictUTF8.GetBytes(property.Name); + } + else + { + if (attribute.ConstructorArguments[0].Value is byte b) + { + key = new byte[] { b }; + } + else if (attribute.ConstructorArguments[0].Value is string s) + { + key = Utility.StrictUTF8.GetBytes(s); + } + else + { + throw new CompilationException(Symbol, DiagnosticId.SyntaxNotSupported, $"Unknown StorageBacked constructor: {Symbol}"); + } + } + return key; + } + + private void ConvertStorageBackedProperty(IPropertySymbol property, AttributeData attribute) + { + IFieldSymbol[] fields = property.ContainingType.GetAllMembers().OfType().ToArray(); + byte[] key = GetStorageBackedKey(property, attribute); + if (Symbol.MethodKind == MethodKind.PropertyGet) + { + JumpTarget endTarget = new(); + if (Symbol.IsStatic) + { + // AddInstruction(OpCode.DUP); + AddInstruction(OpCode.ISNULL); + // Ensure that no object was sent + Jump(OpCode.JMPIFNOT_L, endTarget); + } + else + { + // Check class + Jump(OpCode.JMPIF_L, endTarget); + } + Push(key); + Call(ApplicationEngine.System_Storage_GetReadOnlyContext); + Call(ApplicationEngine.System_Storage_Get); + switch (property.Type.Name) + { + case "SByte": + case "Short": + case "Int32": + case "Int64": + case "Byte": + case "UInt16": + case "UInt32": + case "UInt64": + case "BigInteger": + ChangeType(VM.Types.StackItemType.Integer); + break; + case "String": + case "ByteString": + case "UInt160": + case "UInt256": + break; + default: + Call(NativeContract.StdLib.Hash, "deserialize", 1, true); + break; + } + AddInstruction(OpCode.DUP); if (Symbol.IsStatic) { IFieldSymbol backingField = Array.Find(fields, p => SymbolEqualityComparer.Default.Equals(p.AssociatedSymbol, property))!; byte backingFieldIndex = context.AddStaticField(backingField); - switch (Symbol.MethodKind) - { - case MethodKind.PropertyGet: - AccessSlot(OpCode.LDSFLD, backingFieldIndex); - break; - case MethodKind.PropertySet: - AccessSlot(OpCode.STSFLD, backingFieldIndex); - break; - default: - throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Unsupported accessor: {syntax}"); - } + AccessSlot(OpCode.STSFLD, backingFieldIndex); } else { fields = fields.Where(p => !p.IsStatic).ToArray(); int backingFieldIndex = Array.FindIndex(fields, p => SymbolEqualityComparer.Default.Equals(p.AssociatedSymbol, property)); - switch (Symbol.MethodKind) - { - case MethodKind.PropertyGet: - Push(backingFieldIndex); - AddInstruction(OpCode.PICKITEM); - break; - case MethodKind.PropertySet: - Push(backingFieldIndex); - AddInstruction(OpCode.ROT); - AddInstruction(OpCode.SETITEM); - break; - default: - throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Unsupported accessor: {syntax}"); - } + AccessSlot(OpCode.LDARG, 0); + Push(backingFieldIndex); + AddInstruction(OpCode.ROT); + AddInstruction(OpCode.SETITEM); + } + endTarget.Instruction = AddInstruction(OpCode.NOP); + } + else + { + if (Symbol.IsStatic) + AccessSlot(OpCode.LDARG, 0); + else + AccessSlot(OpCode.LDARG, 1); + switch (property.Type.Name) + { + case "SByte": + case "Short": + case "Int32": + case "Int64": + case "Byte": + case "UInt16": + case "UInt32": + case "UInt64": + case "BigInteger": + case "String": + case "ByteString": + case "UInt160": + case "UInt256": + break; + default: + Call(NativeContract.StdLib.Hash, "serialize", 1, true); + break; } + Push(key); + Call(ApplicationEngine.System_Storage_GetContext); + Call(ApplicationEngine.System_Storage_Put); } } @@ -2849,7 +2987,7 @@ private void ConvertElementAccessPostIncrementOrDecrementExpression(SemanticMode AddInstruction(OpCode.DUP); AddInstruction(OpCode.REVERSE4); AddInstruction(OpCode.REVERSE3); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, property.Type); Call(model, property.SetMethod!, CallingConvention.StdCall); } else @@ -2862,7 +3000,7 @@ private void ConvertElementAccessPostIncrementOrDecrementExpression(SemanticMode AddInstruction(OpCode.DUP); AddInstruction(OpCode.REVERSE4); AddInstruction(OpCode.REVERSE3); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, model.GetTypeInfo(operand).Type); AddInstruction(OpCode.SETITEM); } } @@ -2896,7 +3034,7 @@ private void ConvertFieldIdentifierNamePostIncrementOrDecrementExpression(Syntax byte index = context.AddStaticField(symbol); AccessSlot(OpCode.LDSFLD, index); AddInstruction(OpCode.DUP); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AccessSlot(OpCode.STSFLD, index); } else @@ -2907,7 +3045,7 @@ private void ConvertFieldIdentifierNamePostIncrementOrDecrementExpression(Syntax Push(index); AddInstruction(OpCode.PICKITEM); AddInstruction(OpCode.TUCK); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); Push(index); AddInstruction(OpCode.SWAP); AddInstruction(OpCode.SETITEM); @@ -2919,7 +3057,7 @@ private void ConvertLocalIdentifierNamePostIncrementOrDecrementExpression(Syntax byte index = _localVariables[symbol]; AccessSlot(OpCode.LDLOC, index); AddInstruction(OpCode.DUP); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AccessSlot(OpCode.STLOC, index); } @@ -2928,7 +3066,7 @@ private void ConvertParameterIdentifierNamePostIncrementOrDecrementExpression(Sy byte index = _parameters[symbol]; AccessSlot(OpCode.LDARG, index); AddInstruction(OpCode.DUP); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AccessSlot(OpCode.STARG, index); } @@ -2938,7 +3076,7 @@ private void ConvertPropertyIdentifierNamePostIncrementOrDecrementExpression(Sem { Call(model, symbol.GetMethod!); AddInstruction(OpCode.DUP); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); Call(model, symbol.SetMethod!); } else @@ -2947,7 +3085,7 @@ private void ConvertPropertyIdentifierNamePostIncrementOrDecrementExpression(Sem AddInstruction(OpCode.DUP); Call(model, symbol.GetMethod!); AddInstruction(OpCode.TUCK); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); Call(model, symbol.SetMethod!, CallingConvention.StdCall); } } @@ -2975,7 +3113,7 @@ private void ConvertFieldMemberAccessPostIncrementOrDecrementExpression(Semantic byte index = context.AddStaticField(symbol); AccessSlot(OpCode.LDSFLD, index); AddInstruction(OpCode.DUP); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AccessSlot(OpCode.STSFLD, index); } else @@ -2986,7 +3124,7 @@ private void ConvertFieldMemberAccessPostIncrementOrDecrementExpression(Semantic Push(index); AddInstruction(OpCode.PICKITEM); AddInstruction(OpCode.TUCK); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); Push(index); AddInstruction(OpCode.SWAP); AddInstruction(OpCode.SETITEM); @@ -2999,7 +3137,7 @@ private void ConvertPropertyMemberAccessPostIncrementOrDecrementExpression(Seman { Call(model, symbol.GetMethod!); AddInstruction(OpCode.DUP); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); Call(model, symbol.SetMethod!); } else @@ -3008,7 +3146,7 @@ private void ConvertPropertyMemberAccessPostIncrementOrDecrementExpression(Seman AddInstruction(OpCode.DUP); Call(model, symbol.GetMethod!); AddInstruction(OpCode.TUCK); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); Call(model, symbol.SetMethod!, CallingConvention.StdCall); } } @@ -3076,7 +3214,7 @@ private void ConvertElementAccessPreIncrementOrDecrementExpression(SemanticModel AddInstruction(OpCode.OVER); AddInstruction(OpCode.OVER); Call(model, property.GetMethod!, CallingConvention.StdCall); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, property.Type); AddInstruction(OpCode.DUP); AddInstruction(OpCode.REVERSE4); Call(model, property.SetMethod!, CallingConvention.Cdecl); @@ -3088,7 +3226,7 @@ private void ConvertElementAccessPreIncrementOrDecrementExpression(SemanticModel AddInstruction(OpCode.OVER); AddInstruction(OpCode.OVER); AddInstruction(OpCode.PICKITEM); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, model.GetTypeInfo(operand).Type); AddInstruction(OpCode.DUP); AddInstruction(OpCode.REVERSE4); AddInstruction(OpCode.REVERSE3); @@ -3124,7 +3262,7 @@ private void ConvertFieldIdentifierNamePreIncrementOrDecrementExpression(SyntaxT { byte index = context.AddStaticField(symbol); AccessSlot(OpCode.LDSFLD, index); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.DUP); AccessSlot(OpCode.STSFLD, index); } @@ -3135,7 +3273,7 @@ private void ConvertFieldIdentifierNamePreIncrementOrDecrementExpression(SyntaxT AddInstruction(OpCode.DUP); Push(index); AddInstruction(OpCode.PICKITEM); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.TUCK); Push(index); AddInstruction(OpCode.SWAP); @@ -3147,7 +3285,7 @@ private void ConvertLocalIdentifierNamePreIncrementOrDecrementExpression(SyntaxT { byte index = _localVariables[symbol]; AccessSlot(OpCode.LDLOC, index); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.DUP); AccessSlot(OpCode.STLOC, index); } @@ -3156,7 +3294,7 @@ private void ConvertParameterIdentifierNamePreIncrementOrDecrementExpression(Syn { byte index = _parameters[symbol]; AccessSlot(OpCode.LDARG, index); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.DUP); AccessSlot(OpCode.STARG, index); } @@ -3166,7 +3304,7 @@ private void ConvertPropertyIdentifierNamePreIncrementOrDecrementExpression(Sema if (symbol.IsStatic) { Call(model, symbol.GetMethod!); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.DUP); Call(model, symbol.SetMethod!); } @@ -3175,7 +3313,7 @@ private void ConvertPropertyIdentifierNamePreIncrementOrDecrementExpression(Sema AddInstruction(OpCode.LDARG0); AddInstruction(OpCode.DUP); Call(model, symbol.GetMethod!); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.TUCK); Call(model, symbol.SetMethod!, CallingConvention.StdCall); } @@ -3203,7 +3341,7 @@ private void ConvertFieldMemberAccessPreIncrementOrDecrementExpression(SemanticM { byte index = context.AddStaticField(symbol); AccessSlot(OpCode.LDSFLD, index); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.DUP); AccessSlot(OpCode.STSFLD, index); } @@ -3214,7 +3352,7 @@ private void ConvertFieldMemberAccessPreIncrementOrDecrementExpression(SemanticM AddInstruction(OpCode.DUP); Push(index); AddInstruction(OpCode.PICKITEM); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.TUCK); Push(index); AddInstruction(OpCode.SWAP); @@ -3227,7 +3365,7 @@ private void ConvertPropertyMemberAccessPreIncrementOrDecrementExpression(Semant if (symbol.IsStatic) { Call(model, symbol.GetMethod!); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.DUP); Call(model, symbol.SetMethod!); } @@ -3236,13 +3374,13 @@ private void ConvertPropertyMemberAccessPreIncrementOrDecrementExpression(Semant ConvertExpression(model, operand.Expression); AddInstruction(OpCode.DUP); Call(model, symbol.GetMethod!); - EmitIncrementOrDecrement(operatorToken); + EmitIncrementOrDecrement(operatorToken, symbol.Type); AddInstruction(OpCode.TUCK); Call(model, symbol.SetMethod!, CallingConvention.StdCall); } } - private void EmitIncrementOrDecrement(SyntaxToken operatorToken) + private void EmitIncrementOrDecrement(SyntaxToken operatorToken, ITypeSymbol? typeSymbol) { AddInstruction(operatorToken.ValueText switch { @@ -3250,6 +3388,7 @@ private void EmitIncrementOrDecrement(SyntaxToken operatorToken) "--" => OpCode.DEC, _ => throw new CompilationException(operatorToken, DiagnosticId.SyntaxNotSupported, $"Unsupported operator: {operatorToken}") }); + if (typeSymbol != null) EnsureIntegerInRange(typeSymbol); } private void ConvertSwitchExpression(SemanticModel model, SwitchExpressionSyntax expression) @@ -4037,6 +4176,14 @@ private bool TryProcessSystemMethods(SemanticModel model, IMethodSymbol symbol, Push(1); AddInstruction(OpCode.NUMEQUAL); return true; + case "System.Numerics.BigInteger.IsEven.get": + if (instanceExpression is not null) + ConvertExpression(model, instanceExpression); + Push(1); + AddInstruction(OpCode.AND); + Push(0); + AddInstruction(OpCode.NUMEQUAL); + return true; case "System.Numerics.BigInteger.Sign.get": if (instanceExpression is not null) ConvertExpression(model, instanceExpression); diff --git a/src/Neo.SmartContract.Framework/Attributes/StorageBackedAttribute.cs b/src/Neo.SmartContract.Framework/Attributes/StorageBackedAttribute.cs new file mode 100644 index 000000000..c0f9c3ffe --- /dev/null +++ b/src/Neo.SmartContract.Framework/Attributes/StorageBackedAttribute.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2023 The Neo Project. +// +// The Neo.SmartContract.Framework is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +#pragma warning disable IDE0060 + +namespace Neo.SmartContract.Framework.Attributes +{ + [AttributeUsage(AttributeTargets.Property, Inherited = false)] + public class StorageBackedAttribute : Attribute + { + /// + /// The property will be backed in the storage using the specific key using the property name as storage key + /// + public StorageBackedAttribute() { } + + /// + /// The property will be backed in the storage using the specific key + /// + /// Storage key + public StorageBackedAttribute(byte storageKey) { } + + /// + /// The property will be backed in the storage using the specific key + /// + /// Storage key + public StorageBackedAttribute(string storageKey) { } + } +} diff --git a/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj b/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj index 66991944d..58d6f1691 100644 --- a/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj +++ b/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj @@ -9,6 +9,8 @@ MIT git https://github.com/neo-project/neo-devpack-dotnet.git + true + snupkg Neo.SmartContract.Framework diff --git a/src/Neo.SmartContract.Framework/Nep11Token.cs b/src/Neo.SmartContract.Framework/Nep11Token.cs index c9fdb5c1f..e024a06a4 100644 --- a/src/Neo.SmartContract.Framework/Nep11Token.cs +++ b/src/Neo.SmartContract.Framework/Nep11Token.cs @@ -31,8 +31,7 @@ public abstract class Nep11Token : TokenContract protected const byte Prefix_Token = 0x03; protected const byte Prefix_AccountToken = 0x04; - [Safe] - public sealed override byte Decimals() => 0; + public sealed override byte Decimals => 0; [Safe] public static UInt160 OwnerOf(ByteString tokenId) @@ -107,7 +106,7 @@ protected static void Mint(ByteString tokenId, TokenState token) StorageMap tokenMap = new(Storage.CurrentContext, Prefix_Token); tokenMap[tokenId] = StdLib.Serialize(token); UpdateBalance(token.Owner, tokenId, +1); - UpdateTotalSupply(+1); + TotalSupply++; PostTransfer(null, token.Owner, tokenId, null); } @@ -117,7 +116,7 @@ protected static void Burn(ByteString tokenId) TokenState token = (TokenState)StdLib.Deserialize(tokenMap[tokenId]); tokenMap.Delete(tokenId); UpdateBalance(token.Owner, tokenId, -1); - UpdateTotalSupply(-1); + TotalSupply--; PostTransfer(token.Owner, null, tokenId, null); } diff --git a/src/Neo.SmartContract.Framework/Nep17Token.cs b/src/Neo.SmartContract.Framework/Nep17Token.cs index 3658a4335..079aeb8e5 100644 --- a/src/Neo.SmartContract.Framework/Nep17Token.cs +++ b/src/Neo.SmartContract.Framework/Nep17Token.cs @@ -50,7 +50,7 @@ protected static void Mint(UInt160 account, BigInteger amount) if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; UpdateBalance(account, +amount); - UpdateTotalSupply(+amount); + TotalSupply += amount; PostTransfer(null, account, amount, null); } @@ -60,7 +60,7 @@ protected static void Burn(UInt160 account, BigInteger amount) if (amount.IsZero) return; if (!UpdateBalance(account, -amount)) throw new InvalidOperationException(); - UpdateTotalSupply(-amount); + TotalSupply -= amount; PostTransfer(account, null, amount, null); } diff --git a/src/Neo.SmartContract.Framework/TokenContract.cs b/src/Neo.SmartContract.Framework/TokenContract.cs index eb2509ad2..b2f1b1e4a 100644 --- a/src/Neo.SmartContract.Framework/TokenContract.cs +++ b/src/Neo.SmartContract.Framework/TokenContract.cs @@ -20,14 +20,12 @@ public abstract class TokenContract : SmartContract protected const byte Prefix_TotalSupply = 0x00; protected const byte Prefix_Balance = 0x01; - [Safe] - public abstract string Symbol(); + public abstract string Symbol { [Safe] get; } - [Safe] - public abstract byte Decimals(); + public abstract byte Decimals { [Safe] get; } - [Safe] - public static BigInteger TotalSupply() => (BigInteger)Storage.Get(Storage.CurrentContext, new byte[] { Prefix_TotalSupply }); + [StorageBacked(Prefix_TotalSupply)] + public static BigInteger TotalSupply { [Safe] get; protected set; } [Safe] public static BigInteger BalanceOf(UInt160 owner) @@ -38,15 +36,6 @@ public static BigInteger BalanceOf(UInt160 owner) return (BigInteger)balanceMap[owner]; } - protected static void UpdateTotalSupply(BigInteger increment) - { - StorageContext context = Storage.CurrentContext; - byte[] key = new byte[] { Prefix_TotalSupply }; - BigInteger totalSupply = (BigInteger)Storage.Get(context, key); - totalSupply += increment; - Storage.Put(context, key, totalSupply); - } - protected static bool UpdateBalance(UInt160 owner, BigInteger increment) { StorageMap balanceMap = new(Storage.CurrentContext, Prefix_Balance); diff --git a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_BigInteger.cs b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_BigInteger.cs index c37fe87e5..9a38add35 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_BigInteger.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_BigInteger.cs @@ -118,5 +118,10 @@ public static ulong testulong(BigInteger input) throw new System.Exception(); } } + + public static bool testIsEven(BigInteger input) + { + return input.IsEven; + } } } diff --git a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Inc_Dec.cs b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Inc_Dec.cs new file mode 100644 index 000000000..77f39387d --- /dev/null +++ b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Inc_Dec.cs @@ -0,0 +1,282 @@ +using System; +using System.ComponentModel; + +namespace Neo.Compiler.CSharp.UnitTests.TestClasses +{ + public class Contract2_Inc_Dec : SmartContract.Framework.SmartContract + { + private static uint _property; + private static int _intProperty; + + public static uint UnitTest_Property_Inc_Checked() + { + _property = uint.MaxValue; + checked + { + ++_property; + _property++; + } + return _property; + } + + public static uint UnitTest_Property_Inc_UnChecked() + { + _property = uint.MaxValue; + unchecked + { + ++_property; + _property++; + } + return _property; + } + + public static uint UnitTest_Property_Dec_Checked() + { + _property = uint.MinValue; + checked + { + --_property; + _property--; + } + return _property; + } + + public static uint UnitTest_Property_Dec_UnChecked() + { + _property = uint.MinValue; + unchecked + { + --_property; + _property--; + } + return _property; + } + + public static uint UnitTest_Local_Dec_Checked() + { + uint local = uint.MinValue; + checked + { + --local; + local--; + } + return local; + } + + public static uint UnitTest_Local_Dec_UnChecked() + { + uint local = uint.MinValue; + unchecked + { + --local; + local--; + } + return local; + } + + public static uint UnitTest_Local_Inc_Checked() + { + uint local = uint.MaxValue; + checked + { + ++local; + local++; + } + return local; + } + + public static uint UnitTest_Local_Inc_UnChecked() + { + uint local = uint.MaxValue; + unchecked + { + ++local; + local++; + } + return local; + } + + public static uint UnitTest_Param_Dec_Checked(uint param) + { + param = uint.MinValue; + checked + { + --param; + param--; + } + return param; + } + + public static uint UnitTest_Param_Dec_UnChecked(uint param) + { + param = uint.MinValue; + unchecked + { + --param; + param--; + } + return param; + } + + public static uint UnitTest_Param_Inc_Checked(uint param) + { + param = uint.MaxValue; + checked + { + ++param; + param++; + } + return param; + } + + public static uint UnitTest_Param_Inc_UnChecked(uint param) + { + param = uint.MaxValue; + unchecked + { + ++param; + param++; + } + return param; + } + + public static int UnitTest_Property_Inc_Checked_Int() + { + _intProperty = int.MaxValue; + checked + { + ++_intProperty; + _intProperty++; + } + return _intProperty; + } + + public static int UnitTest_Property_Inc_UnChecked_Int() + { + _intProperty = int.MaxValue; + unchecked + { + ++_intProperty; + _intProperty++; + } + return _intProperty; + } + + public static int UnitTest_Property_Dec_Checked_Int() + { + _intProperty = int.MinValue; + checked + { + --_intProperty; + _intProperty--; + } + return _intProperty; + } + + public static int UnitTest_Property_Dec_UnChecked_Int() + { + _intProperty = int.MinValue; + unchecked + { + --_intProperty; + _intProperty--; + } + return _intProperty; + } + + // Local Variable Tests + public static int UnitTest_Local_Inc_Checked_Int() + { + int local = int.MaxValue; + checked + { + ++local; + local++; + } + return local; + } + + public static int UnitTest_Local_Inc_UnChecked_Int() + { + int local = int.MaxValue; + unchecked + { + ++local; + local++; + } + return local; + } + + public static int UnitTest_Local_Dec_Checked_Int() + { + int local = int.MinValue; + checked + { + --local; + local--; + } + return local; + } + + public static int UnitTest_Local_Dec_UnChecked_Int() + { + int local = int.MinValue; + unchecked + { + --local; + local--; + } + return local; + } + + // Parameter Tests + public static int UnitTest_Param_Inc_Checked_Int(int param) + { + param = int.MaxValue; + checked + { + ++param; + param++; + } + return param; + } + + public static int UnitTest_Param_Inc_UnChecked_Int(int param) + { + param = int.MaxValue; + unchecked + { + ++param; + param++; + } + return param; + } + + public static int UnitTest_Param_Dec_Checked_Int(int param) + { + param = int.MinValue; + checked + { + --param; + param--; + } + return param; + } + + public static int UnitTest_Param_Dec_UnChecked_Int(int param) + { + param = int.MinValue; + unchecked + { + --param; + param--; + } + return param; + } + + public static void UnitTest_Not_DeadLoop() + { + for (uint i = 5; i < 7; i--) ; + } + } +} diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs index 0563d3c5a..eed94055b 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs @@ -1,3 +1,5 @@ +using System; +using System.Numerics; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Compiler.CSharp.UnitTests.Utils; using Neo.VM; @@ -271,5 +273,36 @@ public void Test_ulong() Assert.AreEqual(VMState.FAULT, testengine.State); Assert.IsNotNull(testengine.FaultException); } + [TestMethod] + public void Test_IsEven() + { + var testengine = new TestEngine(); + testengine.AddEntryScript("./TestClasses/Contract_BigInteger.cs"); + // Test 0 + var result = testengine.ExecuteTestCaseStandard("testIsEven", 0); + var value = result.Pop().GetBoolean(); + Assert.AreEqual(new BigInteger(0).IsEven, value); + testengine.Reset(); + // Test 1 + result = testengine.ExecuteTestCaseStandard("testIsEven", 1); + value = result.Pop().GetBoolean(); + Assert.AreEqual(new BigInteger(1).IsEven, value); + testengine.Reset(); + // Test 2 + result = testengine.ExecuteTestCaseStandard("testIsEven", 2); + value = result.Pop().GetBoolean(); + Assert.AreEqual(new BigInteger(2).IsEven, value); + testengine.Reset(); + // Test -1 + result = testengine.ExecuteTestCaseStandard("testIsEven", -1); + value = result.Pop().GetBoolean(); + Assert.AreEqual(new BigInteger(-1).IsEven, value); + testengine.Reset(); + // Test -2 + result = testengine.ExecuteTestCaseStandard("testIsEven", -2); + value = result.Pop().GetBoolean(); + Assert.AreEqual(new BigInteger(-2).IsEven, value); + testengine.Reset(); + } } } diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Inc_Dec.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Inc_Dec.cs new file mode 100644 index 000000000..7003cafdd --- /dev/null +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Inc_Dec.cs @@ -0,0 +1,233 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.VM; + +namespace Neo.Compiler.CSharp.UnitTests +{ + [TestClass] + public class UnitTest_Inc_Dec + { + private TestEngine _engine; + + [TestInitialize] + public void Init() + { + _engine = new TestEngine(); + _engine.AddEntryScript("./TestClasses/Contract_Inc_Dec.cs"); + } + + [TestMethod] + public void Test_Property_Inc_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Inc_Checked"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Property_Inc_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Inc_UnChecked"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(uint.MaxValue + 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Property_Dec_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Dec_Checked"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Property_Dec_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Dec_UnChecked"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(uint.MinValue - 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Local_Inc_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Inc_Checked"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Local_Inc_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Inc_UnChecked"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(uint.MaxValue + 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Local_Dec_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Dec_Checked"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Local_Dec_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Dec_UnChecked"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(uint.MinValue - 2), result.Pop().GetInteger()); + } + [TestMethod] + public void Test_Param_Inc_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Inc_Checked", 0); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Param_Inc_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Inc_UnChecked", 0); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(uint.MaxValue + 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Param_Dec_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Dec_Checked", 0); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Param_Dec_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Dec_UnChecked", 0); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(uint.MinValue - 2), result.Pop().GetInteger()); + } + + // Test Methods for int type + [TestMethod] + public void Test_IntProperty_Inc_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Inc_Checked_Int"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_IntProperty_Inc_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Inc_UnChecked_Int"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(int.MaxValue + 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_IntProperty_Dec_Checked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Dec_Checked_Int"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_IntProperty_Dec_UnChecked() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Property_Dec_UnChecked_Int"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(int.MinValue - 2), result.Pop().GetInteger()); + } + + // Local Variable Tests for int + [TestMethod] + public void Test_Local_Inc_Checked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Inc_Checked_Int"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Local_Inc_UnChecked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Inc_UnChecked_Int"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(int.MaxValue + 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Local_Dec_Checked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Dec_Checked_Int"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Local_Dec_UnChecked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Local_Dec_UnChecked_Int"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(int.MinValue - 2), result.Pop().GetInteger()); + } + + // Parameter Tests for int + [TestMethod] + public void Test_Param_Inc_Checked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Inc_Checked_Int", 0); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Param_Inc_UnChecked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Inc_UnChecked_Int", 0); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(int.MaxValue + 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Param_Dec_Checked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Dec_Checked_Int", 0); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + + [TestMethod] + public void Test_Param_Dec_UnChecked_Int() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Param_Dec_UnChecked_Int", 0); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(unchecked(int.MinValue - 2), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Not_DeadLoop() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("unitTest_Not_DeadLoop"); + Assert.AreEqual(VMState.HALT, _engine.State); + } + } +} diff --git a/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestEngine.cs b/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestEngine.cs index 0a0925278..47d4f5aee 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestEngine.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestEngine.cs @@ -175,5 +175,11 @@ public EvaluationStack ExecuteTestCaseStandard(int offset, ushort rvcount, NefFi } return this.ResultStack; } + + protected override void OnSysCall(InteropDescriptor descriptor) + { + Console.WriteLine("syscall:[" + descriptor.Name + "]"); + base.OnSysCall(descriptor); + } } } diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/BackedStorageTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/BackedStorageTest.cs new file mode 100644 index 000000000..a73ce1e06 --- /dev/null +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/BackedStorageTest.cs @@ -0,0 +1,143 @@ +using System; +using System.Linq; +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.VM; + +namespace Neo.SmartContract.Framework.UnitTests.Services +{ + [TestClass] + public class BackedStorageTest + { + private TestEngine _engine; + + [TestInitialize] + public void Init() + { + var system = TestBlockchain.TheNeoSystem; + var snapshot = system.GetSnapshot().CreateSnapshot(); + + _engine = new TestEngine(snapshot: snapshot); + Assert.IsTrue(_engine.AddEntryScript("./TestClasses/Contract_StorageBacked.cs").Success); + snapshot.ContractAdd(new ContractState() + { + Id = 0, + Hash = _engine.EntryScriptHash, + Nef = _engine.Nef, + Manifest = new Manifest.ContractManifest() + }); + } + + [TestMethod] + public void Test() + { + Assert.AreEqual(0, _engine.Snapshot.GetChangeSet().Count(u => u.Key.Id == 0)); + + Test_Kind("WithoutConstructor"); + Test_Kind("WithKey"); + Test_Kind("WithString"); + + Assert.AreEqual(3, _engine.Snapshot.GetChangeSet().Count(u => u.Key.Id == 0)); + } + + [TestMethod] + public void Test_Private_Getter_Public_Setter() + { + // Read initial value + Console.WriteLine("GET"); + _engine.Reset(); + + // Test private getter + + var result = _engine.ExecuteTestCaseStandard("getPrivateGetterPublicSetter"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.IsTrue(result.Pop().IsNull); + + + // Test public setter + _engine.Reset(); + _engine.ExecuteTestCaseStandard("setPrivateGetterPublicSetter", 123); + Assert.AreEqual(VMState.HALT, _engine.State); + + // check public setter + + Console.WriteLine("GET"); + _engine.Reset(); + + result = _engine.ExecuteTestCaseStandard("getPrivateGetterPublicSetter"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(new BigInteger(123), result.Pop().GetInteger()); + } + + [TestMethod] + public void Test_Non_Static_Private_Getter_Public_Setter() + { + // Read initial value + Console.WriteLine("GET"); + _engine.Reset(); + + // Test private getter + + var result = _engine.ExecuteTestCaseStandard("getNonStaticPrivateGetterPublicSetter"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.IsTrue(result.Pop().IsNull); + + + // Test public setter + _engine.Reset(); + _engine.ExecuteTestCaseStandard("setNonStaticPrivateGetterPublicSetter", 123); + Assert.AreEqual(VMState.HALT, _engine.State); + + // check public setter + + Console.WriteLine("GET"); + _engine.Reset(); + + result = _engine.ExecuteTestCaseStandard("getNonStaticPrivateGetterPublicSetter"); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(new BigInteger(123), result.Pop().GetInteger()); + } + + public void Test_Kind(string kind) + { + // Read initial value + + Console.WriteLine("GET"); + _engine.Reset(); + + var result = _engine.ExecuteTestCaseStandard("get" + kind); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.IsTrue(result.Pop().IsNull); + + // Test public getter + + _engine.Reset(); + result = _engine.ExecuteTestCaseStandard(kind[0].ToString().ToLowerInvariant() + kind[1..]); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.IsTrue(result.Pop().IsNull); + + // Put + + Console.WriteLine("PUT"); + _engine.Reset(); + + _engine.ExecuteTestCaseStandard("put" + kind, 123); + Assert.AreEqual(VMState.HALT, _engine.State); + + Console.WriteLine("GET"); + _engine.Reset(); + + result = _engine.ExecuteTestCaseStandard("get" + kind); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(new BigInteger(123), result.Pop().GetInteger()); + + // Test public getter + + _engine.Reset(); + result = _engine.ExecuteTestCaseStandard(kind[0].ToString().ToLowerInvariant() + kind[1..]); + Assert.AreEqual(VMState.HALT, _engine.State); + Assert.AreEqual(new BigInteger(123), result.Pop().GetInteger()); + } + } +} diff --git a/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_StorageBacked.cs b/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_StorageBacked.cs new file mode 100644 index 000000000..c57411885 --- /dev/null +++ b/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_StorageBacked.cs @@ -0,0 +1,63 @@ +using System.Numerics; +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Services; + +namespace Neo.SmartContract.Framework.UnitTests.TestClasses +{ + public class Contract_StorageBacked : SmartContract + { + // Test non-static + + [StorageBacked] + public BigInteger WithoutConstructor { [Safe] get; protected set; } + + public void putWithoutConstructor(BigInteger value) + { + WithoutConstructor = value; + } + + [Safe] + public BigInteger getWithoutConstructor() => WithoutConstructor; + + // --- + + [StorageBacked(0x01)] + public static BigInteger WithKey { [Safe] get; protected set; } + + public static void putWithKey(BigInteger value) + { + WithKey = value; + } + + [Safe] + public static BigInteger getWithKey() => WithKey; + + // --- + + [StorageBacked("testMe")] + public static BigInteger WithString { [Safe] get; protected set; } + + public static void putWithString(BigInteger value) + { + WithString = value; + } + + [Safe] + public static BigInteger getWithString() => WithString; + + + [StorageBacked] + public static BigInteger PrivateGetterPublicSetter { [Safe] private get; set; } + + [Safe] + public static BigInteger getPrivateGetterPublicSetter() => PrivateGetterPublicSetter; + + + [StorageBacked] + public BigInteger NonStaticPrivateGetterPublicSetter { [Safe] private get; set; } + + [Safe] + public BigInteger getNonStaticPrivateGetterPublicSetter() => NonStaticPrivateGetterPublicSetter; + } +}