diff --git a/pkg/core/interop/contract/account.go b/pkg/core/interop/contract/account.go new file mode 100644 index 000000000..b87b294ec --- /dev/null +++ b/pkg/core/interop/contract/account.go @@ -0,0 +1,71 @@ +package contract + +import ( + "crypto/elliptic" + "errors" + "math" + + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/fee" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// CreateMultisigAccount calculates multisig contract scripthash for a +// given m and a set of public keys. +func CreateMultisigAccount(ic *interop.Context) error { + m := ic.VM.Estack().Pop().BigInt() + mu64 := m.Uint64() + if !m.IsUint64() || mu64 > math.MaxInt32 { + return errors.New("m must be positive and fit int32") + } + arr := ic.VM.Estack().Pop().Array() + pubs := make(keys.PublicKeys, len(arr)) + for i, pk := range arr { + p, err := keys.NewPublicKeyFromBytes(pk.Value().([]byte), elliptic.P256()) + if err != nil { + return err + } + pubs[i] = p + } + var invokeFee int64 + if ic.IsHardforkEnabled(config.HFAspidochelone) { + invokeFee = fee.ECDSAVerifyPrice * int64(len(pubs)) + } else { + invokeFee = 1 << 8 + } + invokeFee *= ic.BaseExecFee() + if !ic.VM.AddGas(invokeFee) { + return errors.New("gas limit exceeded") + } + script, err := smartcontract.CreateMultiSigRedeemScript(int(mu64), pubs) + if err != nil { + return err + } + ic.VM.Estack().PushItem(stackitem.NewByteArray(hash.Hash160(script).BytesBE())) + return nil +} + +// CreateStandardAccount calculates contract scripthash for a given public key. +func CreateStandardAccount(ic *interop.Context) error { + h := ic.VM.Estack().Pop().Bytes() + p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256()) + if err != nil { + return err + } + var invokeFee int64 + if ic.IsHardforkEnabled(config.HFAspidochelone) { + invokeFee = fee.ECDSAVerifyPrice + } else { + invokeFee = 1 << 8 + } + invokeFee *= ic.BaseExecFee() + if !ic.VM.AddGas(invokeFee) { + return errors.New("gas limit exceeded") + } + ic.VM.Estack().PushItem(stackitem.NewByteArray(p.GetScriptHash().BytesBE())) + return nil +} diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index 26a23aaa1..7bed15e45 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -3,6 +3,7 @@ package contract import ( "errors" "fmt" + "math/big" "strings" "github.com/nspcc-dev/neo-go/pkg/core/dao" @@ -172,3 +173,9 @@ func CallFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract } return nil } + +// GetCallFlags returns current context calling flags. +func GetCallFlags(ic *interop.Context) error { + ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.VM.Context().GetCallFlags())))) + return nil +} diff --git a/pkg/core/interop/contract/call_test.go b/pkg/core/interop/contract/call_test.go new file mode 100644 index 000000000..155a9cb90 --- /dev/null +++ b/pkg/core/interop/contract/call_test.go @@ -0,0 +1,25 @@ +package contract_test + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/stretchr/testify/require" +) + +func TestGetCallFlags(t *testing.T) { + bc, _ := chain.NewSingle(t) + ic := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + + ic.VM.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, callflag.All) + require.NoError(t, contract.GetCallFlags(ic)) + require.Equal(t, int64(callflag.All), ic.VM.Estack().Pop().Value().(*big.Int).Int64()) +} diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 96d9b0a09..c7c9731a2 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -2,23 +2,15 @@ package core import ( "context" - "crypto/elliptic" "errors" "fmt" - "math" - "math/big" - "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop" istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) @@ -206,65 +198,3 @@ func storageFind(ic *interop.Context) error { return nil } - -// contractCreateMultisigAccount calculates multisig contract scripthash for a -// given m and a set of public keys. -func contractCreateMultisigAccount(ic *interop.Context) error { - m := ic.VM.Estack().Pop().BigInt() - mu64 := m.Uint64() - if !m.IsUint64() || mu64 > math.MaxInt32 { - return errors.New("m must be positive and fit int32") - } - arr := ic.VM.Estack().Pop().Array() - pubs := make(keys.PublicKeys, len(arr)) - for i, pk := range arr { - p, err := keys.NewPublicKeyFromBytes(pk.Value().([]byte), elliptic.P256()) - if err != nil { - return err - } - pubs[i] = p - } - var invokeFee int64 - if ic.IsHardforkEnabled(config.HFAspidochelone) { - invokeFee = fee.ECDSAVerifyPrice * int64(len(pubs)) - } else { - invokeFee = 1 << 8 - } - invokeFee *= ic.BaseExecFee() - if !ic.VM.AddGas(invokeFee) { - return errors.New("gas limit exceeded") - } - script, err := smartcontract.CreateMultiSigRedeemScript(int(mu64), pubs) - if err != nil { - return err - } - ic.VM.Estack().PushItem(stackitem.NewByteArray(hash.Hash160(script).BytesBE())) - return nil -} - -// contractCreateStandardAccount calculates contract scripthash for a given public key. -func contractCreateStandardAccount(ic *interop.Context) error { - h := ic.VM.Estack().Pop().Bytes() - p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256()) - if err != nil { - return err - } - var invokeFee int64 - if ic.IsHardforkEnabled(config.HFAspidochelone) { - invokeFee = fee.ECDSAVerifyPrice - } else { - invokeFee = 1 << 8 - } - invokeFee *= ic.BaseExecFee() - if !ic.VM.AddGas(invokeFee) { - return errors.New("gas limit exceeded") - } - ic.VM.Estack().PushItem(stackitem.NewByteArray(p.GetScriptHash().BytesBE())) - return nil -} - -// contractGetCallFlags returns current context calling flags. -func contractGetCallFlags(ic *interop.Context) error { - ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.VM.Context().GetCallFlags())))) - return nil -} diff --git a/pkg/core/interop_system_core_test.go b/pkg/core/interop_system_core_test.go index e0211922f..09e38c788 100644 --- a/pkg/core/interop_system_core_test.go +++ b/pkg/core/interop_system_core_test.go @@ -706,14 +706,6 @@ func TestContractCall(t *testing.T) { }) } -func TestContractGetCallFlags(t *testing.T) { - v, ic, _ := createVM(t) - - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, callflag.All) - require.NoError(t, contractGetCallFlags(ic)) - require.Equal(t, int64(callflag.All), v.Estack().Pop().Value().(*big.Int).Int64()) -} - func TestRuntimeCheckWitness(t *testing.T) { _, ic, bc := createVM(t) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index cf2e3982b..7de730dff 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -33,9 +33,9 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, RequiredFlags: callflag.ReadStates | callflag.AllowCall, ParamCount: 4}, {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1}, - {Name: interopnames.SystemContractCreateMultisigAccount, Func: contractCreateMultisigAccount, Price: 0, ParamCount: 2}, - {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 0, ParamCount: 1}, - {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10}, + {Name: interopnames.SystemContractCreateMultisigAccount, Func: contract.CreateMultisigAccount, Price: 0, ParamCount: 2}, + {Name: interopnames.SystemContractCreateStandardAccount, Func: contract.CreateStandardAccount, Price: 0, ParamCount: 1}, + {Name: interopnames.SystemContractGetCallFlags, Func: contract.GetCallFlags, Price: 1 << 10}, {Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: callflag.States}, {Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, RequiredFlags: callflag.States}, {Name: interopnames.SystemCryptoCheckMultisig, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 2},