Skip to content

Commit

Permalink
Merged PR 91: Contract Compiler
Browse files Browse the repository at this point in the history
- Renames GetFileDllHelper to SmartContractCompiler
- Add SmartContractCompilationResult
- Cleans up and unifies list of allowed assemblies in a Smart Contract
- Update tests

Related work items: stratisproject#701
  • Loading branch information
Rowan de Haas authored and Rowan de Haas committed Apr 2, 2018
1 parent 8d757c0 commit 65a8624
Show file tree
Hide file tree
Showing 23 changed files with 550 additions and 382 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Compilation;
using Stratis.SmartContracts.Core.Util;
using Xunit;

Expand All @@ -7,6 +8,7 @@ public class DemoHelperTests
[Fact]
public void GetHexStringForDemo()
{
string example = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/SimpleAuction.cs").ToHexString();
var compilationResult = SmartContractCompiler.CompileFile("SmartContracts/SimpleAuction.cs");
string example = compilationResult.Compilation.ToHexString();
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
using Stratis.SmartContracts;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Backend;
using Stratis.SmartContracts.Core.Compilation;
using Stratis.SmartContracts.Core.State;
using Stratis.SmartContracts.Core.Util;
using Xunit;
using Block = Stratis.SmartContracts.Core.Block;

Expand Down Expand Up @@ -49,16 +49,69 @@ public void TestMethod(int number)
[Fact]
public void TestGasInjector()
{
byte[] originalAssemblyBytes = GetFileDllHelper.GetAssemblyBytesFromSource(TestSource);
SmartContractCompilationResult compilationResult = SmartContractCompiler.Compile(TestSource);
Assert.True(compilationResult.Success);

byte[] originalAssemblyBytes = compilationResult.Compilation;

var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(AppContext.BaseDirectory);
ModuleDefinition moduleDefinition = ModuleDefinition.ReadModule(new MemoryStream(originalAssemblyBytes), new ReaderParameters { AssemblyResolver = resolver });
ModuleDefinition moduleDefinition = ModuleDefinition.ReadModule(new MemoryStream(originalAssemblyBytes),
new ReaderParameters {AssemblyResolver = resolver});
TypeDefinition contractType = moduleDefinition.GetType(ContractName);
TypeDefinition baseType = contractType.BaseType.Resolve();
MethodDefinition testMethod = contractType.Methods.FirstOrDefault(x => x.Name == MethodName);
MethodDefinition constructorMethod = contractType.Methods.FirstOrDefault(x => x.Name.Contains("ctor"));
int aimGasAmount = testMethod.Body.Instructions.Count; // + constructorMethod.Body.Instructions.Count; // Have to figure out ctor gas metering
int aimGasAmount =
testMethod.Body.Instructions
.Count; // + constructorMethod.Body.Instructions.Count; // Have to figure out ctor gas metering

this.spendGasInjector.AddGasCalculationToContract(contractType, baseType);

using (var mem = new MemoryStream())
{
moduleDefinition.Write(mem);
byte[] injectedAssemblyBytes = mem.ToArray();

var gasLimit = (Gas) 500000;
var gasMeter = new GasMeter(gasLimit);
var persistenceStrategy = new MeteredPersistenceStrategy(this.repository, gasMeter);
var persistentState = new PersistentState(this.repository, persistenceStrategy,
TestAddress.ToUint160(this.network), this.network);
var vm = new ReflectionVirtualMachine(persistentState);

var executionContext = new SmartContractExecutionContext(
new Block(0, TestAddress),
new Message(TestAddress, TestAddress, 0, (Gas) 500000), 1, new object[] {1});

var internalTransactionExecutor = new InternalTransactionExecutor(this.repository, this.network);
Func<ulong> getBalance = () => repository.GetCurrentBalance(TestAddress.ToUint160(this.network));

ISmartContractExecutionResult result = vm.ExecuteMethod(
injectedAssemblyBytes,
ContractName,
MethodName,
executionContext,
gasMeter,
internalTransactionExecutor,
getBalance);
Assert.Equal(aimGasAmount, Convert.ToInt32(result.GasConsumed));
}
}

[Fact]
public void TestGasInjector_OutOfGasFails()
{
SmartContractCompilationResult compilationResult = SmartContractCompiler.CompileFile("SmartContracts/OutOfGasTest.cs");
Assert.True(compilationResult.Success);

byte[] originalAssemblyBytes = compilationResult.Compilation;

var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(AppContext.BaseDirectory);
ModuleDefinition moduleDefinition = ModuleDefinition.ReadModule(new MemoryStream(originalAssemblyBytes), new ReaderParameters { AssemblyResolver = resolver });
TypeDefinition contractType = moduleDefinition.GetType("OutOfGasTest");
TypeDefinition baseType = contractType.BaseType.Resolve();

this.spendGasInjector.AddGasCalculationToContract(contractType, baseType);

Expand All @@ -73,20 +126,24 @@ public void TestGasInjector()
var persistentState = new PersistentState(this.repository, persistenceStrategy, TestAddress.ToUint160(this.network), this.network);
var vm = new ReflectionVirtualMachine(persistentState);

var executionContext = new SmartContractExecutionContext(new Block(0, TestAddress), new Message(TestAddress, TestAddress, 0, (Gas) 500000), 1, new object[] { 1 });
var executionContext = new SmartContractExecutionContext(new Block(0, TestAddress), new Message(TestAddress, TestAddress, 0, (Gas)500000), 1);

var internalTransactionExecutor = new InternalTransactionExecutor(this.repository, this.network);
Func<ulong> getBalance = () => repository.GetCurrentBalance(TestAddress.ToUint160(this.network));

ISmartContractExecutionResult result = vm.ExecuteMethod(
injectedAssemblyBytes,
ContractName,
MethodName,
injectedAssemblyBytes,
"OutOfGasTest",
"UseAllGas",
executionContext,
gasMeter,
internalTransactionExecutor,
getBalance);
Assert.Equal(aimGasAmount, Convert.ToInt32(result.GasConsumed));

Assert.NotNull(result.Exception);
Assert.Equal((Gas)0, gasMeter.GasAvailable);
Assert.Equal(gasLimit, result.GasConsumed);
Assert.Equal(gasLimit, gasMeter.GasConsumed);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Stratis.SmartContracts;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Backend;
using Stratis.SmartContracts.Core.Compilation;
using Stratis.SmartContracts.Core.ContractValidation;
using Stratis.SmartContracts.Core.State;
using Stratis.SmartContracts.Core.Util;
Expand Down Expand Up @@ -43,7 +44,10 @@ public ReflectionVirtualMachineTests()
public void VM_ExecuteContract_WithoutParameters()
{
//Get the contract execution code------------------------
byte[] contractExecutionCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/StorageTest.cs");
SmartContractCompilationResult compilationResult = SmartContractCompiler.CompileFile("SmartContracts/StorageTest.cs");
Assert.True(compilationResult.Success);

byte[] contractExecutionCode = compilationResult.Compilation;
//-------------------------------------------------------

//Call smart contract and add to transaction-------------
Expand Down Expand Up @@ -111,7 +115,10 @@ public void VM_ExecuteContract_WithoutParameters()
public void VM_ExecuteContract_WithParameters()
{
//Get the contract execution code------------------------
byte[] contractExecutionCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/StorageTestWithParameters.cs");
SmartContractCompilationResult compilationResult = SmartContractCompiler.CompileFile("SmartContracts/StorageTestWithParameters.cs");
Assert.True(compilationResult.Success);

byte[] contractExecutionCode = compilationResult.Compilation;
//-------------------------------------------------------

// //Call smart contract and add to transaction-------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Compilation;
using Xunit;

namespace Stratis.Bitcoin.Features.SmartContracts.Tests
{
public class SmartContractCompilerTests
{
[Fact]
public void SmartContract_Compiler_ReturnsFalse()
{
SmartContractCompilationResult compilationResult = SmartContractCompiler.Compile("Uncompilable");

Assert.False(compilationResult.Success);
Assert.NotEmpty(compilationResult.Diagnostics);
Assert.Null(compilationResult.Compilation);
}

[Fact]
public void SmartContract_Compiler_ReturnsTrue()
{
SmartContractCompilationResult compilationResult = SmartContractCompiler.Compile("class C{static void M(){}}");

Assert.True(compilationResult.Success);
Assert.Empty(compilationResult.Diagnostics);
Assert.NotNull(compilationResult.Compilation);
}

[Fact]
public void SmartContract_ReferenceResolver_HasCorrectAssemblies()
{
List<Assembly> allowedAssemblies = ReferencedAssemblyResolver.AllowedAssemblies.ToList();

Assert.Equal(4, allowedAssemblies.Count);
Assert.Contains(allowedAssemblies, a => a.GetName().Name == "System.Runtime");
Assert.Contains(allowedAssemblies, a => a.GetName().Name == "System.Private.CoreLib");
Assert.Contains(allowedAssemblies, a => a.GetName().Name == "Stratis.SmartContracts");
Assert.Contains(allowedAssemblies, a => a.GetName().Name == "System.Linq");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
using Stratis.SmartContracts;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Backend;
using Stratis.SmartContracts.Core.Exceptions;
using Stratis.SmartContracts.Core.Compilation;
using Stratis.SmartContracts.Core.State;
using Stratis.SmartContracts.Core.Util;
using Xunit;
using Block = Stratis.SmartContracts.Core.Block;

Expand All @@ -19,56 +18,28 @@ public sealed class SmartContractExceptionTests
{
private readonly ContractStateRepositoryRoot repository;
private readonly Network network;
private static readonly Address TestAddress = (Address)"mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn";
private static readonly Address TestAddress = (Address) "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn";

public SmartContractExceptionTests()
{
this.repository = new ContractStateRepositoryRoot(new NoDeleteSource<byte[], byte[]>(new MemoryDictionarySource()));
this.repository =
new ContractStateRepositoryRoot(new NoDeleteSource<byte[], byte[]>(new MemoryDictionarySource()));
this.network = Network.SmartContractsRegTest;
}

[Fact]
public void VM_Throws_OutOfGasException_CanCatch()
public void VM_Throws_Exception_CanCatch()
{
byte[] contractCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ThrowOutOfGasExceptionContract.cs");
SmartContractCompilationResult compilationResult = SmartContractCompiler.CompileFile("SmartContracts/ThrowExceptionContract.cs");
Assert.True(compilationResult.Success);

var gasLimit = (Gas)100;
var gasMeter = new GasMeter(gasLimit);
var persistenceStrategy = new MeteredPersistenceStrategy(this.repository, gasMeter);
var persistentState = new PersistentState(this.repository, persistenceStrategy, TestAddress.ToUint160(this.network), this.network);
var vm = new ReflectionVirtualMachine(persistentState);

var context = new SmartContractExecutionContext(
new Block(0, TestAddress),
new Message(TestAddress, TestAddress, 0, gasLimit),
1,
new object[] { }
);

var internalTransactionExecutor = new InternalTransactionExecutor(this.repository, this.network);
Func<ulong> getBalance = () => this.repository.GetCurrentBalance(TestAddress.ToUint160(this.network));

ISmartContractExecutionResult result = vm.ExecuteMethod(
contractCode,
"ThrowOutOfGasExceptionContract",
"ThrowException",
context,
gasMeter,
internalTransactionExecutor,
getBalance);

Assert.Equal(typeof(OutOfGasException), result.Exception.GetType());
}

[Fact]
public void VM_Throws_RefundGasException_CanCatch()
{
byte[] contractCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ThrowRefundGasExceptionContract.cs");
byte[] contractCode = compilationResult.Compilation;

var gasLimit = (Gas)100;
var gasLimit = (Gas) 100;
var gasMeter = new GasMeter(gasLimit);
var persistenceStrategy = new MeteredPersistenceStrategy(this.repository, gasMeter);
var persistentState = new PersistentState(this.repository, persistenceStrategy, TestAddress.ToUint160(this.network), this.network);
var persistentState = new PersistentState(this.repository, persistenceStrategy,
TestAddress.ToUint160(this.network), this.network);
var vm = new ReflectionVirtualMachine(persistentState);

var context = new SmartContractExecutionContext(
Expand All @@ -83,41 +54,7 @@ public void VM_Throws_RefundGasException_CanCatch()

ISmartContractExecutionResult result = vm.ExecuteMethod(
contractCode,
"ThrowRefundGasExceptionContract",
"ThrowException",
context,
gasMeter,
internalTransactionExecutor,
getBalance);

Assert.Equal<ulong>(10, result.GasConsumed);
Assert.Equal(typeof(RefundGasException), result.Exception.GetType());
}

[Fact]
public void VM_Throws_SystemException_CanCatch()
{
byte[] contractCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ThrowSystemExceptionContract.cs");

var gasLimit = (Gas)100;
var gasMeter = new GasMeter(gasLimit);
var persistenceStrategy = new MeteredPersistenceStrategy(this.repository, gasMeter);
var persistentState = new PersistentState(this.repository, persistenceStrategy, TestAddress.ToUint160(this.network), this.network);
var vm = new ReflectionVirtualMachine(persistentState);

var context = new SmartContractExecutionContext(
new Block(0, TestAddress),
new Message(TestAddress, TestAddress, 0, (Gas)100),
1,
new object[] { }
);

var internalTransactionExecutor = new InternalTransactionExecutor(this.repository, this.network);
Func<ulong> getBalance = () => this.repository.GetCurrentBalance(TestAddress.ToUint160(this.network));

ISmartContractExecutionResult result = vm.ExecuteMethod(
contractCode,
"ThrowSystemExceptionContract",
"ThrowExceptionContract",
"ThrowException",
context,
gasMeter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Stratis.SmartContracts;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Backend;
using Stratis.SmartContracts.Core.Compilation;
using Stratis.SmartContracts.Core.ContractValidation;
using Stratis.SmartContracts.Core.Exceptions;
using Stratis.SmartContracts.Core.State;
Expand Down Expand Up @@ -32,7 +33,10 @@ public SmartContractExecutorTests()
public void SME_CallContract_Fails_ReturnFundsToSender()
{
//Get the contract execution code------------------------
byte[] contractExecutionCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ThrowSystemExceptionContract.cs");
SmartContractCompilationResult compilationResult =
SmartContractCompiler.CompileFile("SmartContracts/ThrowExceptionContract.cs");
Assert.True(compilationResult.Success);
byte[] contractExecutionCode = compilationResult.Compilation;
//-------------------------------------------------------

var toAddress = new uint160(1);
Expand Down Expand Up @@ -71,7 +75,10 @@ public void SME_CallContract_Fails_ReturnFundsToSender()
public void SME_CreateContract_ValidationFails_RefundGas_MempoolFeeLessGas()
{
//Get the contract execution code------------------------
byte[] contractExecutionCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ContractFailsValidation.cs");
SmartContractCompilationResult compilationResult =
SmartContractCompiler.CompileFile("SmartContracts/ContractFailsValidation.cs");
Assert.True(compilationResult.Success);
byte[] contractExecutionCode = compilationResult.Compilation;
//-------------------------------------------------------

var toAddress = new uint160(1);
Expand Down Expand Up @@ -118,7 +125,10 @@ public void SME_CallContract_DoesNotExist_Refund()
[Fact]
public void SME_CreateContract_ConstructorFails_Refund()
{
byte[] contractCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ContractConstructorInvalid.cs");
SmartContractCompilationResult compilationResult =
SmartContractCompiler.CompileFile("SmartContracts/ContractConstructorInvalid.cs");
Assert.True(compilationResult.Success);
byte[] contractCode = compilationResult.Compilation;

var carrier = SmartContractCarrier.CreateContract(0, contractCode, 1, (Gas)10000);
var tx = new Transaction();
Expand All @@ -136,7 +146,10 @@ public void SME_CreateContract_ConstructorFails_Refund()
[Fact]
public void SME_CreateContract_MethodParameters_InvalidParameterCount()
{
byte[] contractCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ContractInvalidParameterCount.cs");
SmartContractCompilationResult compilationResult =
SmartContractCompiler.CompileFile("SmartContracts/ContractInvalidParameterCount.cs");
Assert.True(compilationResult.Success);
byte[] contractCode = compilationResult.Compilation;

string[] methodParameters = new string[]
{
Expand All @@ -159,7 +172,10 @@ public void SME_CreateContract_MethodParameters_InvalidParameterCount()
[Fact]
public void SME_CreateContract_MethodParameters_ParameterTypeMismatch()
{
byte[] contractCode = GetFileDllHelper.GetAssemblyBytesFromFile("SmartContracts/ContractMethodParameterTypeMismatch.cs");
SmartContractCompilationResult compilationResult =
SmartContractCompiler.CompileFile("SmartContracts/ContractMethodParameterTypeMismatch.cs");
Assert.True(compilationResult.Success);
byte[] contractCode = compilationResult.Compilation;

string[] methodParameters = new string[]
{
Expand Down
Loading

0 comments on commit 65a8624

Please sign in to comment.