forked from TrueCloudLab/neoneo-go
rpcclient: add new NEP-17 wrapper
This commit is contained in:
parent
aa80416632
commit
309358c85b
4 changed files with 417 additions and 0 deletions
111
pkg/rpcclient/nep17/nep17.go
Normal file
111
pkg/rpcclient/nep17/nep17.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
Package nep17 contains RPC wrappers to work with NEP-17 contracts.
|
||||||
|
|
||||||
|
Safe methods are encapsulated into TokenReader structure while Token provides
|
||||||
|
various methods to perform the only NEP-17 state-changing call, Transfer.
|
||||||
|
*/
|
||||||
|
package nep17
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neptoken"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Invoker is used by TokenReader to call various safe methods.
|
||||||
|
type Invoker interface {
|
||||||
|
neptoken.Invoker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actor is used by Token to create and send transactions.
|
||||||
|
type Actor interface {
|
||||||
|
Invoker
|
||||||
|
|
||||||
|
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||||
|
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||||
|
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenReader represents safe (read-only) methods of NEP-17 token. It can be
|
||||||
|
// used to query various data.
|
||||||
|
type TokenReader struct {
|
||||||
|
neptoken.Base
|
||||||
|
|
||||||
|
invoker Invoker
|
||||||
|
hash util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token provides full NEP-17 interface, both safe and state-changing methods.
|
||||||
|
type Token struct {
|
||||||
|
TokenReader
|
||||||
|
|
||||||
|
actor Actor
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferEvent represents a Transfer event as defined in the NEP-17 standard.
|
||||||
|
type TransferEvent struct {
|
||||||
|
From util.Uint160
|
||||||
|
To util.Uint160
|
||||||
|
Amount *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader creates an instance of TokenReader for contract with the given hash
|
||||||
|
// using the given Invoker.
|
||||||
|
func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
|
||||||
|
return &TokenReader{*neptoken.New(invoker, hash), invoker, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an instance of Token for contract with the given hash
|
||||||
|
// using the given Actor.
|
||||||
|
func New(actor Actor, hash util.Uint160) *Token {
|
||||||
|
return &Token{*NewReader(actor, hash), actor}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceOf returns the token balance of the given account.
|
||||||
|
func (t *TokenReader) BalanceOf(account util.Uint160) (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(t.invoker.Call(t.hash, "balanceOf", account))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer creates and sends a transaction that performs a `transfer` method
|
||||||
|
// call using the given parameters and checks for this call result, failing the
|
||||||
|
// transaction if it's not true. The returned values are transaction hash, its
|
||||||
|
// ValidUntilBlock value and an error if any.
|
||||||
|
func (t *Token) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error) {
|
||||||
|
script, err := t.transferScript(from, to, amount, data)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint256{}, 0, err
|
||||||
|
}
|
||||||
|
return t.actor.SendRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferTransaction creates a transaction that performs a `transfer` method
|
||||||
|
// call using the given parameters and checks for this call result, failing the
|
||||||
|
// transaction if it's not true. This transaction is signed, but not sent to the
|
||||||
|
// network, instead it's returned to the caller.
|
||||||
|
func (t *Token) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
|
||||||
|
script, err := t.transferScript(from, to, amount, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t.actor.MakeRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferUnsigned creates a transaction that performs a `transfer` method
|
||||||
|
// call using the given parameters and checks for this call result, failing the
|
||||||
|
// transaction if it's not true. This transaction is not signed and just returned
|
||||||
|
// to the caller.
|
||||||
|
func (t *Token) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
|
||||||
|
script, err := t.transferScript(from, to, amount, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t.actor.MakeUnsignedRun(script, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) transferScript(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) ([]byte, error) {
|
||||||
|
return smartcontract.CreateCallWithAssertScript(t.hash, "transfer", from, to, amount, data)
|
||||||
|
}
|
106
pkg/rpcclient/nep17/nep17_test.go
Normal file
106
pkg/rpcclient/nep17/nep17_test.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package nep17
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testAct struct {
|
||||||
|
err error
|
||||||
|
res *result.Invoke
|
||||||
|
tx *transaction.Transaction
|
||||||
|
txh util.Uint256
|
||||||
|
vub uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testAct) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||||
|
return t.res, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) MakeRun(script []byte) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) SendRun(script []byte) (util.Uint256, uint32, error) {
|
||||||
|
return t.txh, t.vub, t.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaderBalanceOf(t *testing.T) {
|
||||||
|
ta := new(testAct)
|
||||||
|
tr := NewReader(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err := tr.BalanceOf(util.Uint160{3, 2, 1})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make(100500),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bal, err := tr.BalanceOf(util.Uint160{3, 2, 1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, big.NewInt(100500), bal)
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = tr.BalanceOf(util.Uint160{3, 2, 1})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenTransfer(t *testing.T) {
|
||||||
|
ta := new(testAct)
|
||||||
|
tok := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, _, err := tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = util.Uint256{1, 2, 3}
|
||||||
|
ta.vub = 42
|
||||||
|
h, vub, err := tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, h)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
|
||||||
|
_, _, err = tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenTransferTransaction(t *testing.T) {
|
||||||
|
ta := new(testAct)
|
||||||
|
tok := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
for _, fun := range []func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error){
|
||||||
|
tok.TransferTransaction,
|
||||||
|
tok.TransferUnsigned,
|
||||||
|
} {
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
tx, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
|
_, err = fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
63
pkg/rpcclient/neptoken/base.go
Normal file
63
pkg/rpcclient/neptoken/base.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
Package neptoken contains RPC wrapper for common NEP-11 and NEP-17 methods.
|
||||||
|
|
||||||
|
All of these methods are safe, read-only.
|
||||||
|
*/
|
||||||
|
package neptoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxValidDecimals is the maximum value 'decimals' contract method can
|
||||||
|
// return to be considered as valid. It's log10(2^256), higher values
|
||||||
|
// don't make any sense on a VM with 256-bit integers. This restriction
|
||||||
|
// is not imposed by NEP-17 or NEP-11, but we do it as a sanity check
|
||||||
|
// anyway (and return plain int as a result).
|
||||||
|
MaxValidDecimals = 77
|
||||||
|
)
|
||||||
|
|
||||||
|
// Invoker is used by Base to call various methods.
|
||||||
|
type Invoker interface {
|
||||||
|
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base is a reader interface for common NEP-11 and NEP-17 methods built
|
||||||
|
// on top of Invoker.
|
||||||
|
type Base struct {
|
||||||
|
invoker Invoker
|
||||||
|
hash util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an instance of Base for contract with the given hash using the
|
||||||
|
// given invoker.
|
||||||
|
func New(invoker Invoker, hash util.Uint160) *Base {
|
||||||
|
return &Base{invoker, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimals implements `decimals` NEP-17 or NEP-11 method and returns the number
|
||||||
|
// of decimals used by token. For non-divisible NEP-11 tokens this method always
|
||||||
|
// returns zero. Values less than 0 or more than MaxValidDecimals are considered
|
||||||
|
// to be invalid (with an appropriate error) even if returned by the contract.
|
||||||
|
func (b *Base) Decimals() (int, error) {
|
||||||
|
r, err := b.invoker.Call(b.hash, "decimals")
|
||||||
|
dec, err := unwrap.LimitedInt64(r, err, 0, MaxValidDecimals)
|
||||||
|
return int(dec), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol implements `symbol` NEP-17 or NEP-11 method and returns a short token
|
||||||
|
// identifier (like "NEO" or "GAS").
|
||||||
|
func (b *Base) Symbol() (string, error) {
|
||||||
|
return unwrap.PrintableASCIIString(b.invoker.Call(b.hash, "symbol"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalSupply returns the total token supply currently available (the amount
|
||||||
|
// of minted tokens).
|
||||||
|
func (b *Base) TotalSupply() (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(b.invoker.Call(b.hash, "totalSupply"))
|
||||||
|
}
|
137
pkg/rpcclient/neptoken/base_test.go
Normal file
137
pkg/rpcclient/neptoken/base_test.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package neptoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testInv struct {
|
||||||
|
err error
|
||||||
|
res *result.Invoke
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testInv) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||||
|
return t.res, t.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBaseErrors(t *testing.T) {
|
||||||
|
ti := new(testInv)
|
||||||
|
base := New(ti, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
ti.err = errors.New("")
|
||||||
|
_, err := base.Decimals()
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = base.Symbol()
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = base.TotalSupply()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ti.err = nil
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "FAULT",
|
||||||
|
FaultException: "bad thing happened",
|
||||||
|
}
|
||||||
|
_, err = base.Decimals()
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = base.Symbol()
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = base.TotalSupply()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
}
|
||||||
|
_, err = base.Decimals()
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = base.Symbol()
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = base.TotalSupply()
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBaseDecimals(t *testing.T) {
|
||||||
|
ti := new(testInv)
|
||||||
|
base := New(ti, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make(0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dec, err := base.Decimals()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, dec)
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make(-1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = base.Decimals()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make(100500),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = base.Decimals()
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBaseSymbol(t *testing.T) {
|
||||||
|
ti := new(testInv)
|
||||||
|
base := New(ti, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make("SYM"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sym, err := base.Symbol()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "SYM", sym)
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make("\xff"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = base.Symbol()
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBaseTotalSupply(t *testing.T) {
|
||||||
|
ti := new(testInv)
|
||||||
|
base := New(ti, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make(100500),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ts, err := base.TotalSupply()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, big.NewInt(100500), ts)
|
||||||
|
|
||||||
|
ti.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = base.TotalSupply()
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
Loading…
Reference in a new issue