forked from TrueCloudLab/neoneo-go
Merge pull request #1857 from nspcc-dev/rpc/nep11
rpc: add NEP11 and NNS interfaces to RPC Client
This commit is contained in:
commit
5b4f6d255f
16 changed files with 657 additions and 229 deletions
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
|
||||
|
@ -77,10 +78,10 @@ func TestRoleManagementRole(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNameServiceRecordType(t *testing.T) {
|
||||
require.EqualValues(t, native.RecordTypeA, nameservice.TypeA)
|
||||
require.EqualValues(t, native.RecordTypeCNAME, nameservice.TypeCNAME)
|
||||
require.EqualValues(t, native.RecordTypeTXT, nameservice.TypeTXT)
|
||||
require.EqualValues(t, native.RecordTypeAAAA, nameservice.TypeAAAA)
|
||||
require.EqualValues(t, nnsrecords.A, nameservice.TypeA)
|
||||
require.EqualValues(t, nnsrecords.CNAME, nameservice.TypeCNAME)
|
||||
require.EqualValues(t, nnsrecords.TXT, nameservice.TypeTXT)
|
||||
require.EqualValues(t, nnsrecords.AAAA, nameservice.TypeAAAA)
|
||||
}
|
||||
|
||||
func TestCryptoLibNamedCurve(t *testing.T) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
|
@ -484,6 +485,21 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
|
|||
require.NoError(t, bc.AddBlock(b))
|
||||
checkTxHalt(t, bc, txDeploy3.Hash())
|
||||
|
||||
// register `neo.com` with A record type and priv0 owner via NNS
|
||||
transferFundsToCommittee(t, bc) // block #11
|
||||
res, err := invokeContractMethodGeneric(bc, defaultNameServiceSysfee,
|
||||
bc.contracts.NameService.Hash, "addRoot", true, "com") // block #12
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.Null{})
|
||||
res, err = invokeContractMethodGeneric(bc, native.DefaultDomainPrice+defaultNameServiceSysfee,
|
||||
bc.contracts.NameService.Hash, "register", acc0, "neo.com", priv0ScriptHash) // block #13
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.NewBool(true))
|
||||
res, err = invokeContractMethodGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash,
|
||||
"setRecord", acc0, "neo.com", int64(nnsrecords.A), "1.2.3.4") // block #14
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.Null{})
|
||||
|
||||
// Compile contract to test `invokescript` RPC call
|
||||
_, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest", nil)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||
|
@ -42,17 +43,6 @@ type nameState struct {
|
|||
Admin util.Uint160
|
||||
}
|
||||
|
||||
// RecordType represents name record type.
|
||||
type RecordType byte
|
||||
|
||||
// Pre-defined record types.
|
||||
const (
|
||||
RecordTypeA RecordType = 1
|
||||
RecordTypeCNAME RecordType = 5
|
||||
RecordTypeTXT RecordType = 16
|
||||
RecordTypeAAAA RecordType = 28
|
||||
)
|
||||
|
||||
const (
|
||||
nameServiceID = -10
|
||||
|
||||
|
@ -448,19 +438,19 @@ func (n *NameService) setRecord(ic *interop.Context, args []stackitem.Item) stac
|
|||
return stackitem.Null{}
|
||||
}
|
||||
|
||||
func checkName(rt RecordType, name string) {
|
||||
func checkName(rt nnsrecords.Type, name string) {
|
||||
var valid bool
|
||||
switch rt {
|
||||
case RecordTypeA:
|
||||
case nnsrecords.A:
|
||||
// We can't rely on `len(ip) == net.IPv4len` because
|
||||
// IPv4 can be parsed to mapped representation.
|
||||
valid = ipv4Regex.MatchString(name) &&
|
||||
net.ParseIP(name) != nil
|
||||
case RecordTypeCNAME:
|
||||
case nnsrecords.CNAME:
|
||||
valid = matchName(name)
|
||||
case RecordTypeTXT:
|
||||
case nnsrecords.TXT:
|
||||
valid = utf8.RuneCountInString(name) <= 255
|
||||
case RecordTypeAAAA:
|
||||
case nnsrecords.AAAA:
|
||||
valid = ipv6Regex.MatchString(name) &&
|
||||
net.ParseIP(name) != nil
|
||||
}
|
||||
|
@ -513,7 +503,7 @@ func (n *NameService) resolve(ic *interop.Context, args []stackitem.Item) stacki
|
|||
return stackitem.NewByteArray([]byte(result))
|
||||
}
|
||||
|
||||
func (n *NameService) resolveInternal(ic *interop.Context, name string, t RecordType, redirect int) (string, bool) {
|
||||
func (n *NameService) resolveInternal(ic *interop.Context, name string, t nnsrecords.Type, redirect int) (string, bool) {
|
||||
if redirect < 0 {
|
||||
panic("invalid redirect")
|
||||
}
|
||||
|
@ -521,26 +511,26 @@ func (n *NameService) resolveInternal(ic *interop.Context, name string, t Record
|
|||
if data, ok := records[t]; ok {
|
||||
return data, true
|
||||
}
|
||||
data, ok := records[RecordTypeCNAME]
|
||||
data, ok := records[nnsrecords.CNAME]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return n.resolveInternal(ic, data, t, redirect-1)
|
||||
}
|
||||
|
||||
func (n *NameService) getRecordsInternal(d dao.DAO, name string) map[RecordType]string {
|
||||
func (n *NameService) getRecordsInternal(d dao.DAO, name string) map[nnsrecords.Type]string {
|
||||
domain := toDomain(name)
|
||||
key := makeRecordKey(domain, name, 0)
|
||||
key = key[:len(key)-1]
|
||||
res := make(map[RecordType]string)
|
||||
res := make(map[nnsrecords.Type]string)
|
||||
d.Seek(n.ID, key, func(k, v []byte) {
|
||||
rt := RecordType(k[len(k)-1])
|
||||
rt := nnsrecords.Type(k[len(k)-1])
|
||||
res[rt] = string(v)
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
func makeRecordKey(domain, name string, rt RecordType) []byte {
|
||||
func makeRecordKey(domain, name string, rt nnsrecords.Type) []byte {
|
||||
key := make([]byte, 1+util.Uint160Size+util.Uint160Size+1)
|
||||
key[0] = prefixRecord
|
||||
i := 1
|
||||
|
@ -647,7 +637,7 @@ func toDomain(name string) string {
|
|||
return domain
|
||||
}
|
||||
|
||||
func toRecordType(item stackitem.Item) RecordType {
|
||||
func toRecordType(item stackitem.Item) nnsrecords.Type {
|
||||
bi, err := item.TryInteger()
|
||||
if err != nil || !bi.IsInt64() {
|
||||
panic("invalid record type")
|
||||
|
@ -656,8 +646,8 @@ func toRecordType(item stackitem.Item) RecordType {
|
|||
if val > math.MaxUint8 {
|
||||
panic("invalid record type")
|
||||
}
|
||||
switch rt := RecordType(val); rt {
|
||||
case RecordTypeA, RecordTypeCNAME, RecordTypeTXT, RecordTypeAAAA:
|
||||
switch rt := nnsrecords.Type(val); rt {
|
||||
case nnsrecords.A, nnsrecords.CNAME, nnsrecords.TXT, nnsrecords.AAAA:
|
||||
return rt
|
||||
default:
|
||||
panic("invalid record type")
|
||||
|
|
|
@ -3,6 +3,7 @@ package native
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -36,45 +37,45 @@ func TestParseDomain(t *testing.T) {
|
|||
func TestNameService_CheckName(t *testing.T) {
|
||||
// tests are got from the C# implementation
|
||||
testCases := []struct {
|
||||
Type RecordType
|
||||
Type nnsrecords.Type
|
||||
Name string
|
||||
ShouldFail bool
|
||||
}{
|
||||
{Type: RecordTypeA, Name: "0.0.0.0"},
|
||||
{Type: RecordTypeA, Name: "10.10.10.10"},
|
||||
{Type: RecordTypeA, Name: "255.255.255.255"},
|
||||
{Type: RecordTypeA, Name: "192.168.1.1"},
|
||||
{Type: RecordTypeA, Name: "1a", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "256.0.0.0", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "01.01.01.01", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "00.0.0.0", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "0.0.0.-1", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "0.0.0.0.1", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "ff.ff.ff.ff", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "0.0.256", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "0.0.0", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "0.257", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "1.1", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "257", ShouldFail: true},
|
||||
{Type: RecordTypeA, Name: "1", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "2001:db8::8:800:200c:417a"},
|
||||
{Type: RecordTypeAAAA, Name: "ff01::101"},
|
||||
{Type: RecordTypeAAAA, Name: "::1"},
|
||||
{Type: RecordTypeAAAA, Name: "::"},
|
||||
{Type: RecordTypeAAAA, Name: "2001:db8:0:0:8:800:200c:417a"},
|
||||
{Type: RecordTypeAAAA, Name: "ff01:0:0:0:0:0:0:101"},
|
||||
{Type: RecordTypeAAAA, Name: "0:0:0:0:0:0:0:1"},
|
||||
{Type: RecordTypeAAAA, Name: "0:0:0:0:0:0:0:0"},
|
||||
{Type: RecordTypeAAAA, Name: "2001:DB8::8:800:200C:417A", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "FF01::101", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "fF01::101", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "2001:DB8:0:0:8:800:200C:417A", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "FF01:0:0:0:0:0:0:101", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "::ffff:1.01.1.01", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "2001:DB8:0:0:8:800:200C:4Z", ShouldFail: true},
|
||||
{Type: RecordTypeAAAA, Name: "::13.1.68.3", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "0.0.0.0"},
|
||||
{Type: nnsrecords.A, Name: "10.10.10.10"},
|
||||
{Type: nnsrecords.A, Name: "255.255.255.255"},
|
||||
{Type: nnsrecords.A, Name: "192.168.1.1"},
|
||||
{Type: nnsrecords.A, Name: "1a", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "256.0.0.0", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "01.01.01.01", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "00.0.0.0", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "0.0.0.-1", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "0.0.0.0.1", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "ff.ff.ff.ff", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "0.0.256", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "0.0.0", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "0.257", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "1.1", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "257", ShouldFail: true},
|
||||
{Type: nnsrecords.A, Name: "1", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "2001:db8::8:800:200c:417a"},
|
||||
{Type: nnsrecords.AAAA, Name: "ff01::101"},
|
||||
{Type: nnsrecords.AAAA, Name: "::1"},
|
||||
{Type: nnsrecords.AAAA, Name: "::"},
|
||||
{Type: nnsrecords.AAAA, Name: "2001:db8:0:0:8:800:200c:417a"},
|
||||
{Type: nnsrecords.AAAA, Name: "ff01:0:0:0:0:0:0:101"},
|
||||
{Type: nnsrecords.AAAA, Name: "0:0:0:0:0:0:0:1"},
|
||||
{Type: nnsrecords.AAAA, Name: "0:0:0:0:0:0:0:0"},
|
||||
{Type: nnsrecords.AAAA, Name: "2001:DB8::8:800:200C:417A", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "FF01::101", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "fF01::101", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "2001:DB8:0:0:8:800:200C:417A", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "FF01:0:0:0:0:0:0:101", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "::ffff:1.01.1.01", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "2001:DB8:0:0:8:800:200C:4Z", ShouldFail: true},
|
||||
{Type: nnsrecords.AAAA, Name: "::13.1.68.3", ShouldFail: true},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
if testCase.ShouldFail {
|
||||
|
|
12
pkg/core/native/nnsrecords/nnsrecords.go
Normal file
12
pkg/core/native/nnsrecords/nnsrecords.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package nnsrecords
|
||||
|
||||
// Type represents name record type.
|
||||
type Type byte
|
||||
|
||||
// Pre-defined record types.
|
||||
const (
|
||||
A Type = 1
|
||||
CNAME Type = 5
|
||||
TXT Type = 16
|
||||
AAAA Type = 28
|
||||
)
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
|
@ -67,7 +68,7 @@ func TestExpiration(t *testing.T) {
|
|||
true, "first.com", acc.Contract.ScriptHash())
|
||||
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc,
|
||||
"setRecord", stackitem.Null{}, "first.com", int64(native.RecordTypeTXT), "sometext")
|
||||
"setRecord", stackitem.Null{}, "first.com", int64(nnsrecords.TXT), "sometext")
|
||||
b1 := bc.topBlock.Load().(*block.Block)
|
||||
|
||||
tx, err := prepareContractMethodInvokeGeneric(bc, defaultRegisterSysfee, bc.contracts.NameService.Hash,
|
||||
|
@ -108,7 +109,7 @@ func TestExpiration(t *testing.T) {
|
|||
checkResult(t, &aer[0], stackitem.NewBool(false))
|
||||
|
||||
tx, err = prepareContractMethodInvokeGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash,
|
||||
"getRecord", acc, "first.com", int64(native.RecordTypeTXT))
|
||||
"getRecord", acc, "first.com", int64(nnsrecords.TXT))
|
||||
require.NoError(t, err)
|
||||
b5 := newBlockCustom(bc.GetConfig(), func(b *block.Block) {
|
||||
b.Index = b4.Index + 1
|
||||
|
@ -182,36 +183,36 @@ func TestSetGetRecord(t *testing.T) {
|
|||
testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com")
|
||||
|
||||
t.Run("set before register", func(t *testing.T) {
|
||||
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeTXT), "sometext")
|
||||
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(nnsrecords.TXT), "sometext")
|
||||
})
|
||||
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "register",
|
||||
true, "neo.com", testchain.CommitteeScriptHash())
|
||||
t.Run("invalid parameters", func(t *testing.T) {
|
||||
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(0xFF), "1.2.3.4")
|
||||
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeA), "not.an.ip.address")
|
||||
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(nnsrecords.A), "not.an.ip.address")
|
||||
})
|
||||
t.Run("invalid witness", func(t *testing.T) {
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil,
|
||||
"neo.com", int64(native.RecordTypeA), "1.2.3.4")
|
||||
"neo.com", int64(nnsrecords.A), "1.2.3.4")
|
||||
})
|
||||
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA))
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4")
|
||||
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA))
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4")
|
||||
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA))
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeAAAA), "2001:0000:1f1f:0000:0000:0100:11a0:addf")
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME), "nspcc.ru")
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeTXT), "sometext")
|
||||
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A))
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A), "1.2.3.4")
|
||||
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A))
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A), "1.2.3.4")
|
||||
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A))
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.AAAA), "2001:0000:1f1f:0000:0000:0100:11a0:addf")
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME), "nspcc.ru")
|
||||
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.TXT), "sometext")
|
||||
|
||||
// Delete record.
|
||||
t.Run("invalid witness", func(t *testing.T) {
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil,
|
||||
"neo.com", int64(native.RecordTypeCNAME))
|
||||
"neo.com", int64(nnsrecords.CNAME))
|
||||
})
|
||||
testNameServiceInvoke(t, bc, "getRecord", "nspcc.ru", "neo.com", int64(native.RecordTypeCNAME))
|
||||
testNameServiceInvoke(t, bc, "deleteRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME))
|
||||
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME))
|
||||
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA))
|
||||
testNameServiceInvoke(t, bc, "getRecord", "nspcc.ru", "neo.com", int64(nnsrecords.CNAME))
|
||||
testNameServiceInvoke(t, bc, "deleteRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME))
|
||||
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME))
|
||||
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A))
|
||||
}
|
||||
|
||||
func TestSetAdmin(t *testing.T) {
|
||||
|
@ -239,20 +240,20 @@ func TestSetAdmin(t *testing.T) {
|
|||
|
||||
t.Run("set and delete by admin", func(t *testing.T) {
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{},
|
||||
"neo.com", int64(native.RecordTypeTXT), "sometext")
|
||||
"neo.com", int64(nnsrecords.TXT), "sometext")
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, guest, "deleteRecord", nil,
|
||||
"neo.com", int64(native.RecordTypeTXT))
|
||||
"neo.com", int64(nnsrecords.TXT))
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", stackitem.Null{},
|
||||
"neo.com", int64(native.RecordTypeTXT))
|
||||
"neo.com", int64(nnsrecords.TXT))
|
||||
})
|
||||
|
||||
t.Run("set admin to null", func(t *testing.T) {
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{},
|
||||
"neo.com", int64(native.RecordTypeTXT), "sometext")
|
||||
"neo.com", int64(nnsrecords.TXT), "sometext")
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, owner, "setAdmin", stackitem.Null{},
|
||||
"neo.com", nil)
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", nil,
|
||||
"neo.com", int64(native.RecordTypeTXT))
|
||||
"neo.com", int64(nnsrecords.TXT))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -267,7 +268,7 @@ func TestTransfer(t *testing.T) {
|
|||
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "register",
|
||||
true, "neo.com", from.PrivateKey().GetScriptHash())
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{},
|
||||
"neo.com", int64(native.RecordTypeA), "1.2.3.4")
|
||||
"neo.com", int64(nnsrecords.A), "1.2.3.4")
|
||||
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer",
|
||||
nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists"))
|
||||
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "transfer",
|
||||
|
@ -355,23 +356,23 @@ func TestResolve(t *testing.T) {
|
|||
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register",
|
||||
true, "neo.com", acc.PrivateKey().GetScriptHash())
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{},
|
||||
"neo.com", int64(native.RecordTypeA), "1.2.3.4")
|
||||
"neo.com", int64(nnsrecords.A), "1.2.3.4")
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{},
|
||||
"neo.com", int64(native.RecordTypeCNAME), "alias.com")
|
||||
"neo.com", int64(nnsrecords.CNAME), "alias.com")
|
||||
|
||||
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register",
|
||||
true, "alias.com", acc.PrivateKey().GetScriptHash())
|
||||
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{},
|
||||
"alias.com", int64(native.RecordTypeTXT), "sometxt")
|
||||
"alias.com", int64(nnsrecords.TXT), "sometxt")
|
||||
|
||||
testNameServiceInvoke(t, bc, "resolve", "1.2.3.4",
|
||||
"neo.com", int64(native.RecordTypeA))
|
||||
"neo.com", int64(nnsrecords.A))
|
||||
testNameServiceInvoke(t, bc, "resolve", "alias.com",
|
||||
"neo.com", int64(native.RecordTypeCNAME))
|
||||
"neo.com", int64(nnsrecords.CNAME))
|
||||
testNameServiceInvoke(t, bc, "resolve", "sometxt",
|
||||
"neo.com", int64(native.RecordTypeTXT))
|
||||
"neo.com", int64(nnsrecords.TXT))
|
||||
testNameServiceInvoke(t, bc, "resolve", stackitem.Null{},
|
||||
"neo.com", int64(native.RecordTypeAAAA))
|
||||
"neo.com", int64(nnsrecords.AAAA))
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
97
pkg/rpc/client/helper.go
Normal file
97
pkg/rpc/client/helper.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// getInvocationError returns an error in case of bad VM state or empty stack.
|
||||
func getInvocationError(result *result.Invoke) error {
|
||||
if result.State != "HALT" {
|
||||
return fmt.Errorf("invocation failed: %s", result.FaultException)
|
||||
}
|
||||
if len(result.Stack) == 0 {
|
||||
return errors.New("result stack is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// topBoolFromStack returns the top boolean value from stack.
|
||||
func topBoolFromStack(st []stackitem.Item) (bool, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
result, ok := st[index].Value().(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("invalid stack item type: %s", st[index].Type())
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// topIntFromStack returns the top integer value from stack.
|
||||
func topIntFromStack(st []stackitem.Item) (int64, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
bi, err := st[index].TryInteger()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return bi.Int64(), nil
|
||||
}
|
||||
|
||||
// topPublicKeysFromStack returns the top array of public keys from stack.
|
||||
func topPublicKeysFromStack(st []stackitem.Item) (keys.PublicKeys, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
var (
|
||||
pks keys.PublicKeys
|
||||
err error
|
||||
)
|
||||
items, ok := st[index].Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type())
|
||||
}
|
||||
pks = make(keys.PublicKeys, len(items))
|
||||
for i, item := range items {
|
||||
val, ok := item.Value().([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
|
||||
}
|
||||
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pks, nil
|
||||
}
|
||||
|
||||
// top string from stack returns the top string from stack.
|
||||
func topStringFromStack(st []stackitem.Item) (string, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
bs, err := st[index].TryBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
// topUint160FromStack returns the top util.Uint160 from stack.
|
||||
func topUint160FromStack(st []stackitem.Item) (util.Uint160, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
bs, err := st[index].TryBytes()
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return util.Uint160DecodeBytesBE(bs)
|
||||
}
|
||||
|
||||
// topMapFromStack returns the top stackitem.Map from stack.
|
||||
func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
if t := st[index].Type(); t != stackitem.MapT {
|
||||
return nil, fmt.Errorf("invalid return stackitem type: %s", t.String())
|
||||
}
|
||||
return st[index].(*stackitem.Map), nil
|
||||
}
|
|
@ -3,14 +3,14 @@ package client
|
|||
// Various non-policy things from native contracts.
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"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"
|
||||
)
|
||||
|
||||
// GetOraclePrice invokes `getPrice` method on a native Oracle contract.
|
||||
|
@ -66,27 +66,53 @@ func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.Pu
|
|||
return topPublicKeysFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// topPublicKeysFromStack returns the top array of public keys from stack.
|
||||
func topPublicKeysFromStack(st []stackitem.Item) (keys.PublicKeys, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
var (
|
||||
pks keys.PublicKeys
|
||||
err error
|
||||
)
|
||||
items, ok := st[index].Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type())
|
||||
// NNSResolve invokes `resolve` method on a native NameService contract.
|
||||
func (c *Client) NNSResolve(name string, typ nnsrecords.Type) (string, error) {
|
||||
if typ == nnsrecords.CNAME {
|
||||
return "", errors.New("can't resolve CNAME record type")
|
||||
}
|
||||
pks = make(keys.PublicKeys, len(items))
|
||||
for i, item := range items {
|
||||
val, ok := item.Value().([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
|
||||
}
|
||||
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rmHash, err := c.GetNativeContractHash(nativenames.NameService)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get native NameService hash: %w", err)
|
||||
}
|
||||
return pks, nil
|
||||
result, err := c.InvokeFunction(rmHash, "resolve", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: name,
|
||||
},
|
||||
{
|
||||
Type: smartcontract.IntegerType,
|
||||
Value: int64(typ),
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("`resolve`: %w", err)
|
||||
}
|
||||
return topStringFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// NNSIsAvailable invokes `isAvailable` method on a native NameService contract.
|
||||
func (c *Client) NNSIsAvailable(name string) (bool, error) {
|
||||
rmHash, err := c.GetNativeContractHash(nativenames.NameService)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get native NameService hash: %w", err)
|
||||
}
|
||||
result, err := c.InvokeFunction(rmHash, "isAvailable", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: name,
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("`isAvailable`: %w", err)
|
||||
}
|
||||
return topBoolFromStack(result.Stack)
|
||||
}
|
||||
|
|
72
pkg/rpc/client/nep.go
Normal file
72
pkg/rpc/client/nep.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// nepDecimals invokes `decimals` NEP* method on a specified contract.
|
||||
func (c *Client) nepDecimals(tokenHash util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// nepSymbol invokes `symbol` NEP* method on a specified contract.
|
||||
func (c *Client) nepSymbol(tokenHash util.Uint160) (string, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return topStringFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// nepTotalSupply invokes `totalSupply` NEP* method on a specified contract.
|
||||
func (c *Client) nepTotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// nepBalanceOf invokes `balanceOf` NEP* method on a specified contract.
|
||||
func (c *Client) nepBalanceOf(tokenHash, acc util.Uint160, tokenID *string) (int64, error) {
|
||||
params := []smartcontract.Parameter{{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: acc,
|
||||
}}
|
||||
if tokenID != nil {
|
||||
params = append(params, smartcontract.Parameter{
|
||||
Type: smartcontract.StringType,
|
||||
Value: *tokenID,
|
||||
})
|
||||
}
|
||||
result, err := c.InvokeFunction(tokenHash, "balanceOf", params, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
}
|
165
pkg/rpc/client/nep11.go
Normal file
165
pkg/rpc/client/nep11.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
// NEP11Decimals invokes `decimals` NEP11 method on a specified contract.
|
||||
func (c *Client) NEP11Decimals(tokenHash util.Uint160) (int64, error) {
|
||||
return c.nepDecimals(tokenHash)
|
||||
}
|
||||
|
||||
// NEP11Symbol invokes `symbol` NEP11 method on a specified contract.
|
||||
func (c *Client) NEP11Symbol(tokenHash util.Uint160) (string, error) {
|
||||
return c.nepSymbol(tokenHash)
|
||||
}
|
||||
|
||||
// NEP11TotalSupply invokes `totalSupply` NEP11 method on a specified contract.
|
||||
func (c *Client) NEP11TotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||
return c.nepTotalSupply(tokenHash)
|
||||
}
|
||||
|
||||
// NEP11BalanceOf invokes `balanceOf` NEP11 method on a specified contract.
|
||||
func (c *Client) NEP11BalanceOf(tokenHash, owner util.Uint160) (int64, error) {
|
||||
return c.nepBalanceOf(tokenHash, owner, nil)
|
||||
}
|
||||
|
||||
// TransferNEP11 creates an invocation transaction that invokes 'transfer' method
|
||||
// on a given token to move the whole NEP11 token with the specified token ID to
|
||||
// given account and sends it to the network returning just a hash of it.
|
||||
func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
|
||||
tokenHash util.Uint160, tokenID string, gas int64) (util.Uint256, error) {
|
||||
if !c.initDone {
|
||||
return util.Uint256{}, errNetworkNotInitialized
|
||||
}
|
||||
tx, err := c.createNEP11TransferTx(acc, tokenHash, gas, to, tokenID)
|
||||
if err != nil {
|
||||
return util.Uint256{}, err
|
||||
}
|
||||
|
||||
if err := acc.SignTx(c.GetNetwork(), tx); err != nil {
|
||||
return util.Uint256{}, fmt.Errorf("can't sign NEP11 transfer tx: %w", err)
|
||||
}
|
||||
|
||||
return c.SendRawTransaction(tx)
|
||||
}
|
||||
|
||||
// createNEP11TransferTx is an internal helper for TransferNEP11 and
|
||||
// TransferNEP11D which creates an invocation transaction for the
|
||||
// 'transfer' method of a given contract (token) to move the whole (or the
|
||||
// specified amount of) NEP11 token with the specified token ID to given account
|
||||
// and returns it. The returned transaction is not signed.
|
||||
// `args` for TransferNEP11: to util.Uint160, tokenID string;
|
||||
// `args` for TransferNEP11D: from, to util.Uint160, amount int64, tokenID string.
|
||||
func (c *Client) createNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint160,
|
||||
gas int64, args ...interface{}) (*transaction.Transaction, error) {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, tokenHash, "transfer", callflag.All, args...)
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
if w.Err != nil {
|
||||
return nil, fmt.Errorf("failed to create NEP11 transfer script: %w", w.Err)
|
||||
}
|
||||
from, err := address.StringToUint160(acc.Address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad account address: %w", err)
|
||||
}
|
||||
return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, []SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: from,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc,
|
||||
}})
|
||||
}
|
||||
|
||||
// Non-divisible NFT methods section start.
|
||||
|
||||
// NEP11NDOwnerOf invokes `ownerOf` non-devisible NEP11 method with the
|
||||
// specified token ID on a specified contract.
|
||||
func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID string) (util.Uint160, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: tokenID,
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
|
||||
return topUint160FromStack(result.Stack)
|
||||
}
|
||||
|
||||
// Non-divisible NFT methods section end.
|
||||
|
||||
// Divisible NFT methods section start.
|
||||
|
||||
// TransferNEP11D creates an invocation transaction that invokes 'transfer'
|
||||
// method on a given token to move specified amount of divisible NEP11 assets
|
||||
// (in FixedN format using contract's number of decimals) to given account and
|
||||
// sends it to the network returning just a hash of it.
|
||||
func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
|
||||
tokenHash util.Uint160, amount int64, tokenID string, gas int64) (util.Uint256, error) {
|
||||
if !c.initDone {
|
||||
return util.Uint256{}, errNetworkNotInitialized
|
||||
}
|
||||
from, err := address.StringToUint160(acc.Address)
|
||||
if err != nil {
|
||||
return util.Uint256{}, fmt.Errorf("bad account address: %w", err)
|
||||
}
|
||||
tx, err := c.createNEP11TransferTx(acc, tokenHash, gas, acc.Address, from, to, amount, tokenID)
|
||||
if err != nil {
|
||||
return util.Uint256{}, err
|
||||
}
|
||||
|
||||
if err := acc.SignTx(c.GetNetwork(), tx); err != nil {
|
||||
return util.Uint256{}, fmt.Errorf("can't sign NEP11 divisible transfer tx: %w", err)
|
||||
}
|
||||
|
||||
return c.SendRawTransaction(tx)
|
||||
}
|
||||
|
||||
// NEP11DBalanceOf invokes `balanceOf` divisible NEP11 method on a
|
||||
// specified contract.
|
||||
func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID string) (int64, error) {
|
||||
return c.nepBalanceOf(tokenHash, owner, &tokenID)
|
||||
}
|
||||
|
||||
// Divisible NFT methods section end.
|
||||
|
||||
// Optional NFT methods section start.
|
||||
|
||||
// NEP11Properties invokes `properties` optional NEP11 method on a
|
||||
// specified contract.
|
||||
func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID string) (*stackitem.Map, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "properties", []smartcontract.Parameter{{
|
||||
Type: smartcontract.StringType,
|
||||
Value: tokenID,
|
||||
}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return topMapFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// Optional NFT methods section end.
|
|
@ -1,19 +1,15 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
|
@ -33,61 +29,22 @@ type SignerAccount struct {
|
|||
|
||||
// NEP17Decimals invokes `decimals` NEP17 method on a specified contract.
|
||||
func (c *Client) NEP17Decimals(tokenHash util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get NEP17 decimals: %w", err)
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
return c.nepDecimals(tokenHash)
|
||||
}
|
||||
|
||||
// NEP17Symbol invokes `symbol` NEP17 method on a specified contract.
|
||||
func (c *Client) NEP17Symbol(tokenHash util.Uint160) (string, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get NEP17 symbol: %w", err)
|
||||
}
|
||||
|
||||
return topStringFromStack(result.Stack)
|
||||
return c.nepSymbol(tokenHash)
|
||||
}
|
||||
|
||||
// NEP17TotalSupply invokes `totalSupply` NEP17 method on a specified contract.
|
||||
func (c *Client) NEP17TotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get NEP17 total supply: %w", err)
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
return c.nepTotalSupply(tokenHash)
|
||||
}
|
||||
|
||||
// NEP17BalanceOf invokes `balanceOf` NEP17 method on a specified contract.
|
||||
func (c *Client) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "balanceOf", []smartcontract.Parameter{{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: acc,
|
||||
}}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get NEP17 balance: %w", err)
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
return c.nepBalanceOf(tokenHash, acc, nil)
|
||||
}
|
||||
|
||||
// NEP17TokenInfo returns full NEP17 token info.
|
||||
|
@ -143,13 +100,9 @@ func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, reci
|
|||
if w.Err != nil {
|
||||
return nil, fmt.Errorf("failed to create transfer script: %w", w.Err)
|
||||
}
|
||||
accAddr, err := address.StringToUint160(acc.Address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad account address: %v", err)
|
||||
}
|
||||
return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, []SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: accAddr,
|
||||
Account: from,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc,
|
||||
|
@ -176,9 +129,6 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee,
|
|||
sysFee = result.GasConsumed
|
||||
}
|
||||
|
||||
if !c.initDone {
|
||||
return nil, errNetworkNotInitialized
|
||||
}
|
||||
tx := transaction.New(script, sysFee)
|
||||
tx.Signers = signers
|
||||
|
||||
|
@ -200,6 +150,10 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee,
|
|||
// using contract's number of decimals) to given account with data specified and
|
||||
// sends it to the network returning just a hash of it.
|
||||
func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas int64, data interface{}) (util.Uint256, error) {
|
||||
if !c.initDone {
|
||||
return util.Uint256{}, errNetworkNotInitialized
|
||||
}
|
||||
|
||||
tx, err := c.CreateNEP17TransferTx(acc, to, token, amount, gas, data)
|
||||
if err != nil {
|
||||
return util.Uint256{}, err
|
||||
|
@ -214,6 +168,10 @@ func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util.
|
|||
|
||||
// MultiTransferNEP17 is similar to TransferNEP17, buf allows to have multiple recipients.
|
||||
func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients []TransferTarget, data []interface{}) (util.Uint256, error) {
|
||||
if !c.initDone {
|
||||
return util.Uint256{}, errNetworkNotInitialized
|
||||
}
|
||||
|
||||
tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients, data)
|
||||
if err != nil {
|
||||
return util.Uint256{}, err
|
||||
|
@ -225,32 +183,3 @@ func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients [
|
|||
|
||||
return c.SendRawTransaction(tx)
|
||||
}
|
||||
|
||||
func topIntFromStack(st []stackitem.Item) (int64, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
bi, err := st[index].TryInteger()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return bi.Int64(), nil
|
||||
}
|
||||
|
||||
func topStringFromStack(st []stackitem.Item) (string, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
bs, err := st[index].TryBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
// getInvocationError returns an error in case of bad VM state or empty stack.
|
||||
func getInvocationError(result *result.Invoke) error {
|
||||
if result.State != "HALT" {
|
||||
return fmt.Errorf("invocation failed: %s", result.FaultException)
|
||||
}
|
||||
if len(result.Stack) == 0 {
|
||||
return errors.New("result stack is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// GetFeePerByte invokes `getFeePerByte` method on a native Policy contract.
|
||||
|
@ -80,13 +79,3 @@ func (c *Client) IsBlocked(hash util.Uint160) (bool, error) {
|
|||
}
|
||||
return topBoolFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// topBoolFromStack returns the top boolean value from stack
|
||||
func topBoolFromStack(st []stackitem.Item) (bool, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
result, ok := st[index].Value().(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("invalid stack item type: %s", st[index].Type())
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
|
||||
"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"
|
||||
|
@ -20,6 +21,7 @@ import (
|
|||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -640,3 +642,92 @@ func TestClient_GetNativeContracts(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, chain.GetNatives(), cs)
|
||||
}
|
||||
|
||||
func TestClient_NEP11(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||
defer chain.Close()
|
||||
defer rpcSrv.Shutdown()
|
||||
|
||||
c, err := client.New(context.Background(), httpSrv.URL, client.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
|
||||
h, err := chain.GetNativeContractScriptHash(nativenames.NameService)
|
||||
require.NoError(t, err)
|
||||
acc := testchain.PrivateKeyByID(0).GetScriptHash()
|
||||
|
||||
t.Run("Decimals", func(t *testing.T) {
|
||||
d, err := c.NEP11Decimals(h)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, d) // non-divisible
|
||||
})
|
||||
t.Run("TotalSupply", func(t *testing.T) {
|
||||
s, err := c.NEP11TotalSupply(h)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, s) // the only `neo.com` of acc0
|
||||
})
|
||||
t.Run("Symbol", func(t *testing.T) {
|
||||
sym, err := c.NEP11Symbol(h)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "NNS", sym)
|
||||
})
|
||||
t.Run("BalanceOf", func(t *testing.T) {
|
||||
b, err := c.NEP11BalanceOf(h, acc)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, b)
|
||||
})
|
||||
t.Run("OwnerOf", func(t *testing.T) {
|
||||
b, err := c.NEP11NDOwnerOf(h, "neo.com")
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, acc, b)
|
||||
})
|
||||
t.Run("Properties", func(t *testing.T) {
|
||||
p, err := c.NEP11Properties(h, "neo.com")
|
||||
require.NoError(t, err)
|
||||
blockRegisterDomain, err := chain.GetBlock(chain.GetHeaderHash(13)) // `neo.com` domain was registered in 13th block
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockRegisterDomain.Transactions))
|
||||
expected := stackitem.NewMap()
|
||||
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("neo.com")))
|
||||
expected.Add(stackitem.Make([]byte("expiration")), stackitem.Make(blockRegisterDomain.Timestamp/1000+365*24*3600)) // expiration formula
|
||||
require.EqualValues(t, expected, p)
|
||||
})
|
||||
t.Run("Transfer", func(t *testing.T) {
|
||||
_, err := c.TransferNEP11(wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)), testchain.PrivateKeyByID(1).GetScriptHash(), h, "neo.com", 0)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClient_NNS(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||
defer chain.Close()
|
||||
defer rpcSrv.Shutdown()
|
||||
|
||||
c, err := client.New(context.Background(), httpSrv.URL, client.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
|
||||
t.Run("NNSIsAvailable, false", func(t *testing.T) {
|
||||
b, err := c.NNSIsAvailable("neo.com")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, b)
|
||||
})
|
||||
t.Run("NNSIsAvailable, true", func(t *testing.T) {
|
||||
b, err := c.NNSIsAvailable("neogo.com")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, b)
|
||||
})
|
||||
t.Run("NNSResolve, good", func(t *testing.T) {
|
||||
b, err := c.NNSResolve("neo.com", nnsrecords.A)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1.2.3.4", b)
|
||||
})
|
||||
t.Run("NNSResolve, bad", func(t *testing.T) {
|
||||
_, err := c.NNSResolve("neogo.com", nnsrecords.A)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("NNSResolve, forbidden", func(t *testing.T) {
|
||||
_, err := c.NNSResolve("neogo.com", nnsrecords.CNAME)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ type rpcTestCase struct {
|
|||
}
|
||||
|
||||
const testContractHash = "c6ca2347bb84b99807221365c900ec069a265e7c"
|
||||
const deploymentTxHash = "fdd4f9252cde778010d14e9710efeeb80796fd38d778e9943c1d5bb2dc656c99"
|
||||
const deploymentTxHash = "26692315f71f4790263160c0f570828be77c6927493ae6657a6e5a6a09229eb9"
|
||||
const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c"
|
||||
|
||||
const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e"
|
||||
|
@ -654,7 +654,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
require.True(t, ok)
|
||||
expected := result.UnclaimedGas{
|
||||
Address: testchain.MultisigScriptHash(),
|
||||
Unclaimed: *big.NewInt(5000),
|
||||
Unclaimed: *big.NewInt(7000),
|
||||
}
|
||||
assert.Equal(t, expected, *actual)
|
||||
},
|
||||
|
@ -1415,7 +1415,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
require.NoErrorf(t, err, "could not parse response: %s", txOut)
|
||||
|
||||
assert.Equal(t, *block.Transactions[0], actual.Transaction)
|
||||
assert.Equal(t, 11, actual.Confirmations)
|
||||
assert.Equal(t, 15, actual.Confirmations)
|
||||
assert.Equal(t, TXHash, actual.Transaction.Hash())
|
||||
})
|
||||
|
||||
|
@ -1533,12 +1533,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
require.NoError(t, json.Unmarshal(res, actual))
|
||||
checkNep17TransfersAux(t, e, actual, sent, rcvd)
|
||||
}
|
||||
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{6, 7, 8, 9}, []int{1, 2}) })
|
||||
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{8, 9, 10, 11}, []int{2, 3}) })
|
||||
t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) })
|
||||
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{3, 4}, []int{0}) })
|
||||
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{6}, []int{1}) })
|
||||
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{5, 6}, []int{1}) })
|
||||
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{7, 8}, []int{2}) })
|
||||
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{5, 6}, []int{1}) })
|
||||
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{8}, []int{2}) })
|
||||
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{7, 8}, []int{2}) })
|
||||
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{9, 10}, []int{3}) })
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1639,8 +1639,8 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "68992456820",
|
||||
LastUpdated: 10,
|
||||
Amount: "67960042780",
|
||||
LastUpdated: 14,
|
||||
}},
|
||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||
}
|
||||
|
@ -1649,7 +1649,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
}
|
||||
|
||||
func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) {
|
||||
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []int{0, 1, 2, 3, 4, 5, 6})
|
||||
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []int{0, 1, 2, 3, 4, 5, 6, 7})
|
||||
}
|
||||
|
||||
func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) {
|
||||
|
@ -1658,6 +1658,19 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
|
|||
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(14)) // add type A record to `neo.com` domain via NNS
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockSetRecord.Transactions))
|
||||
txSetRecord := blockSetRecord.Transactions[0]
|
||||
|
||||
blockRegisterDomain, err := e.chain.GetBlock(e.chain.GetHeaderHash(13)) // register `neo.com` domain via NNS
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockRegisterDomain.Transactions))
|
||||
txRegisterDomain := blockRegisterDomain.Transactions[0]
|
||||
|
||||
blockGASBounty2, err := e.chain.GetBlock(e.chain.GetHeaderHash(12)) // size of committee = 6
|
||||
require.NoError(t, err)
|
||||
|
||||
blockDeploy3, err := e.chain.GetBlock(e.chain.GetHeaderHash(10)) // deploy verification_with_args_contract.go
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockDeploy3.Transactions))
|
||||
|
@ -1677,7 +1690,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockSendRubles.Transactions))
|
||||
txSendRubles := blockSendRubles.Transactions[0]
|
||||
blockGASBounty := blockSendRubles // index 6 = size of committee
|
||||
blockGASBounty1 := blockSendRubles // index 6 = size of committee
|
||||
|
||||
blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5))
|
||||
require.NoError(t, err)
|
||||
|
@ -1716,6 +1729,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
|
|||
// duplicate the Server method.
|
||||
expected := result.NEP17Transfers{
|
||||
Sent: []result.NEP17Transfer{
|
||||
{
|
||||
Timestamp: blockSetRecord.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "", // burn
|
||||
Amount: big.NewInt(txSetRecord.SystemFee + txSetRecord.NetworkFee).String(),
|
||||
Index: 14,
|
||||
TxHash: blockSetRecord.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockRegisterDomain.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "", // burn
|
||||
Amount: big.NewInt(txRegisterDomain.SystemFee + txRegisterDomain.NetworkFee).String(),
|
||||
Index: 13,
|
||||
TxHash: blockRegisterDomain.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockDeploy3.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
|
@ -1818,13 +1847,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
|
|||
},
|
||||
Received: []result.NEP17Transfer{
|
||||
{
|
||||
Timestamp: blockGASBounty.Timestamp,
|
||||
Timestamp: blockGASBounty2.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "",
|
||||
Amount: "50000000",
|
||||
Index: 12,
|
||||
NotifyIndex: 0,
|
||||
TxHash: blockGASBounty2.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockGASBounty1.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "",
|
||||
Amount: "50000000",
|
||||
Index: 6,
|
||||
NotifyIndex: 0,
|
||||
TxHash: blockGASBounty.Hash(),
|
||||
TxHash: blockGASBounty1.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockReceiveRubles.Timestamp,
|
||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -94,7 +94,7 @@ func Array(w *io.BinWriter, es ...interface{}) {
|
|||
Bool(w, e)
|
||||
default:
|
||||
if es[i] != nil {
|
||||
w.Err = errors.New("unsupported type")
|
||||
w.Err = fmt.Errorf("unsupported type: %v", e)
|
||||
return
|
||||
}
|
||||
Opcodes(w, opcode.PUSHNULL)
|
||||
|
|
Loading…
Reference in a new issue