diff --git a/src/Neo.Compiler.CSharp/CompilationContext.cs b/src/Neo.Compiler.CSharp/CompilationContext.cs index 17d9ebcb2..0d166c8bf 100644 --- a/src/Neo.Compiler.CSharp/CompilationContext.cs +++ b/src/Neo.Compiler.CSharp/CompilationContext.cs @@ -325,9 +325,6 @@ private void ProcessClass(SemanticModel model, INamedTypeSymbol symbol) { return; } - // Process the target contract - // add this compilation context - _engine.Contexts.Add(symbol, this); foreach (var attribute in symbol.GetAttributesWithInherited()) { diff --git a/src/Neo.Compiler.CSharp/CompilationEngine.cs b/src/Neo.Compiler.CSharp/CompilationEngine.cs index e9719d960..fa9e42227 100644 --- a/src/Neo.Compiler.CSharp/CompilationEngine.cs +++ b/src/Neo.Compiler.CSharp/CompilationEngine.cs @@ -118,14 +118,14 @@ private List CompileProjectContracts(Compilation compilation foreach (var classNode in classNodes) { var classSymbol = semanticModel.GetDeclaredSymbol(classNode); - if (classSymbol != null && IsDerivedFromSmartContract(classSymbol, "Neo.SmartContract.Framework.SmartContract", semanticModel)) + if (classSymbol is { IsAbstract: false, DeclaredAccessibility: Accessibility.Public } && IsDerivedFromSmartContract(classSymbol, "Neo.SmartContract.Framework.SmartContract", semanticModel)) { allSmartContracts.Add(classSymbol); classDependencies[classSymbol] = new List(); foreach (var member in classSymbol.GetMembers()) { var memberTypeSymbol = (member as IFieldSymbol)?.Type ?? (member as IPropertySymbol)?.Type; - if (memberTypeSymbol is INamedTypeSymbol namedTypeSymbol && allSmartContracts.Contains(namedTypeSymbol)) + if (memberTypeSymbol is INamedTypeSymbol namedTypeSymbol && allSmartContracts.Contains(namedTypeSymbol) && !namedTypeSymbol.IsAbstract) { classDependencies[classSymbol].Add(namedTypeSymbol); } @@ -138,7 +138,15 @@ private List CompileProjectContracts(Compilation compilation if (classDependencies.Count == 0) throw new FormatException("No valid neo SmartContract found. Please make sure your contract is subclass of SmartContract and is not abstract."); // Check contract dependencies, make sure there is no cycle in the dependency graph var sortedClasses = TopologicalSort(classDependencies); - sortedClasses.ForEach(c => new CompilationContext(this, c).Compile()); + + sortedClasses.ForEach(c => + { + var context = new CompilationContext(this, c); + context.Compile(); + // Process the target contract add this compilation context + this.Contexts.Add(c, context); + }); + return Contexts.Select(p => p.Value).ToList(); } diff --git a/src/Neo.SmartContract.Framework/Interfaces/IOracle.cs b/src/Neo.SmartContract.Framework/Interfaces/IOracle.cs new file mode 100644 index 000000000..3b6814769 --- /dev/null +++ b/src/Neo.SmartContract.Framework/Interfaces/IOracle.cs @@ -0,0 +1,30 @@ +using Neo.SmartContract.Framework.Native; + +namespace Neo.SmartContract.Framework.Interfaces; + +/// +/// Interface of oracle callback method +/// +public interface IOracle +{ + /// + /// This method is called after the Oracle receives response from requested URL + /// + /// The url of the data source + /// Extra oracle request data specified by user + /// Oracle response code from data source + /// The oracle response data in format of json string + /// + /// + /// public static void OnOracleResponse(string url, object userData, OracleResponseCode code, string result) + /// { + /// if (Runtime.CallingScriptHash != Oracle.Hash) throw new Exception("Unauthorized!"); + /// Storage.Put(Storage.CurrentContext, PreData, result); + /// } + /// + public void OnOracleResponse( + string requestedUrl, + object userData, + OracleResponseCode oracleResponse, + string jsonString); +} diff --git a/tests/Neo.SmartContract.Framework.TestContracts/Contract_IOracle.cs b/tests/Neo.SmartContract.Framework.TestContracts/Contract_IOracle.cs new file mode 100644 index 000000000..a85791d0d --- /dev/null +++ b/tests/Neo.SmartContract.Framework.TestContracts/Contract_IOracle.cs @@ -0,0 +1,18 @@ +using Neo.SmartContract.Framework.Interfaces; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +namespace Neo.SmartContract.Framework.UnitTests.TestClasses +{ + public class Contract_IOracle : SmartContract, IOracle + { + private const string PreData = "PreData"; + + public void OnOracleResponse(string url, object userData, OracleResponseCode code, string result) + { + if (Runtime.CallingScriptHash != Oracle.Hash) + throw new System.Exception("Unauthorized!"); + Storage.Put(Storage.CurrentContext, PreData, result); + } + } +} diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/OracleTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/OracleTest.cs new file mode 100644 index 000000000..9935d2b51 --- /dev/null +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/OracleTest.cs @@ -0,0 +1,30 @@ +using System.Security.Cryptography; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.TestEngine; +using Neo.VM; +using Neo.Wallets; + +namespace Neo.SmartContract.Framework.UnitTests.Services +{ + [TestClass] + public class OracleTest + { + private TestEngine.TestEngine _engine; + + [TestInitialize] + public void Init() + { + _engine = new TestEngine.TestEngine(); + _engine.AddEntryScript(Utils.Extensions.TestContractRoot + "Contract_IOracle.cs"); + } + + [TestMethod] + public void Test_OracleResponse() + { + _engine.Reset(); + var result = _engine.ExecuteTestCaseStandard("onOracleResponse", "http://127.0.0.1", "test", 0x14, "{}"); + Assert.AreEqual(VMState.FAULT, _engine.State); + } + } +}