neo-devpack-dotnet icon indicating copy to clipboard operation
neo-devpack-dotnet copied to clipboard

NCCS incorrectly treating some methods as extern

Open devhawk opened this issue 3 years ago • 7 comments

If I try and compile a contract with ExecutionEngine.Assert(false, "message"); statement, C# compilation succeeds but nccs compilation fails

error NC1002: Unknown method: Neo.SmartContract.Framework.ExecutionEngine.Assert(bool, string)
Compilation failed.

cc @erikzhang

devhawk avatar Jul 01 '22 17:07 devhawk

Here is used, and it works... https://github.com/neo-project/neo-devpack-dotnet/pull/758/files#diff-7bcb038b9f088192d7cf45b98a601920e3ec55272c9e5460c028e46cb8d02acbR21 (https://github.com/neo-project/neo-devpack-dotnet/pull/758)

shargon avatar Jul 01 '22 18:07 shargon

@shargon Can you test compiling the contracts in this zip file using nccs 3.3.0? There are two single-file contracts in this zip file.

> nccs AssertContract.cs
error NC1002: Unknown method: Neo.SmartContract.Framework.ExecutionEngine.Assert(bool, string)
Compilation failed.

> nccs SomeToken.cs
error NC1002: Unknown method: Neo.SmartContract.Framework.TokenContract.TotalSupply()
Compilation failed.

AssertContract is a simple contract with a single method demonstrating the failure to compile a contract that calls the ExecutionEngine.Assert(bool, string) overload

public class AssertContract : SmartContract
{
    public static void TestAssertWithMessage()
    {
        ExecutionEngine.Assert(1 == 2, "Bad Math");
    }
}

SomeToken is the simplest TokenContract subclass you can implement, but it won't compile either - though the error message is different (unknown method TokenContract.TotalSupply) than the error message for AssertContract.

public class SomeToken : TokenContract
{
    public override byte Decimals() => 10;
    public override string Symbol() => "SOME";
}

TestContracts.zip

devhawk avatar Jul 02 '22 14:07 devhawk

For AssertContract, nccs is throwing in ConvertExtern even though the ExecutionEngine.Assert(bool, string) overload is not an extern method

From https://github.com/neo-project/neo-devpack-dotnet/blob/master/src/Neo.Compiler.CSharp/MethodConvert.cs#L123

public void Convert(SemanticModel model)
{

// when converting ExecutionEngine.Assert(bool, string), 
// ContainingType.DeclaringSyntaxReferences.IsEmpty is true

    if (Symbol.IsExtern || Symbol.ContainingType.DeclaringSyntaxReferences.IsEmpty)
    {
        if (Symbol.Name == "_initialize")
        {
            ProcessStaticFields(model);
            if (context.StaticFieldCount > 0)
            {
                _instructions.Insert(0, new Instruction
                {
                    OpCode = OpCode.INITSSLOT,
                    Operand = new[] { (byte)context.StaticFieldCount }
                });
            }
        }
        else
        {

// Symbol.Name is "Assert" so we end up here. 
// But the Assert overload we're calling isn't extern so ConvertExtern fails

            ConvertExtern();
        }
    }

// remainder of Convert method omitted for clarity

devhawk avatar Jul 02 '22 14:07 devhawk

Note, SomeToken contract ends up in ConvertExtern when trying to compile TotalSupply as well. I will update the issue title to reflect this

devhawk avatar Jul 02 '22 14:07 devhawk

FYI, it appears both of these contracts also fail to compile under nccs v3.1.0

devhawk avatar Jul 02 '22 23:07 devhawk

I think I know what's going on here. In nccs, depending on the command line parameters the execution path goes thru either CompilationContext.CompileProject or CompilationContext.CompileSources.

The CompileSources path simply adds Neo.SmartContract.Framework.dll as a normal C# assembly reference. The CompileProject path parses the project.assets.json file to find the referenced packages. If the package includes source code, nccs creates a CSharpCompilation from the package source which is then added to the contract project.

Since the SmartContract Framework source code is not available when nccs goes down the CompileSources path, then Symbol.ContainingType.DeclaringSyntaxReferences.IsEmpty value is true for any method that is in the package reference (aka in SmartContract Framework). For methods like ExecutionEngine.Assert(bool) which are extern and implemented via attributes, it's no problem. For methods like ExecutionEngine.Assert(bool, string), the compilation fails because the source is not available.

To solve this, I think we need to:

  • Include the SmartContract Framework source in NCCS so that it can be used during compilation
  • nccs needs a mechanism to receive references, similar to C# CSC's --references option

devhawk avatar Jul 03 '22 00:07 devhawk

FYI, I'm going to tackle this bug after I do #760

devhawk avatar Jul 08 '22 17:07 devhawk