From 0114f9a9121f300cfcc03e1dff831eb91eca9266 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Thu, 29 Apr 2021 11:33:21 +0300 Subject: [PATCH] interop: add `System.Runtime.BurnGas` --- pkg/compiler/syscall_test.go | 1 + pkg/core/interop/interopnames/names.go | 2 ++ pkg/core/interop/runtime/engine.go | 19 +++++++++++ pkg/core/interop_system_test.go | 46 ++++++++++++++++++++++++++ pkg/core/interops.go | 1 + pkg/interop/runtime/runtime.go | 5 +++ 6 files changed, 74 insertions(+) diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index f0080b476..a2b02b385 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -68,6 +68,7 @@ func TestSyscallExecution(t *testing.T) { "iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false}, "iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false}, "iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false}, + "runtime.BurnGas": {interopnames.SystemRuntimeBurnGas, []string{"1"}, true}, "runtime.CheckWitness": {interopnames.SystemRuntimeCheckWitness, []string{b}, false}, "runtime.GasLeft": {interopnames.SystemRuntimeGasLeft, nil, false}, "runtime.GetCallingScriptHash": {interopnames.SystemRuntimeGetCallingScriptHash, nil, false}, diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 0ad602163..6d2da3bea 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -16,6 +16,7 @@ const ( SystemIteratorCreate = "System.Iterator.Create" SystemIteratorNext = "System.Iterator.Next" SystemIteratorValue = "System.Iterator.Value" + SystemRuntimeBurnGas = "System.Runtime.BurnGas" SystemRuntimeCheckWitness = "System.Runtime.CheckWitness" SystemRuntimeGasLeft = "System.Runtime.GasLeft" SystemRuntimeGetCallingScriptHash = "System.Runtime.GetCallingScriptHash" @@ -55,6 +56,7 @@ var names = []string{ SystemIteratorCreate, SystemIteratorNext, SystemIteratorValue, + SystemRuntimeBurnGas, SystemRuntimeCheckWitness, SystemRuntimeGasLeft, SystemRuntimeGetCallingScriptHash, diff --git a/pkg/core/interop/runtime/engine.go b/pkg/core/interop/runtime/engine.go index abd071678..8bb3f8e19 100644 --- a/pkg/core/interop/runtime/engine.go +++ b/pkg/core/interop/runtime/engine.go @@ -1,6 +1,7 @@ package runtime import ( + "errors" "fmt" "github.com/nspcc-dev/neo-go/pkg/core/interop" @@ -99,3 +100,21 @@ func GetTime(ic *interop.Context) error { ic.VM.Estack().PushVal(ic.Block.Timestamp) return nil } + +// BurnGas burns GAS to benefit NEO ecosystem. +func BurnGas(ic *interop.Context) error { + gas := ic.VM.Estack().Pop().BigInt() + if !gas.IsInt64() { + return errors.New("invalid GAS value") + } + + g := gas.Int64() + if g <= 0 { + return errors.New("GAS must be positive") + } + + if !ic.VM.AddGas(g) { + return errors.New("GAS limit exceeded") + } + return nil +} diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index b682f204c..06c77a04a 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -23,6 +23,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -346,6 +347,9 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET) callT2Off := w.Len() emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET) + burnGasOff := w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas) + emit.Opcodes(w.BinWriter, opcode.RET) script := w.Bytes() h := hash.Hash160(script) @@ -506,6 +510,14 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { Offset: callT2Off, ReturnType: smartcontract.IntegerType, }, + { + Name: "burnGas", + Offset: burnGasOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("amount", smartcontract.IntegerType), + }, + ReturnType: smartcontract.VoidType, + }, } m.Permissions = make([]manifest.Permission, 2) m.Permissions[0].Contract.Type = manifest.PermissionHash @@ -941,3 +953,37 @@ func TestLoadToken(t *testing.T) { checkFAULTState(t, aer) }) } + +func TestRuntimeBurnGas(t *testing.T) { + bc := newTestChain(t) + + cs, _ := getTestContractState(bc) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) + + const sysFee = 2_000000 + + t.Run("good", func(t *testing.T) { + aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", int64(1)) + require.NoError(t, err) + require.Equal(t, vm.HaltState, aer.VMState) + + t.Run("gas limit exceeded", func(t *testing.T) { + aer, err = invokeContractMethod(bc, aer.GasConsumed, cs.Hash, "burnGas", int64(2)) + require.NoError(t, err) + require.Equal(t, vm.FaultState, aer.VMState) + }) + }) + t.Run("too big integer", func(t *testing.T) { + gas := big.NewInt(math.MaxInt64) + gas.Add(gas, big.NewInt(1)) + + aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", gas) + require.NoError(t, err) + checkFAULTState(t, aer) + }) + t.Run("zero GAS", func(t *testing.T) { + aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", int64(0)) + require.NoError(t, err) + checkFAULTState(t, aer) + }) +} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 6b4e88ac9..07f09cb9e 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -41,6 +41,7 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 1 << 4, ParamCount: 1}, {Name: interopnames.SystemIteratorNext, Func: iterator.Next, Price: 1 << 15, ParamCount: 1}, {Name: interopnames.SystemIteratorValue, Func: iterator.Value, Price: 1 << 4, ParamCount: 1}, + {Name: interopnames.SystemRuntimeBurnGas, Func: runtime.BurnGas, Price: 1 << 4, ParamCount: 1}, {Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 1 << 10, RequiredFlags: callflag.NoneFlag, ParamCount: 1}, {Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 1 << 4}, diff --git a/pkg/interop/runtime/runtime.go b/pkg/interop/runtime/runtime.go index 8a8b8cb16..174fc0a91 100644 --- a/pkg/interop/runtime/runtime.go +++ b/pkg/interop/runtime/runtime.go @@ -17,6 +17,11 @@ const ( Verification byte = 0x20 ) +// BurnGas burns provided amount of GAS. It uses `System.Runtime.BurnGas` syscall. +func BurnGas(gas int) { + neogointernal.Syscall1NoReturn("System.Runtime.BurnGas", gas) +} + // CheckWitness verifies if the given script hash (160-bit BE value in a 20 byte // slice) or key (compressed serialized 33-byte form) is one of the signers of // this invocation. It uses `System.Runtime.CheckWitness` syscall.