Merge pull request #2266 from nspcc-dev/nep-11-tracking

NEP-11 tracking
This commit is contained in:
Roman Khimov 2021-11-19 13:07:37 +03:00 committed by GitHub
commit aa06770b3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1343 additions and 534 deletions

View file

@ -294,7 +294,7 @@ Improvements:
StateRootInHeader option (#2028) StateRootInHeader option (#2028)
* cached GAS per vote value leads to substantially improved block processing * cached GAS per vote value leads to substantially improved block processing
speed (#2032) speed (#2032)
* failed native NEP17 transfers now still re-save the old value to the * failed native NEP-17 transfers now still re-save the old value to the
storage making state dumps compatible with C# (#2034) storage making state dumps compatible with C# (#2034)
* refactored and renamed some stackitem package functions, added proper error * refactored and renamed some stackitem package functions, added proper error
values in all cases (#2045) values in all cases (#2045)
@ -412,30 +412,30 @@ Bugs fixed:
## 0.95.0 "Sharpness" (17 May 2021) ## 0.95.0 "Sharpness" (17 May 2021)
This version mostly implements N3 RC2 protocol changes (and is fully This version mostly implements N3 RC2 protocol changes (and is fully
RC2-compatible), but also brings NEP11 CLI support and small improvements for RC2-compatible), but also brings NEP-11 CLI support and small improvements for
node operators. node operators.
Please note that this release is incompatible with 0.94.1 and there won't be Please note that this release is incompatible with 0.94.1 and there won't be
long-term support provided for it. long-term support provided for it.
New features: New features:
* CLI command for NEP17 transfers now accepts `data` parameter for the * CLI command for NEP-17 transfers now accepts `data` parameter for the
transfer (#1906) transfer (#1906)
* contract deployment CLI comand now also accepts `data` parameter for * contract deployment CLI comand now also accepts `data` parameter for
`_deploy` method (#1907) `_deploy` method (#1907)
* NEP11 and NEP17 transfers from CLI can now have multiple signers (#1914) * NEP-11 and NEP-17 transfers from CLI can now have multiple signers (#1914)
* `System.Runtime.BurnGas` interop was added to burn some GAS as well as * `System.Runtime.BurnGas` interop was added to burn some GAS as well as
`refuel` GAS contract method to add GAS to current execution environment `refuel` GAS contract method to add GAS to current execution environment
(#1937) (#1937)
* port number announced via P2P can now differ from actual port node is bound * port number announced via P2P can now differ from actual port node is bound
to if new option is used (#1942) to if new option is used (#1942)
* CLI now supports full set of NEP11 commands, including balance and * CLI now supports full set of NEP-11 commands, including balance and
transfers (#1918) transfers (#1918)
* string split, memory search and compare functions added to stdlib (#1943) * string split, memory search and compare functions added to stdlib (#1943)
* MaxValidUntilBlockIncrement can now be configured (#1963) * MaxValidUntilBlockIncrement can now be configured (#1963)
Behavior changes: Behavior changes:
* `data` parameter is now passed in a different way to NEP17 RPC client * `data` parameter is now passed in a different way to NEP-17 RPC client
methods (#1906) methods (#1906)
* default (used if nothing else specified) signer scope is now * default (used if nothing else specified) signer scope is now
`CalledByEntry` in CLI and RPC (#1909) `CalledByEntry` in CLI and RPC (#1909)
@ -454,7 +454,7 @@ Behavior changes:
* node will reread TLS certificates (if any configured) on SIGHUP (#1945) * node will reread TLS certificates (if any configured) on SIGHUP (#1945)
* contract trusts are now expressed with permission descriptors in manifest * contract trusts are now expressed with permission descriptors in manifest
(#1946) (#1946)
* NEP11 transfers now also support `data` parameter (#1950) * NEP-11 transfers now also support `data` parameter (#1950)
* N3 RC2 testnet magic differs from N2 RC1 testnet (#1951, #1954) * N3 RC2 testnet magic differs from N2 RC1 testnet (#1951, #1954)
* stdlib encoding/decoding methods now only accept inputs no longer than 1024 * stdlib encoding/decoding methods now only accept inputs no longer than 1024
bytes (#1943) bytes (#1943)
@ -485,7 +485,7 @@ Improvements:
Bugs fixed: Bugs fixed:
* `getproof` RPC request returned successful results in some cases where it * `getproof` RPC request returned successful results in some cases where it
should fail should fail
* `Transfer` events with invalid numbers were not rejected by NEP17 tracking * `Transfer` events with invalid numbers were not rejected by NEP-17 tracking
code (#1902) code (#1902)
* boolean function parameters were not accepted by `invokefunction` RPC call * boolean function parameters were not accepted by `invokefunction` RPC call
implementation (#1920) implementation (#1920)
@ -744,9 +744,9 @@ New features:
Behavior changes: Behavior changes:
* VM CLI now supports and requires manifests to properly work with NEF files * VM CLI now supports and requires manifests to properly work with NEF files
(#1642) (#1642)
* NEP17-related CLI commands now output GAS balance as floating point numbers * NEP-17-related CLI commands now output GAS balance as floating point numbers
(#1654) (#1654)
* `--from` parameter can be omitted now for NEP17 CLI transfers, the default * `--from` parameter can be omitted now for NEP-17 CLI transfers, the default
wallet address will be used in this case (#1655) wallet address will be used in this case (#1655)
* native contracts now use more specific types for methods arguments (#1657) * native contracts now use more specific types for methods arguments (#1657)
* some native contract names and IDs have changed (#1622, #1660) * some native contract names and IDs have changed (#1622, #1660)

View file

@ -53,7 +53,7 @@ func TestNEP11Import(t *testing.T) {
// already exists // already exists
e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...) e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...)
// not a NEP11 token // not a NEP-11 token
e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...) e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...)
t.Run("Info", func(t *testing.T) { t.Run("Info", func(t *testing.T) {
@ -283,7 +283,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
checkBalanceResult(t, nftOwnerAddr, "1") // tokenID1 checkBalanceResult(t, nftOwnerAddr, "1") // tokenID1
// transfer: good, to NEP11-Payable contract, with data // transfer: good, to NEP-11-Payable contract, with data
verifyH := deployVerifyContract(t, e) verifyH := deployVerifyContract(t, e)
cmdTransfer = []string{ cmdTransfer = []string{
"neo-go", "wallet", "nep11", "transfer", "neo-go", "wallet", "nep11", "transfer",

View file

@ -321,7 +321,7 @@ func TestNEP17ImportToken(t *testing.T) {
"--wallet", walletPath, "--wallet", walletPath,
"--token", address.Uint160ToString(neoContractHash)) // try address instead of sh "--token", address.Uint160ToString(neoContractHash)) // try address instead of sh
// not a NEP17 token // not a NEP-17 token
e.RunWithError(t, "neo-go", "wallet", "nep17", "import", e.RunWithError(t, "neo-go", "wallet", "nep17", "import",
"--rpc-endpoint", "http://"+e.RPC.Addr, "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", walletPath, "--wallet", walletPath,

View file

@ -12,8 +12,8 @@ func Verify() bool {
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
} }
// OnNEP11Payment notifies about NEP11 payment. You don't call this method directly, // OnNEP11Payment notifies about NEP-11 payment. You don't call this method directly,
// instead it's called by NEP11 contract when you transfer funds from your address // instead it's called by NEP-11 contract when you transfer funds from your address
// to the address of this NFT contract. // to the address of this NFT contract.
func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data interface{}) { func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data interface{}) {
runtime.Notify("OnNEP11Payment", from, amount, token, data) runtime.Notify("OnNEP11Payment", from, amount, token, data)

View file

@ -52,14 +52,14 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "import", Name: "import",
Usage: "import NEP11 token to a wallet", Usage: "import NEP-11 token to a wallet",
UsageText: "import --wallet <path> --rpc-endpoint <node> --timeout <time> --token <hash>", UsageText: "import --wallet <path> --rpc-endpoint <node> --timeout <time> --token <hash>",
Action: importNEP11Token, Action: importNEP11Token,
Flags: importFlags, Flags: importFlags,
}, },
{ {
Name: "info", Name: "info",
Usage: "print imported NEP11 token info", Usage: "print imported NEP-11 token info",
UsageText: "print --wallet <path> [--token <hash-or-name>]", UsageText: "print --wallet <path> [--token <hash-or-name>]",
Action: printNEP11Info, Action: printNEP11Info,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -69,7 +69,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "remove", Name: "remove",
Usage: "remove NEP11 token from the wallet", Usage: "remove NEP-11 token from the wallet",
UsageText: "remove --wallet <path> --token <hash-or-name>", UsageText: "remove --wallet <path> --token <hash-or-name>",
Action: removeNEP11Token, Action: removeNEP11Token,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -80,13 +80,13 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "transfer", Name: "transfer",
Usage: "transfer NEP11 tokens", Usage: "transfer NEP-11 tokens",
UsageText: "transfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]", UsageText: "transfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
Action: transferNEP11, Action: transferNEP11,
Flags: transferFlags, Flags: transferFlags,
Description: `Transfers specified NEP11 token with optional cosigners list attached to Description: `Transfers specified NEP-11 token with optional cosigners list attached to
the transfer. Amount should be specified for divisible NEP11 the transfer. Amount should be specified for divisible NEP-11
tokens and omitted for non-divisible NEP11 tokens. See tokens and omitted for non-divisible NEP-11 tokens. See
'contract testinvokefunction' documentation for the details 'contract testinvokefunction' documentation for the details
about cosigners syntax. If no cosigners are given then the about cosigners syntax. If no cosigners are given then the
sender with CalledByEntry scope will be used as the only sender with CalledByEntry scope will be used as the only
@ -95,7 +95,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "properties", Name: "properties",
Usage: "print properties of NEP11 token", Usage: "print properties of NEP-11 token",
UsageText: "properties --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>", UsageText: "properties --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>",
Action: printNEP11Properties, Action: printNEP11Properties,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
@ -105,7 +105,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "ownerOf", Name: "ownerOf",
Usage: "print owner of non-divisible NEP11 token with the specified ID", Usage: "print owner of non-divisible NEP-11 token with the specified ID",
UsageText: "ownerOf --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>", UsageText: "ownerOf --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>",
Action: printNEP11Owner, Action: printNEP11Owner,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
@ -301,7 +301,7 @@ func printNEP11Owner(ctx *cli.Context) error {
result, err := c.NEP11NDOwnerOf(tokenHash.Uint160(), tokenID) result, err := c.NEP11NDOwnerOf(tokenHash.Uint160(), tokenID)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP11 `ownerOf` method: %s", err.Error()), 1) return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `ownerOf` method: %s", err.Error()), 1)
} }
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(result)) fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(result))
@ -330,7 +330,7 @@ func printNEP11TokensOf(ctx *cli.Context) error {
result, err := c.NEP11TokensOf(tokenHash.Uint160(), acc.Uint160()) result, err := c.NEP11TokensOf(tokenHash.Uint160(), acc.Uint160())
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP11 `tokensOf` method: %s", err.Error()), 1) return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `tokensOf` method: %s", err.Error()), 1)
} }
for i := range result { for i := range result {
@ -356,7 +356,7 @@ func printNEP11Tokens(ctx *cli.Context) error {
result, err := c.NEP11Tokens(tokenHash.Uint160()) result, err := c.NEP11Tokens(tokenHash.Uint160())
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call optional NEP11 `tokens` method: %s", err.Error()), 1) return cli.NewExitError(fmt.Sprintf("failed to call optional NEP-11 `tokens` method: %s", err.Error()), 1)
} }
for i := range result { for i := range result {
@ -387,7 +387,7 @@ func printNEP11Properties(ctx *cli.Context) error {
result, err := c.NEP11Properties(tokenHash.Uint160(), tokenID) result, err := c.NEP11Properties(tokenHash.Uint160(), tokenID)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP11 `properties` method: %s", err.Error()), 1) return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1)
} }
bytes, err := stackitem.ToJSON(result) bytes, err := stackitem.ToJSON(result)

View file

@ -89,14 +89,14 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "import", Name: "import",
Usage: "import NEP17 token to a wallet", Usage: "import NEP-17 token to a wallet",
UsageText: "import --wallet <path> --rpc-endpoint <node> --timeout <time> --token <hash>", UsageText: "import --wallet <path> --rpc-endpoint <node> --timeout <time> --token <hash>",
Action: importNEP17Token, Action: importNEP17Token,
Flags: importFlags, Flags: importFlags,
}, },
{ {
Name: "info", Name: "info",
Usage: "print imported NEP17 token info", Usage: "print imported NEP-17 token info",
UsageText: "print --wallet <path> [--token <hash-or-name>]", UsageText: "print --wallet <path> [--token <hash-or-name>]",
Action: printNEP17Info, Action: printNEP17Info,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -106,7 +106,7 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "remove", Name: "remove",
Usage: "remove NEP17 token from the wallet", Usage: "remove NEP-17 token from the wallet",
UsageText: "remove --wallet <path> --token <hash-or-name>", UsageText: "remove --wallet <path> --token <hash-or-name>",
Action: removeNEP17Token, Action: removeNEP17Token,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -117,11 +117,11 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "transfer", Name: "transfer",
Usage: "transfer NEP17 tokens", Usage: "transfer NEP-17 tokens",
UsageText: "transfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]", UsageText: "transfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
Action: transferNEP17, Action: transferNEP17,
Flags: transferFlags, Flags: transferFlags,
Description: `Transfers specified NEP17 token amount with optional 'data' parameter and cosigners Description: `Transfers specified NEP-17 token amount with optional 'data' parameter and cosigners
list attached to the transfer. See 'contract testinvokefunction' documentation list attached to the transfer. See 'contract testinvokefunction' documentation
for the details about 'data' parameter and cosigners syntax. If no 'data' is for the details about 'data' parameter and cosigners syntax. If no 'data' is
given then default nil value will be used. If no cosigners are given then the given then default nil value will be used. If no cosigners are given then the
@ -130,7 +130,7 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "multitransfer", Name: "multitransfer",
Usage: "transfer NEP17 tokens to multiple recipients", Usage: "transfer NEP-17 tokens to multiple recipients",
UsageText: `multitransfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr>` + UsageText: `multitransfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr>` +
` <token1>:<addr1>:<amount1> [<token2>:<addr2>:<amount2> [...]] [-- <cosigner1:Scope> [<cosigner2> [...]]]`, ` <token1>:<addr1>:<amount1> [<token2>:<addr2>:<amount2> [...]] [-- <cosigner1:Scope> [<cosigner2> [...]]]`,
Action: multiTransferNEP17, Action: multiTransferNEP17,
@ -478,7 +478,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
} }
cosignersAccounts, err := cmdargs.GetSignersAccounts(wall, cosigners) cosignersAccounts, err := cmdargs.GetSignersAccounts(wall, cosigners)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create NEP17 multitransfer transaction: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to create NEP-17 multitransfer transaction: %w", err), 1)
} }
return signAndSendNEP17Transfer(ctx, c, acc, recipients, cosignersAccounts) return signAndSendNEP17Transfer(ctx, c, acc, recipients, cosignersAccounts)
@ -537,7 +537,7 @@ func transferNEP(ctx *cli.Context, standard string) error {
} }
cosignersAccounts, err := cmdargs.GetSignersAccounts(wall, cosigners) cosignersAccounts, err := cmdargs.GetSignersAccounts(wall, cosigners)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create NEP17 transfer transaction: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to create NEP-17 transfer transaction: %w", err), 1)
} }
amountArg := ctx.String("amount") amountArg := ctx.String("amount")

View file

@ -231,12 +231,12 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "nep17", Name: "nep17",
Usage: "work with NEP17 contracts", Usage: "work with NEP-17 contracts",
Subcommands: newNEP17Commands(), Subcommands: newNEP17Commands(),
}, },
{ {
Name: "nep11", Name: "nep11",
Usage: "work with NEP11 contracts", Usage: "work with NEP-11 contracts",
Subcommands: newNEP11Commands(), Subcommands: newNEP11Commands(),
}, },
{ {

View file

@ -464,7 +464,7 @@ To print token IDs owned by the specified owner, use `tokensOf` command with
#### Owner Of #### Owner Of
For non-divisible NEP-11 tokens only. To print owner of non-divisible NEP11 token For non-divisible NEP-11 tokens only. To print owner of non-divisible NEP-11 token
use `ownerOf` method, specify token hash via `--token` flag and token ID via use `ownerOf` method, specify token hash via `--token` flag and token ID via
`--id` flag: `--id` flag:

View file

@ -134,6 +134,7 @@ RPC:
MaxGasInvoke: 50 MaxGasInvoke: 50
MaxIteratorResultItems: 100 MaxIteratorResultItems: 100
MaxFindResultItems: 100 MaxFindResultItems: 100
MaxNEP11Tokens: 100
Port: 10332 Port: 10332
TLSConfig: TLSConfig:
Address: "" Address: ""
@ -154,6 +155,8 @@ where:
`n`, only `n` iterations are returned and truncated is true, indicating that `n`, only `n` iterations are returned and truncated is true, indicating that
there is still data to be returned. there is still data to be returned.
- `MaxFindResultItems` - the maximum number of elements for `findstates` response. - `MaxFindResultItems` - the maximum number of elements for `findstates` response.
- `MaxNEP11Tokens` - limit for the number of tokens returned from
`getnep11balances` call.
- `Port` is an RPC server port it should be bound to. - `Port` is an RPC server port it should be bound to.
- `TLS` section configures TLS protocol. - `TLS` section configures TLS protocol.

View file

@ -48,6 +48,9 @@ which would yield the response:
| `getconnectioncount` | | `getconnectioncount` |
| `getcontractstate` | | `getcontractstate` |
| `getnativecontracts` | | `getnativecontracts` |
| `getnep11balances` |
| `getnep11properties` |
| `getnep11transfers` |
| `getnep17balances` | | `getnep17balances` |
| `getnep17transfers` | | `getnep17transfers` |
| `getnextblockvalidators` | | `getnextblockvalidators` |
@ -107,28 +110,32 @@ This method doesn't work for the Ledger contract, you can get data via regular
the native contract by its name (case-insensitive), unlike the C# node where the native contract by its name (case-insensitive), unlike the C# node where
it only possible for index or hash. it only possible for index or hash.
#### `getnep17balances` #### `getnep11balances` and `getnep17balances`
neo-go's implementation of `getnep11balances` and `getnep17balances` does not
perform tracking of NEP-11 and NEP-17 balances for each account as it is done
in the C# node. Instead, neo-go node maintains the list of standard-compliant
contracts, i.e. those contracts that have `NEP-11` or `NEP-17` declared in the
supported standards section of the manifest. Each time balances are queried,
neo-go node asks every NEP-11/NEP-17 contract for the account balance by
invoking `balanceOf` method with the corresponding args. Invocation GAS limit
is set to be 3 GAS. All non-zero balances are included in the RPC call result.
neo-go's implementation of `getnep17balances` does not perform tracking of NEP17 Thus, if token contract doesn't have proper standard declared in the list of
balances for each account as it is done in the C# node. Instead, neo-go node supported standards but emits compliant NEP-11/NEP-17 `Transfer`
maintains the list of NEP17-compliant contracts, i.e. those contracts that have notifications, the token balance won't be shown in the list of balances
`NEP-17` declared in the supported standards section of the manifest. Each time returned by the neo-go node (unlike the C# node behavior). However, transfer
`getnep17balances` is queried, neo-go node asks every NEP17 contract for the logs of such tokens are still available via respective `getnepXXtransfers` RPC
account balance by invoking `balanceOf` method with the corresponding args. calls.
Invocation GAS limit is set to be 3 GAS. All non-zero NEP17 balances are included
in the RPC call result.
Thus, if NEP17 token contract doesn't have `NEP-17` standard declared in the list
of supported standards but emits proper NEP17 `Transfer` notifications, the token
balance won't be shown in the list of NEP17 balances returned by the neo-go node
(unlike the C# node behavior). However, transfer logs of such token are still
available via `getnep17transfers` RPC call.
The behaviour of the `LastUpdatedBlock` tracking for archival nodes as far as for The behaviour of the `LastUpdatedBlock` tracking for archival nodes as far as for
governing token balances matches the C# node's one. For non-archival nodes and governing token balances matches the C# node's one. For non-archival nodes and
other NEP17-compliant tokens if transfer's `LastUpdatedBlock` is lower than the other NEP-11/NEP-17 tokens if transfer's `LastUpdatedBlock` is lower than the
latest state synchronization point P the node working against, then latest state synchronization point P the node working against, then
`LastUpdatedBlock` equals P. `LastUpdatedBlock` equals P. For NEP-11 NFTs `LastUpdatedBlock` is equal for
all tokens of the same asset.
#### `getnep11transfers` and `getnep17transfers`
`transfernotifyindex` is not tracked by NeoGo, thus this field is always zero.
### Unsupported methods ### Unsupported methods
@ -166,12 +173,12 @@ burned).
This method can be used on P2P Notary enabled networks to submit new notary This method can be used on P2P Notary enabled networks to submit new notary
payloads to be relayed from RPC to P2P. payloads to be relayed from RPC to P2P.
#### Limits and paging for getnep17transfers #### Limits and paging for getnep11transfers and getnep17transfers
`getnep17transfers` RPC call never returns more than 1000 results for one `getnep11transfers` and `getnep17transfers` RPC calls never return more than
request (within specified time frame). You can pass your own limit via an 1000 results for one request (within specified time frame). You can pass your
additional parameter and then use paging to request the next batch of own limit via an additional parameter and then use paging to request the next
transfers. batch of transfers.
Example requesting 10 events for address NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc Example requesting 10 events for address NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc
within 0-1600094189000 timestamps: within 0-1600094189000 timestamps:

View file

@ -31,7 +31,7 @@ See the table below for the detailed examples description.
| [runtime](runtime) | This contract demonstrates how to use special `_initialize` and `_deploy` methods. See the [compiler documentation](../docs/compiler.md#vm-api-interop-layer ) for methods details. It also shows the pattern for checking owner witness inside the contract with the help of `runtime.CheckWitness` interop [function](../pkg/interop/runtime/runtime.go). | | [runtime](runtime) | This contract demonstrates how to use special `_initialize` and `_deploy` methods. See the [compiler documentation](../docs/compiler.md#vm-api-interop-layer ) for methods details. It also shows the pattern for checking owner witness inside the contract with the help of `runtime.CheckWitness` interop [function](../pkg/interop/runtime/runtime.go). |
| [storage](storage) | The contract implements API for basic operations with a contract storage. It shows hos to use `storage` interop package. See the `storage` [package documentation](../pkg/interop/storage/storage.go). | | [storage](storage) | The contract implements API for basic operations with a contract storage. It shows hos to use `storage` interop package. See the `storage` [package documentation](../pkg/interop/storage/storage.go). |
| [timer](timer) | The idea of the contract is to count `tick` method invocations and destroy itself after the third invocation. It shows how to use `contract.Call` interop function to call, update (migrate) and destroy the contract. Please, refer to the `contract.Call` [function documentation](../pkg/interop/contract/contract.go) | | [timer](timer) | The idea of the contract is to count `tick` method invocations and destroy itself after the third invocation. It shows how to use `contract.Call` interop function to call, update (migrate) and destroy the contract. Please, refer to the `contract.Call` [function documentation](../pkg/interop/contract/contract.go) |
| [token](token) | This contract implements NEP17 token standard (like NEO and GAS tokens) with all required methods and operations. See the NEP17 token standard [specification](https://github.com/neo-project/proposals/pull/126) for details. | | [token](token) | This contract implements NEP-17 token standard (like NEO and GAS tokens) with all required methods and operations. See the NEP-17 token standard [specification](https://github.com/neo-project/proposals/pull/126) for details. |
| [token-sale](token-sale) | The contract represents a token with `allowance`. It means that the token owner should approve token withdrawing before the transfer. The contract demonstrates how interop packages can be combined to work together. | | [token-sale](token-sale) | The contract represents a token with `allowance`. It means that the token owner should approve token withdrawing before the transfer. The contract demonstrates how interop packages can be combined to work together. |
## Compile ## Compile

View file

@ -1,5 +1,5 @@
/* /*
Package nns contains non-divisible non-fungible NEP11-compatible token Package nns contains non-divisible non-fungible NEP-11-compatible token
implementation. This token is a compatible analogue of C# Neo Name Service implementation. This token is a compatible analogue of C# Neo Name Service
token and is aimed to serve as a domain name service for Neo smart-contracts, token and is aimed to serve as a domain name service for Neo smart-contracts,
thus it's NeoNameService. This token can be minted with new domain name thus it's NeoNameService. This token can be minted with new domain name

View file

@ -1,5 +1,5 @@
/* /*
Package nft contains non-divisible non-fungible NEP11-compatible token Package nft contains non-divisible non-fungible NEP-11-compatible token
implementation. This token can be minted with GAS transfer to contract address, implementation. This token can be minted with GAS transfer to contract address,
it will hash some data (including data provided in transfer) and produce it will hash some data (including data provided in transfer) and produce
base64-encoded string that is your NFT. Since it's based on hashing and basically base64-encoded string that is your NFT. Since it's based on hashing and basically

View file

@ -276,13 +276,23 @@ func (chain *FakeChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
panic("TODO") panic("TODO")
} }
// GetNEP17Contracts implements Blockchainer interface.
func (chain *FakeChain) GetNEP11Contracts() []util.Uint160 {
panic("TODO")
}
// GetNEP17Contracts implements Blockchainer interface. // GetNEP17Contracts implements Blockchainer interface.
func (chain *FakeChain) GetNEP17Contracts() []util.Uint160 { func (chain *FakeChain) GetNEP17Contracts() []util.Uint160 {
panic("TODO") panic("TODO")
} }
// GetNEP17LastUpdated implements Blockchainer interface. // GetNEP17LastUpdated implements Blockchainer interface.
func (chain *FakeChain) GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) { func (chain *FakeChain) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error) {
panic("TODO")
}
// ForEachNEP17Transfer implements Blockchainer interface.
func (chain *FakeChain) ForEachNEP11Transfer(util.Uint160, func(*state.NEP11Transfer) (bool, error)) error {
panic("TODO") panic("TODO")
} }

View file

@ -53,6 +53,7 @@ func LoadFile(configPath string) (Config, error) {
RPC: rpc.Config{ RPC: rpc.Config{
MaxIteratorResultItems: 100, MaxIteratorResultItems: 100,
MaxFindResultItems: 100, MaxFindResultItems: 100,
MaxNEP11Tokens: 100,
}, },
}, },
} }

View file

@ -45,7 +45,7 @@ import (
// Tuning parameters. // Tuning parameters.
const ( const (
headerBatchCount = 2000 headerBatchCount = 2000
version = "0.1.4" version = "0.1.5"
defaultInitialGAS = 52000000_00000000 defaultInitialGAS = 52000000_00000000
defaultMemPoolSize = 50000 defaultMemPoolSize = 50000
@ -194,8 +194,9 @@ type bcEvent struct {
// transferData is used for transfer caching during storeBlock. // transferData is used for transfer caching during storeBlock.
type transferData struct { type transferData struct {
Info state.NEP17TransferInfo Info state.TokenTransferInfo
Log state.NEP17TransferLog Log11 state.TokenTransferLog
Log17 state.TokenTransferLog
} }
// NewBlockchain returns a new blockchain object the will use the // NewBlockchain returns a new blockchain object the will use the
@ -540,7 +541,7 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateJumpStage) error
if err != nil { if err != nil {
return fmt.Errorf("failed to remove outdated state data for the genesis block: %w", err) return fmt.Errorf("failed to remove outdated state data for the genesis block: %w", err)
} }
// TODO: remove NEP17 transfers and NEP17 transfer info for genesis block, #2096 related. // TODO: remove NEP-17 transfers and NEP-17 transfer info for genesis block, #2096 related.
_, err = cache.Persist() _, err = cache.Persist()
if err != nil { if err != nil {
return fmt.Errorf("failed to drop genesis block state: %w", err) return fmt.Errorf("failed to drop genesis block state: %w", err)
@ -1004,15 +1005,24 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
return return
} }
for acc, trData := range transCache { for acc, trData := range transCache {
err = kvcache.PutNEP17TransferInfo(acc, &trData.Info) err = kvcache.PutTokenTransferInfo(acc, &trData.Info)
if err != nil { if err != nil {
aerdone <- err aerdone <- err
return return
} }
err = kvcache.PutNEP17TransferLog(acc, trData.Info.NextTransferBatch, &trData.Log) if !trData.Info.NewNEP11Batch {
if err != nil { err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP11Batch, true, &trData.Log11)
aerdone <- err if err != nil {
return aerdone <- err
return
}
}
if !trData.Info.NewNEP17Batch {
err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP17Batch, false, &trData.Log17)
if err != nil {
aerdone <- err
return
}
} }
} }
close(aerdone) close(aerdone)
@ -1246,7 +1256,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d dao.DA
return return
} }
arr, ok := note.Item.Value().([]stackitem.Item) arr, ok := note.Item.Value().([]stackitem.Item)
if !ok || len(arr) != 3 { if !ok || !(len(arr) == 3 || len(arr) == 4) {
return return
} }
from, err := parseUint160(arr[0]) from, err := parseUint160(arr[0])
@ -1261,7 +1271,14 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d dao.DA
if err != nil { if err != nil {
return return
} }
bc.processNEP17Transfer(d, transCache, h, b, note.ScriptHash, from, to, amount) var id []byte
if len(arr) == 4 {
id, err = arr[3].TryBytes()
if err != nil || len(id) > storage.MaxStorageKeyLen {
return
}
}
bc.processTokenTransfer(d, transCache, h, b, note.ScriptHash, from, to, amount, id)
} }
func parseUint160(itm stackitem.Item) (util.Uint160, error) { func parseUint160(itm stackitem.Item) (util.Uint160, error) {
@ -1276,8 +1293,9 @@ func parseUint160(itm stackitem.Item) (util.Uint160, error) {
return util.Uint160DecodeBytesBE(bytes) return util.Uint160DecodeBytesBE(bytes)
} }
func (bc *Blockchain) processNEP17Transfer(cache dao.DAO, transCache map[util.Uint160]transferData, func (bc *Blockchain) processTokenTransfer(cache dao.DAO, transCache map[util.Uint160]transferData,
h util.Uint256, b *block.Block, sc util.Uint160, from util.Uint160, to util.Uint160, amount *big.Int) { h util.Uint256, b *block.Block, sc util.Uint160, from util.Uint160, to util.Uint160,
amount *big.Int, tokenID []byte) {
var id int32 var id int32
nativeContract := bc.contracts.ByHash(sc) nativeContract := bc.contracts.ByHash(sc)
if nativeContract != nil { if nativeContract != nil {
@ -1289,72 +1307,115 @@ func (bc *Blockchain) processNEP17Transfer(cache dao.DAO, transCache map[util.Ui
} }
id = assetContract.ID id = assetContract.ID
} }
transfer := &state.NEP17Transfer{ var transfer io.Serializable
Asset: id, var nep17xfer *state.NEP17Transfer
From: from, var isNEP11 = (tokenID != nil)
To: to, if !isNEP11 {
Block: b.Index, nep17xfer = &state.NEP17Transfer{
Timestamp: b.Timestamp, Asset: id,
Tx: h, From: from,
To: to,
Block: b.Index,
Timestamp: b.Timestamp,
Tx: h,
}
transfer = nep17xfer
} else {
nep11xfer := &state.NEP11Transfer{
NEP17Transfer: state.NEP17Transfer{
Asset: id,
From: from,
To: to,
Block: b.Index,
Timestamp: b.Timestamp,
Tx: h,
},
ID: tokenID,
}
transfer = nep11xfer
nep17xfer = &nep11xfer.NEP17Transfer
} }
if !from.Equals(util.Uint160{}) { if !from.Equals(util.Uint160{}) {
_ = transfer.Amount.Neg(amount) // We already have the Int. _ = nep17xfer.Amount.Neg(amount) // We already have the Int.
if appendNEP17Transfer(cache, transCache, from, transfer) != nil { if appendTokenTransfer(cache, transCache, from, transfer, id, b.Index, isNEP11) != nil {
return return
} }
} }
if !to.Equals(util.Uint160{}) { if !to.Equals(util.Uint160{}) {
_ = transfer.Amount.Set(amount) // We already have the Int. _ = nep17xfer.Amount.Set(amount) // We already have the Int.
_ = appendNEP17Transfer(cache, transCache, to, transfer) // Nothing useful we can do. _ = appendTokenTransfer(cache, transCache, to, transfer, id, b.Index, isNEP11) // Nothing useful we can do.
} }
} }
func appendNEP17Transfer(cache dao.DAO, transCache map[util.Uint160]transferData, addr util.Uint160, transfer *state.NEP17Transfer) error { func appendTokenTransfer(cache dao.DAO, transCache map[util.Uint160]transferData, addr util.Uint160, transfer io.Serializable,
token int32, bIndex uint32, isNEP11 bool) error {
transferData, ok := transCache[addr] transferData, ok := transCache[addr]
if !ok { if !ok {
balances, err := cache.GetNEP17TransferInfo(addr) balances, err := cache.GetTokenTransferInfo(addr)
if err != nil { if err != nil {
return err return err
} }
if !balances.NewBatch { if !balances.NewNEP11Batch {
trLog, err := cache.GetNEP17TransferLog(addr, balances.NextTransferBatch) trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP11Batch, true)
if err != nil { if err != nil {
return err return err
} }
transferData.Log = *trLog transferData.Log11 = *trLog
}
if !balances.NewNEP17Batch {
trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP17Batch, false)
if err != nil {
return err
}
transferData.Log17 = *trLog
} }
transferData.Info = *balances transferData.Info = *balances
} }
err := transferData.Log.Append(transfer) var (
log *state.TokenTransferLog
newBatch *bool
nextBatch *uint32
)
if !isNEP11 {
log = &transferData.Log17
newBatch = &transferData.Info.NewNEP17Batch
nextBatch = &transferData.Info.NextNEP17Batch
} else {
log = &transferData.Log11
newBatch = &transferData.Info.NewNEP11Batch
nextBatch = &transferData.Info.NextNEP11Batch
}
err := log.Append(transfer)
if err != nil { if err != nil {
return err return err
} }
transferData.Info.LastUpdated[transfer.Asset] = transfer.Block transferData.Info.LastUpdated[token] = bIndex
transferData.Info.NewBatch = transferData.Log.Size() >= state.NEP17TransferBatchSize *newBatch = log.Size() >= state.TokenTransferBatchSize
if transferData.Info.NewBatch { if *newBatch {
err = cache.PutNEP17TransferLog(addr, transferData.Info.NextTransferBatch, &transferData.Log) err = cache.PutTokenTransferLog(addr, *nextBatch, isNEP11, log)
if err != nil { if err != nil {
return err return err
} }
transferData.Info.NextTransferBatch++ *nextBatch++
transferData.Log = state.NEP17TransferLog{} // Put makes a copy of it anyway.
log.Raw = log.Raw[:0]
} }
transCache[addr] = transferData transCache[addr] = transferData
return nil return nil
} }
// ForEachNEP17Transfer executes f for each nep17 transfer in log. // ForEachNEP17Transfer executes f for each NEP-17 transfer in log.
func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17Transfer) (bool, error)) error { func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17Transfer) (bool, error)) error {
balances, err := bc.dao.GetNEP17TransferInfo(acc) balances, err := bc.dao.GetTokenTransferInfo(acc)
if err != nil { if err != nil {
return nil return nil
} }
for i := int(balances.NextTransferBatch); i >= 0; i-- { for i := int(balances.NextNEP17Batch); i >= 0; i-- {
lg, err := bc.dao.GetNEP17TransferLog(acc, uint32(i)) lg, err := bc.dao.GetTokenTransferLog(acc, uint32(i), false)
if err != nil { if err != nil {
return nil return nil
} }
cont, err := lg.ForEach(f) cont, err := lg.ForEachNEP17(f)
if err != nil { if err != nil {
return err return err
} }
@ -1365,16 +1426,43 @@ func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17
return nil return nil
} }
// GetNEP17Contracts returns the list of deployed NEP17 contracts. // ForEachNEP11Transfer executes f for each NEP-11 transfer in log.
func (bc *Blockchain) ForEachNEP11Transfer(acc util.Uint160, f func(*state.NEP11Transfer) (bool, error)) error {
balances, err := bc.dao.GetTokenTransferInfo(acc)
if err != nil {
return nil
}
for i := int(balances.NextNEP11Batch); i >= 0; i-- {
lg, err := bc.dao.GetTokenTransferLog(acc, uint32(i), true)
if err != nil {
return nil
}
cont, err := lg.ForEachNEP11(f)
if err != nil {
return err
}
if !cont {
break
}
}
return nil
}
// GetNEP17Contracts returns the list of deployed NEP-17 contracts.
func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 { func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 {
return bc.contracts.Management.GetNEP17Contracts() return bc.contracts.Management.GetNEP17Contracts()
} }
// GetNEP17LastUpdated returns a set of contract ids with the corresponding last updated // GetNEP11Contracts returns the list of deployed NEP-11 contracts.
func (bc *Blockchain) GetNEP11Contracts() []util.Uint160 {
return bc.contracts.Management.GetNEP11Contracts()
}
// GetTokenLastUpdated returns a set of contract ids with the corresponding last updated
// block indexes. In case of an empty account, latest stored state synchronisation point // block indexes. In case of an empty account, latest stored state synchronisation point
// is returned under Math.MinInt32 key. // is returned under Math.MinInt32 key.
func (bc *Blockchain) GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) { func (bc *Blockchain) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error) {
info, err := bc.dao.GetNEP17TransferInfo(acc) info, err := bc.dao.GetTokenTransferInfo(acc)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -34,6 +34,7 @@ type Blockchainer interface {
GetContractScriptHash(id int32) (util.Uint160, error) GetContractScriptHash(id int32) (util.Uint160, error)
GetEnrollments() ([]state.Validator, error) GetEnrollments() ([]state.Validator, error)
GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32)
ForEachNEP11Transfer(util.Uint160, func(*state.NEP11Transfer) (bool, error)) error
ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (bool, error)) error ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (bool, error)) error
GetHeaderHash(int) util.Uint256 GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*block.Header, error) GetHeader(hash util.Uint256) (*block.Header, error)
@ -47,8 +48,9 @@ type Blockchainer interface {
GetNativeContractScriptHash(string) (util.Uint160, error) GetNativeContractScriptHash(string) (util.Uint160, error)
GetNatives() []state.NativeContract GetNatives() []state.NativeContract
GetNextBlockValidators() ([]*keys.PublicKey, error) GetNextBlockValidators() ([]*keys.PublicKey, error)
GetNEP11Contracts() []util.Uint160
GetNEP17Contracts() []util.Uint160 GetNEP17Contracts() []util.Uint160
GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error)
GetNotaryContractScriptHash() util.Uint160 GetNotaryContractScriptHash() util.Uint160
GetNotaryBalance(acc util.Uint160) *big.Int GetNotaryBalance(acc util.Uint160) *big.Int
GetPolicer() Policer GetPolicer() Policer

View file

@ -31,7 +31,6 @@ var (
// DAO is a data access object. // DAO is a data access object.
type DAO interface { type DAO interface {
AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error
AppendNEP17Transfer(acc util.Uint160, index uint32, isNew bool, tr *state.NEP17Transfer) (bool, error)
DeleteBlock(h util.Uint256, buf *io.BufBinWriter) error DeleteBlock(h util.Uint256, buf *io.BufBinWriter) error
DeleteContractID(id int32) error DeleteContractID(id int32) error
DeleteStorageItem(id int32, key []byte) error DeleteStorageItem(id int32, key []byte) error
@ -43,8 +42,8 @@ type DAO interface {
GetCurrentBlockHeight() (uint32, error) GetCurrentBlockHeight() (uint32, error)
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
GetHeaderHashes() ([]util.Uint256, error) GetHeaderHashes() ([]util.Uint256, error)
GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferInfo, error) GetTokenTransferInfo(acc util.Uint160) (*state.TokenTransferInfo, error)
GetNEP17TransferLog(acc util.Uint160, index uint32) (*state.NEP17TransferLog, error) GetTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool) (*state.TokenTransferLog, error)
GetStateSyncPoint() (uint32, error) GetStateSyncPoint() (uint32, error)
GetStateSyncCurrentBlockHeight() (uint32, error) GetStateSyncCurrentBlockHeight() (uint32, error)
GetStorageItem(id int32, key []byte) state.StorageItem GetStorageItem(id int32, key []byte) state.StorageItem
@ -58,8 +57,8 @@ type DAO interface {
PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error
PutContractID(id int32, hash util.Uint160) error PutContractID(id int32, hash util.Uint160) error
PutCurrentHeader(hashAndIndex []byte) error PutCurrentHeader(hashAndIndex []byte) error
PutNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo) error PutTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo) error
PutNEP17TransferLog(acc util.Uint160, index uint32, lg *state.NEP17TransferLog) error PutTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool, lg *state.TokenTransferLog) error
PutStateSyncPoint(p uint32) error PutStateSyncPoint(p uint32) error
PutStateSyncCurrentBlockHeight(h uint32) error PutStateSyncCurrentBlockHeight(h uint32) error
PutStorageItem(id int32, key []byte, si state.StorageItem) error PutStorageItem(id int32, key []byte, si state.StorageItem) error
@ -69,7 +68,7 @@ type DAO interface {
StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error
StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error
StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error
putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo, buf *io.BufBinWriter) error putTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo, buf *io.BufBinWriter) error
} }
// Simple is memCached wrapper around DB, simple DAO implementation. // Simple is memCached wrapper around DB, simple DAO implementation.
@ -150,12 +149,12 @@ func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) {
return *data, nil return *data, nil
} }
// -- start nep17 transfer info. // -- start NEP-17 transfer info.
// GetNEP17TransferInfo retrieves nep17 transfer info from the cache. // GetTokenTransferInfo retrieves NEP-17 transfer info from the cache.
func (dao *Simple) GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferInfo, error) { func (dao *Simple) GetTokenTransferInfo(acc util.Uint160) (*state.TokenTransferInfo, error) {
key := storage.AppendPrefix(storage.STNEP17TransferInfo, acc.BytesBE()) key := storage.AppendPrefix(storage.STTokenTransferInfo, acc.BytesBE())
bs := state.NewNEP17TransferInfo() bs := state.NewTokenTransferInfo()
err := dao.GetAndDecode(bs, key) err := dao.GetAndDecode(bs, key)
if err != nil && err != storage.ErrKeyNotFound { if err != nil && err != storage.ErrKeyNotFound {
return nil, err return nil, err
@ -163,66 +162,51 @@ func (dao *Simple) GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferI
return bs, nil return bs, nil
} }
// PutNEP17TransferInfo saves nep17 transfer info in the cache. // PutTokenTransferInfo saves NEP-17 transfer info in the cache.
func (dao *Simple) PutNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo) error { func (dao *Simple) PutTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo) error {
return dao.putNEP17TransferInfo(acc, bs, io.NewBufBinWriter()) return dao.putTokenTransferInfo(acc, bs, io.NewBufBinWriter())
} }
func (dao *Simple) putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo, buf *io.BufBinWriter) error { func (dao *Simple) putTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo, buf *io.BufBinWriter) error {
key := storage.AppendPrefix(storage.STNEP17TransferInfo, acc.BytesBE()) key := storage.AppendPrefix(storage.STTokenTransferInfo, acc.BytesBE())
return dao.putWithBuffer(bs, key, buf) return dao.putWithBuffer(bs, key, buf)
} }
// -- end nep17 transfer info. // -- end NEP-17 transfer info.
// -- start transfer log. // -- start transfer log.
func getNEP17TransferLogKey(acc util.Uint160, index uint32) []byte { func getTokenTransferLogKey(acc util.Uint160, index uint32, isNEP11 bool) []byte {
key := make([]byte, 1+util.Uint160Size+4) key := make([]byte, 1+util.Uint160Size+4)
key[0] = byte(storage.STNEP17Transfers) if isNEP11 {
key[0] = byte(storage.STNEP11Transfers)
} else {
key[0] = byte(storage.STNEP17Transfers)
}
copy(key[1:], acc.BytesBE()) copy(key[1:], acc.BytesBE())
binary.LittleEndian.PutUint32(key[util.Uint160Size:], index) binary.LittleEndian.PutUint32(key[util.Uint160Size:], index)
return key return key
} }
// GetNEP17TransferLog retrieves transfer log from the cache. // GetTokenTransferLog retrieves transfer log from the cache.
func (dao *Simple) GetNEP17TransferLog(acc util.Uint160, index uint32) (*state.NEP17TransferLog, error) { func (dao *Simple) GetTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool) (*state.TokenTransferLog, error) {
key := getNEP17TransferLogKey(acc, index) key := getTokenTransferLogKey(acc, index, isNEP11)
value, err := dao.Store.Get(key) value, err := dao.Store.Get(key)
if err != nil { if err != nil {
if err == storage.ErrKeyNotFound { if err == storage.ErrKeyNotFound {
return new(state.NEP17TransferLog), nil return new(state.TokenTransferLog), nil
} }
return nil, err return nil, err
} }
return &state.NEP17TransferLog{Raw: value}, nil return &state.TokenTransferLog{Raw: value}, nil
} }
// PutNEP17TransferLog saves given transfer log in the cache. // PutTokenTransferLog saves given transfer log in the cache.
func (dao *Simple) PutNEP17TransferLog(acc util.Uint160, index uint32, lg *state.NEP17TransferLog) error { func (dao *Simple) PutTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool, lg *state.TokenTransferLog) error {
key := getNEP17TransferLogKey(acc, index) key := getTokenTransferLogKey(acc, index, isNEP11)
return dao.Store.Put(key, lg.Raw) return dao.Store.Put(key, lg.Raw)
} }
// AppendNEP17Transfer appends a single NEP17 transfer to a log.
// First return value signalizes that log size has exceeded batch size.
func (dao *Simple) AppendNEP17Transfer(acc util.Uint160, index uint32, isNew bool, tr *state.NEP17Transfer) (bool, error) {
var lg *state.NEP17TransferLog
if isNew {
lg = new(state.NEP17TransferLog)
} else {
var err error
lg, err = dao.GetNEP17TransferLog(acc, index)
if err != nil {
return false, err
}
}
if err := lg.Append(tr); err != nil {
return false, err
}
return lg.Size() >= state.NEP17TransferBatchSize, dao.PutNEP17TransferLog(acc, index, lg)
}
// -- end transfer log. // -- end transfer log.
// -- start notification event. // -- start notification event.

View file

@ -512,7 +512,7 @@ func newNEP17TransferWithAssert(sc, from, to util.Uint160, amount int64, needAss
emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.Opcodes(w.BinWriter, opcode.ASSERT)
} }
if w.Err != nil { if w.Err != nil {
panic(fmt.Errorf("failed to create nep17 transfer transaction: %w", w.Err)) panic(fmt.Errorf("failed to create NEP-17 transfer transaction: %w", w.Err))
} }
script := w.Bytes() script := w.Bytes()

View file

@ -33,7 +33,9 @@ type Management struct {
mtx sync.RWMutex mtx sync.RWMutex
contracts map[util.Uint160]*state.Contract contracts map[util.Uint160]*state.Contract
// nep17 is a map of NEP17-compliant contracts which is updated with every PostPersist. // nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist.
nep11 map[util.Uint160]struct{}
// nep17 is a map of NEP-17-compliant contracts which is updated with every PostPersist.
nep17 map[util.Uint160]struct{} nep17 map[util.Uint160]struct{}
} }
@ -65,6 +67,7 @@ func newManagement() *Management {
var m = &Management{ var m = &Management{
ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID), ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID),
contracts: make(map[util.Uint160]*state.Contract), contracts: make(map[util.Uint160]*state.Contract),
nep11: make(map[util.Uint160]struct{}),
nep17: make(map[util.Uint160]struct{}), nep17: make(map[util.Uint160]struct{}),
} }
defer m.UpdateHash() defer m.UpdateHash()
@ -453,6 +456,18 @@ func (m *Management) Metadata() *interop.ContractMD {
return &m.ContractMD return &m.ContractMD
} }
// updateContractCache saves contract in the common and NEP-related caches. It's
// an internal method that must be called with m.mtx lock taken.
func (m *Management) updateContractCache(cs *state.Contract) {
m.contracts[cs.Hash] = cs
if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) {
m.nep11[cs.Hash] = struct{}{}
}
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[cs.Hash] = struct{}{}
}
}
// OnPersist implements Contract interface. // OnPersist implements Contract interface.
func (m *Management) OnPersist(ic *interop.Context) error { func (m *Management) OnPersist(ic *interop.Context) error {
for _, native := range ic.Natives { for _, native := range ic.Natives {
@ -473,10 +488,7 @@ func (m *Management) OnPersist(ic *interop.Context) error {
return fmt.Errorf("initializing %s native contract: %w", md.Name, err) return fmt.Errorf("initializing %s native contract: %w", md.Name, err)
} }
m.mtx.Lock() m.mtx.Lock()
m.contracts[md.Hash] = cs m.updateContractCache(cs)
if md.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[md.Hash] = struct{}{}
}
m.mtx.Unlock() m.mtx.Unlock()
} }
@ -497,10 +509,7 @@ func (m *Management) InitializeCache(d dao.DAO) error {
if initErr != nil { if initErr != nil {
return return
} }
m.contracts[cs.Hash] = cs m.updateContractCache(cs)
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[cs.Hash] = struct{}{}
}
}) })
return initErr return initErr
} }
@ -512,25 +521,34 @@ func (m *Management) PostPersist(ic *interop.Context) error {
if cs != nil { if cs != nil {
continue continue
} }
delete(m.nep11, h)
delete(m.nep17, h)
newCs, err := m.getContractFromDAO(ic.DAO, h) newCs, err := m.getContractFromDAO(ic.DAO, h)
if err != nil { if err != nil {
// Contract was destroyed. // Contract was destroyed.
delete(m.contracts, h) delete(m.contracts, h)
delete(m.nep17, h)
continue continue
} }
m.contracts[h] = newCs m.updateContractCache(newCs)
if newCs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[h] = struct{}{}
} else {
delete(m.nep17, h)
}
} }
m.mtx.Unlock() m.mtx.Unlock()
return nil return nil
} }
// GetNEP17Contracts returns hashes of all deployed contracts that support NEP17 standard. The list // GetNEP11Contracts returns hashes of all deployed contracts that support NEP-11 standard. The list
// is updated every PostPersist, so until PostPersist is called, the result for the previous block
// is returned.
func (m *Management) GetNEP11Contracts() []util.Uint160 {
m.mtx.RLock()
result := make([]util.Uint160, 0, len(m.nep11))
for h := range m.nep11 {
result = append(result, h)
}
m.mtx.RUnlock()
return result
}
// GetNEP17Contracts returns hashes of all deployed contracts that support NEP-17 standard. The list
// is updated every PostPersist, so until PostPersist is called, the result for the previous block // is updated every PostPersist, so until PostPersist is called, the result for the previous block
// is returned. // is returned.
func (m *Management) GetNEP17Contracts() []util.Uint160 { func (m *Management) GetNEP17Contracts() []util.Uint160 {

View file

@ -92,7 +92,7 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
require.Empty(t, mgmt.GetNEP17Contracts()) require.Empty(t, mgmt.GetNEP17Contracts())
// Deploy NEP17 contract // Deploy NEP-17 contract
script := []byte{byte(opcode.RET)} script := []byte{byte(opcode.RET)}
sender := util.Uint160{1, 2, 3} sender := util.Uint160{1, 2, 3}
ne, err := nef.NewFile(script) ne, err := nef.NewFile(script)
@ -107,7 +107,7 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
c1, err := mgmt.Deploy(d, sender, ne, manif) c1, err := mgmt.Deploy(d, sender, ne, manif)
require.NoError(t, err) require.NoError(t, err)
// PostPersist is not yet called, thus no NEP17 contracts are expected // PostPersist is not yet called, thus no NEP-17 contracts are expected
require.Empty(t, mgmt.GetNEP17Contracts()) require.Empty(t, mgmt.GetNEP17Contracts())
// Call PostPersist, check c1 contract hash is returned // Call PostPersist, check c1 contract hash is returned

View file

@ -15,7 +15,7 @@ func TestGAS_Roundtrip(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)
getUtilityTokenBalance := func(bc *Blockchain, acc util.Uint160) (*big.Int, uint32) { getUtilityTokenBalance := func(bc *Blockchain, acc util.Uint160) (*big.Int, uint32) {
lub, err := bc.GetNEP17LastUpdated(acc) lub, err := bc.GetTokenLastUpdated(acc)
require.NoError(t, err) require.NoError(t, err)
return bc.GetUtilityTokenBalance(acc), lub[bc.contracts.GAS.ID] return bc.GetUtilityTokenBalance(acc), lub[bc.contracts.GAS.ID]
} }

View file

@ -11,7 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
// NEP17Balance represents balance state of a NEP17-token. // NEP17Balance represents balance state of a NEP-17-token.
type NEP17Balance struct { type NEP17Balance struct {
Balance big.Int Balance big.Int
} }

View file

@ -1,154 +0,0 @@
package state
import (
"bytes"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// NEP17TransferBatchSize is the maximum number of entries for NEP17TransferLog.
const NEP17TransferBatchSize = 128
// NEP17TransferLog is a log of NEP17 token transfers for the specific command.
type NEP17TransferLog struct {
Raw []byte
}
// NEP17Transfer represents a single NEP17 Transfer event.
type NEP17Transfer struct {
// Asset is a NEP17 contract ID.
Asset int32
// Address is the address of the sender.
From util.Uint160
// To is the address of the receiver.
To util.Uint160
// Amount is the amount of tokens transferred.
// It is negative when tokens are sent and positive if they are received.
Amount big.Int
// Block is a number of block when the event occurred.
Block uint32
// Timestamp is the timestamp of the block where transfer occurred.
Timestamp uint64
// Tx is a hash the transaction.
Tx util.Uint256
}
// NEP17TransferInfo stores map of the NEP17 contract IDs to the balance's last updated
// block trackers along with information about NEP17 transfer batch.
type NEP17TransferInfo struct {
LastUpdated map[int32]uint32
// NextTransferBatch stores an index of the next transfer batch.
NextTransferBatch uint32
// NewBatch is true if batch with the `NextTransferBatch` index should be created.
NewBatch bool
}
// NewNEP17TransferInfo returns new NEP17TransferInfo.
func NewNEP17TransferInfo() *NEP17TransferInfo {
return &NEP17TransferInfo{
LastUpdated: make(map[int32]uint32),
}
}
// DecodeBinary implements io.Serializable interface.
func (bs *NEP17TransferInfo) DecodeBinary(r *io.BinReader) {
bs.NextTransferBatch = r.ReadU32LE()
bs.NewBatch = r.ReadBool()
lenBalances := r.ReadVarUint()
m := make(map[int32]uint32, lenBalances)
for i := 0; i < int(lenBalances); i++ {
key := int32(r.ReadU32LE())
m[key] = r.ReadU32LE()
}
bs.LastUpdated = m
}
// EncodeBinary implements io.Serializable interface.
func (bs *NEP17TransferInfo) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(bs.NextTransferBatch)
w.WriteBool(bs.NewBatch)
w.WriteVarUint(uint64(len(bs.LastUpdated)))
for k, v := range bs.LastUpdated {
w.WriteU32LE(uint32(k))
w.WriteU32LE(v)
}
}
// Append appends single transfer to a log.
func (lg *NEP17TransferLog) Append(tr *NEP17Transfer) error {
// The first entry, set up counter.
if len(lg.Raw) == 0 {
lg.Raw = append(lg.Raw, 0)
}
b := bytes.NewBuffer(lg.Raw)
w := io.NewBinWriterFromIO(b)
tr.EncodeBinary(w)
if w.Err != nil {
return w.Err
}
lg.Raw = b.Bytes()
lg.Raw[0]++
return nil
}
// ForEach iterates over transfer log returning on first error.
func (lg *NEP17TransferLog) ForEach(f func(*NEP17Transfer) (bool, error)) (bool, error) {
if lg == nil || len(lg.Raw) == 0 {
return true, nil
}
transfers := make([]NEP17Transfer, lg.Size())
r := io.NewBinReaderFromBuf(lg.Raw[1:])
for i := 0; i < lg.Size(); i++ {
transfers[i].DecodeBinary(r)
}
if r.Err != nil {
return false, r.Err
}
for i := len(transfers) - 1; i >= 0; i-- {
cont, err := f(&transfers[i])
if err != nil {
return false, err
}
if !cont {
return false, nil
}
}
return true, nil
}
// Size returns an amount of transfer written in log.
func (lg *NEP17TransferLog) Size() int {
if len(lg.Raw) == 0 {
return 0
}
return int(lg.Raw[0])
}
// EncodeBinary implements io.Serializable interface.
func (t *NEP17Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(uint32(t.Asset))
w.WriteBytes(t.Tx[:])
w.WriteBytes(t.From[:])
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block)
w.WriteU64LE(t.Timestamp)
amount := bigint.ToBytes(&t.Amount)
w.WriteVarBytes(amount)
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP17Transfer) DecodeBinary(r *io.BinReader) {
t.Asset = int32(r.ReadU32LE())
r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU64LE()
amount := r.ReadVarBytes(bigint.MaxBytesLen)
t.Amount = *bigint.FromBytes(amount)
}

View file

@ -1,84 +0,0 @@
package state
import (
"math/big"
"math/rand"
"testing"
"time"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestNEP17TransferLog_Append(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
expected := []*NEP17Transfer{
randomTransfer(r),
randomTransfer(r),
randomTransfer(r),
randomTransfer(r),
}
lg := new(NEP17TransferLog)
for _, tr := range expected {
require.NoError(t, lg.Append(tr))
}
require.Equal(t, len(expected), lg.Size())
i := len(expected) - 1
cont, err := lg.ForEach(func(tr *NEP17Transfer) (bool, error) {
require.Equal(t, expected[i], tr)
i--
return true, nil
})
require.NoError(t, err)
require.True(t, cont)
}
func BenchmarkNEP17TransferLog_Append(b *testing.B) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
ts := make([]*NEP17Transfer, NEP17TransferBatchSize)
for i := range ts {
ts[i] = randomTransfer(r)
}
lg := new(NEP17TransferLog)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, tr := range ts {
err := lg.Append(tr)
if err != nil {
b.FailNow()
}
}
}
}
func TestNEP17Transfer_DecodeBinary(t *testing.T) {
expected := &NEP17Transfer{
Asset: 123,
From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10},
Amount: *big.NewInt(42),
Block: 12345,
Timestamp: 54321,
Tx: util.Uint256{8, 5, 3},
}
testserdes.EncodeDecodeBinary(t, expected, new(NEP17Transfer))
}
func randomTransfer(r *rand.Rand) *NEP17Transfer {
return &NEP17Transfer{
Amount: *big.NewInt(int64(r.Uint64())),
Block: r.Uint32(),
Asset: int32(random.Int(10, 10000000)),
From: random.Uint160(),
To: random.Uint160(),
Tx: random.Uint256(),
}
}

204
pkg/core/state/tokens.go Normal file
View file

@ -0,0 +1,204 @@
package state
import (
"bytes"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// TokenTransferBatchSize is the maximum number of entries for TokenTransferLog.
const TokenTransferBatchSize = 128
// TokenTransferLog is a serialized log of token transfers.
type TokenTransferLog struct {
Raw []byte
}
// NEP17Transfer represents a single NEP-17 Transfer event.
type NEP17Transfer struct {
// Asset is a NEP-17 contract ID.
Asset int32
// Address is the address of the sender.
From util.Uint160
// To is the address of the receiver.
To util.Uint160
// Amount is the amount of tokens transferred.
// It is negative when tokens are sent and positive if they are received.
Amount big.Int
// Block is a number of block when the event occurred.
Block uint32
// Timestamp is the timestamp of the block where transfer occurred.
Timestamp uint64
// Tx is a hash the transaction.
Tx util.Uint256
}
// NEP11Transfer represents a single NEP-11 Transfer event.
type NEP11Transfer struct {
NEP17Transfer
// ID is a NEP-11 token ID.
ID []byte
}
// TokenTransferInfo stores map of the contract IDs to the balance's last updated
// block trackers along with information about NEP-17 and NEP-11 transfer batch.
type TokenTransferInfo struct {
LastUpdated map[int32]uint32
// NextNEP11Batch stores the index of the next NEP-17 transfer batch.
NextNEP11Batch uint32
// NextNEP17Batch stores the index of the next NEP-17 transfer batch.
NextNEP17Batch uint32
// NewNEP11Batch is true if batch with the `NextNEP11Batch` index should be created.
NewNEP11Batch bool
// NewNEP17Batch is true if batch with the `NextNEP17Batch` index should be created.
NewNEP17Batch bool
}
// NewTokenTransferInfo returns new TokenTransferInfo.
func NewTokenTransferInfo() *TokenTransferInfo {
return &TokenTransferInfo{
NewNEP11Batch: true,
NewNEP17Batch: true,
LastUpdated: make(map[int32]uint32),
}
}
// DecodeBinary implements io.Serializable interface.
func (bs *TokenTransferInfo) DecodeBinary(r *io.BinReader) {
bs.NextNEP11Batch = r.ReadU32LE()
bs.NextNEP17Batch = r.ReadU32LE()
bs.NewNEP11Batch = r.ReadBool()
bs.NewNEP17Batch = r.ReadBool()
lenBalances := r.ReadVarUint()
m := make(map[int32]uint32, lenBalances)
for i := 0; i < int(lenBalances); i++ {
key := int32(r.ReadU32LE())
m[key] = r.ReadU32LE()
}
bs.LastUpdated = m
}
// EncodeBinary implements io.Serializable interface.
func (bs *TokenTransferInfo) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(bs.NextNEP11Batch)
w.WriteU32LE(bs.NextNEP17Batch)
w.WriteBool(bs.NewNEP11Batch)
w.WriteBool(bs.NewNEP17Batch)
w.WriteVarUint(uint64(len(bs.LastUpdated)))
for k, v := range bs.LastUpdated {
w.WriteU32LE(uint32(k))
w.WriteU32LE(v)
}
}
// Append appends single transfer to a log.
func (lg *TokenTransferLog) Append(tr io.Serializable) error {
// The first entry, set up counter.
if len(lg.Raw) == 0 {
lg.Raw = append(lg.Raw, 0)
}
b := bytes.NewBuffer(lg.Raw)
w := io.NewBinWriterFromIO(b)
tr.EncodeBinary(w)
if w.Err != nil {
return w.Err
}
lg.Raw = b.Bytes()
lg.Raw[0]++
return nil
}
// ForEachNEP11 iterates over transfer log returning on first error.
func (lg *TokenTransferLog) ForEachNEP11(f func(*NEP11Transfer) (bool, error)) (bool, error) {
if lg == nil || len(lg.Raw) == 0 {
return true, nil
}
transfers := make([]NEP11Transfer, lg.Size())
r := io.NewBinReaderFromBuf(lg.Raw[1:])
for i := 0; i < lg.Size(); i++ {
transfers[i].DecodeBinary(r)
}
if r.Err != nil {
return false, r.Err
}
for i := len(transfers) - 1; i >= 0; i-- {
cont, err := f(&transfers[i])
if err != nil || !cont {
return false, err
}
}
return true, nil
}
// ForEachNEP17 iterates over transfer log returning on first error.
func (lg *TokenTransferLog) ForEachNEP17(f func(*NEP17Transfer) (bool, error)) (bool, error) {
if lg == nil || len(lg.Raw) == 0 {
return true, nil
}
transfers := make([]NEP17Transfer, lg.Size())
r := io.NewBinReaderFromBuf(lg.Raw[1:])
for i := 0; i < lg.Size(); i++ {
transfers[i].DecodeBinary(r)
}
if r.Err != nil {
return false, r.Err
}
for i := len(transfers) - 1; i >= 0; i-- {
cont, err := f(&transfers[i])
if err != nil || !cont {
return false, err
}
}
return true, nil
}
// Size returns an amount of transfer written in log.
func (lg *TokenTransferLog) Size() int {
if len(lg.Raw) == 0 {
return 0
}
return int(lg.Raw[0])
}
// EncodeBinary implements io.Serializable interface.
func (t *NEP17Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(uint32(t.Asset))
w.WriteBytes(t.Tx[:])
w.WriteBytes(t.From[:])
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block)
w.WriteU64LE(t.Timestamp)
amount := bigint.ToBytes(&t.Amount)
w.WriteVarBytes(amount)
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP17Transfer) DecodeBinary(r *io.BinReader) {
t.Asset = int32(r.ReadU32LE())
r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU64LE()
amount := r.ReadVarBytes(bigint.MaxBytesLen)
t.Amount = *bigint.FromBytes(amount)
}
// EncodeBinary implements io.Serializable interface.
func (t *NEP11Transfer) EncodeBinary(w *io.BinWriter) {
t.NEP17Transfer.EncodeBinary(w)
w.WriteVarBytes(t.ID)
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP11Transfer) DecodeBinary(r *io.BinReader) {
t.NEP17Transfer.DecodeBinary(r)
t.ID = r.ReadVarBytes(storage.MaxStorageKeyLen)
}

View file

@ -0,0 +1,141 @@
package state
import (
"math/big"
"math/rand"
"testing"
"time"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestTokenTransferLog_Append17(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
expected := []*NEP17Transfer{
random17Transfer(r),
random17Transfer(r),
random17Transfer(r),
random17Transfer(r),
}
lg := new(TokenTransferLog)
for _, tr := range expected {
require.NoError(t, lg.Append(tr))
}
require.Equal(t, len(expected), lg.Size())
i := len(expected) - 1
cont, err := lg.ForEachNEP17(func(tr *NEP17Transfer) (bool, error) {
require.Equal(t, expected[i], tr)
i--
return true, nil
})
require.NoError(t, err)
require.True(t, cont)
}
func TestTokenTransferLog_Append(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
expected := []*NEP11Transfer{
random11Transfer(r),
random11Transfer(r),
random11Transfer(r),
random11Transfer(r),
}
lg := new(TokenTransferLog)
for _, tr := range expected {
require.NoError(t, lg.Append(tr))
}
require.Equal(t, len(expected), lg.Size())
i := len(expected) - 1
cont, err := lg.ForEachNEP11(func(tr *NEP11Transfer) (bool, error) {
require.Equal(t, expected[i], tr)
i--
return true, nil
})
require.NoError(t, err)
require.True(t, cont)
}
func BenchmarkTokenTransferLog_Append(b *testing.B) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
ts := make([]*NEP17Transfer, TokenTransferBatchSize)
for i := range ts {
ts[i] = random17Transfer(r)
}
lg := new(TokenTransferLog)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, tr := range ts {
err := lg.Append(tr)
if err != nil {
b.FailNow()
}
}
}
}
func TestNEP17Transfer_DecodeBinary(t *testing.T) {
expected := &NEP17Transfer{
Asset: 123,
From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10},
Amount: *big.NewInt(42),
Block: 12345,
Timestamp: 54321,
Tx: util.Uint256{8, 5, 3},
}
testserdes.EncodeDecodeBinary(t, expected, new(NEP17Transfer))
}
func TestNEP11Transfer_DecodeBinary(t *testing.T) {
expected := &NEP11Transfer{
NEP17Transfer: NEP17Transfer{
Asset: 123,
From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10},
Amount: *big.NewInt(42),
Block: 12345,
Timestamp: 54321,
Tx: util.Uint256{8, 5, 3},
},
ID: []byte{42, 42, 42},
}
testserdes.EncodeDecodeBinary(t, expected, new(NEP11Transfer))
}
func random17Transfer(r *rand.Rand) *NEP17Transfer {
return &NEP17Transfer{
Amount: *big.NewInt(int64(r.Uint64())),
Block: r.Uint32(),
Asset: int32(random.Int(10, 10000000)),
From: random.Uint160(),
To: random.Uint160(),
Tx: random.Uint256(),
}
}
func random11Transfer(r *rand.Rand) *NEP11Transfer {
return &NEP11Transfer{
NEP17Transfer: NEP17Transfer{
Amount: *big.NewInt(int64(r.Uint64())),
Block: r.Uint32(),
Asset: int32(random.Int(10, 10000000)),
From: random.Uint160(),
To: random.Uint160(),
Tx: random.Uint256(),
},
ID: random.Uint256().BytesBE(),
}
}

View file

@ -20,8 +20,9 @@ const (
// STStorage prefix. Once state exchange process is completed, all items with // STStorage prefix. Once state exchange process is completed, all items with
// STStorage prefix will be replaced with STTempStorage-prefixed ones. // STStorage prefix will be replaced with STTempStorage-prefixed ones.
STTempStorage KeyPrefix = 0x71 STTempStorage KeyPrefix = 0x71
STNEP17Transfers KeyPrefix = 0x72 STNEP11Transfers KeyPrefix = 0x72
STNEP17TransferInfo KeyPrefix = 0x73 STNEP17Transfers KeyPrefix = 0x73
STTokenTransferInfo KeyPrefix = 0x74
IXHeaderHashList KeyPrefix = 0x80 IXHeaderHashList KeyPrefix = 0x80
SYSCurrentBlock KeyPrefix = 0xc0 SYSCurrentBlock KeyPrefix = 0xc0
SYSCurrentHeader KeyPrefix = 0xc1 SYSCurrentHeader KeyPrefix = 0xc1

View file

@ -29,6 +29,9 @@ Supported methods
getconnectioncount getconnectioncount
getcontractstate getcontractstate
getnativecontracts getnativecontracts
getnep11balances
getnep11properties
getnep11transfers
getnep17balances getnep17balances
getnep17transfers getnep17transfers
getpeers getpeers

View file

@ -16,33 +16,33 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
) )
// NEP11Decimals invokes `decimals` NEP11 method on a specified contract. // NEP11Decimals invokes `decimals` NEP-11 method on a specified contract.
func (c *Client) NEP11Decimals(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP11Decimals(tokenHash util.Uint160) (int64, error) {
return c.nepDecimals(tokenHash) return c.nepDecimals(tokenHash)
} }
// NEP11Symbol invokes `symbol` NEP11 method on a specified contract. // NEP11Symbol invokes `symbol` NEP-11 method on a specified contract.
func (c *Client) NEP11Symbol(tokenHash util.Uint160) (string, error) { func (c *Client) NEP11Symbol(tokenHash util.Uint160) (string, error) {
return c.nepSymbol(tokenHash) return c.nepSymbol(tokenHash)
} }
// NEP11TotalSupply invokes `totalSupply` NEP11 method on a specified contract. // NEP11TotalSupply invokes `totalSupply` NEP-11 method on a specified contract.
func (c *Client) NEP11TotalSupply(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP11TotalSupply(tokenHash util.Uint160) (int64, error) {
return c.nepTotalSupply(tokenHash) return c.nepTotalSupply(tokenHash)
} }
// NEP11BalanceOf invokes `balanceOf` NEP11 method on a specified contract. // NEP11BalanceOf invokes `balanceOf` NEP-11 method on a specified contract.
func (c *Client) NEP11BalanceOf(tokenHash, owner util.Uint160) (int64, error) { func (c *Client) NEP11BalanceOf(tokenHash, owner util.Uint160) (int64, error) {
return c.nepBalanceOf(tokenHash, owner, nil) return c.nepBalanceOf(tokenHash, owner, nil)
} }
// NEP11TokenInfo returns full NEP11 token info. // NEP11TokenInfo returns full NEP-11 token info.
func (c *Client) NEP11TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) { func (c *Client) NEP11TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
return c.nepTokenInfo(tokenHash, manifest.NEP11StandardName) return c.nepTokenInfo(tokenHash, manifest.NEP11StandardName)
} }
// TransferNEP11 creates an invocation transaction that invokes 'transfer' method // 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 // on a given token to move the whole NEP-11 token with the specified token ID to
// given account and sends it to the network returning just a hash of it. // given account and sends it to the network returning just a hash of it.
func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160, func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
tokenHash util.Uint160, tokenID string, data interface{}, gas int64, cosigners []SignerAccount) (util.Uint256, error) { tokenHash util.Uint160, tokenID string, data interface{}, gas int64, cosigners []SignerAccount) (util.Uint256, error) {
@ -59,7 +59,7 @@ func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
// CreateNEP11TransferTx creates an invocation transaction for the 'transfer' // CreateNEP11TransferTx creates an invocation transaction for the 'transfer'
// method of a given contract (token) to move the whole (or the specified amount // 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. // of) NEP-11 token with the specified token ID to given account and returns it.
// The returned transaction is not signed. CreateNEP11TransferTx is also a // The returned transaction is not signed. CreateNEP11TransferTx is also a
// helper for TransferNEP11 and TransferNEP11D. // helper for TransferNEP11 and TransferNEP11D.
// `args` for TransferNEP11: to util.Uint160, tokenID string, data interface{}; // `args` for TransferNEP11: to util.Uint160, tokenID string, data interface{};
@ -70,7 +70,7 @@ func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint1
emit.AppCall(w.BinWriter, tokenHash, "transfer", callflag.All, args...) emit.AppCall(w.BinWriter, tokenHash, "transfer", callflag.All, args...)
emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.Opcodes(w.BinWriter, opcode.ASSERT)
if w.Err != nil { if w.Err != nil {
return nil, fmt.Errorf("failed to create NEP11 transfer script: %w", w.Err) return nil, fmt.Errorf("failed to create NEP-11 transfer script: %w", w.Err)
} }
from, err := address.StringToUint160(acc.Address) from, err := address.StringToUint160(acc.Address)
if err != nil { if err != nil {
@ -114,7 +114,7 @@ func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) ([]st
// Non-divisible NFT methods section start. // Non-divisible NFT methods section start.
// NEP11NDOwnerOf invokes `ownerOf` non-devisible NEP11 method with the // NEP11NDOwnerOf invokes `ownerOf` non-devisible NEP-11 method with the
// specified token ID on a specified contract. // specified token ID on a specified contract.
func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID string) (util.Uint160, error) { func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID string) (util.Uint160, error) {
result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{ result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
@ -139,7 +139,7 @@ func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID string) (util.Ui
// Divisible NFT methods section start. // Divisible NFT methods section start.
// TransferNEP11D creates an invocation transaction that invokes 'transfer' // TransferNEP11D creates an invocation transaction that invokes 'transfer'
// method on a given token to move specified amount of divisible NEP11 assets // method on a given token to move specified amount of divisible NEP-11 assets
// (in FixedN format using contract's number of decimals) to given account and // (in FixedN format using contract's number of decimals) to given account and
// sends it to the network returning just a hash of it. // sends it to the network returning just a hash of it.
func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160, func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
@ -159,13 +159,13 @@ func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
return c.SignAndPushTx(tx, acc, cosigners) return c.SignAndPushTx(tx, acc, cosigners)
} }
// NEP11DBalanceOf invokes `balanceOf` divisible NEP11 method on a // NEP11DBalanceOf invokes `balanceOf` divisible NEP-11 method on a
// specified contract. // specified contract.
func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID string) (int64, error) { func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID string) (int64, error) {
return c.nepBalanceOf(tokenHash, owner, &tokenID) return c.nepBalanceOf(tokenHash, owner, &tokenID)
} }
// NEP11DOwnerOf returns list of the specified NEP11 divisible token owners. // NEP11DOwnerOf returns list of the specified NEP-11 divisible token owners.
func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID string) ([]util.Uint160, error) { func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID string) ([]util.Uint160, error) {
result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{ result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
{ {
@ -196,7 +196,7 @@ func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID string) ([]util.U
// Optional NFT methods section start. // Optional NFT methods section start.
// NEP11Properties invokes `properties` optional NEP11 method on a // NEP11Properties invokes `properties` optional NEP-11 method on a
// specified contract. // specified contract.
func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID string) (*stackitem.Map, error) { func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID string) (*stackitem.Map, error) {
result, err := c.InvokeFunction(tokenHash, "properties", []smartcontract.Parameter{{ result, err := c.InvokeFunction(tokenHash, "properties", []smartcontract.Parameter{{

View file

@ -29,33 +29,33 @@ type SignerAccount struct {
Account *wallet.Account Account *wallet.Account
} }
// NEP17Decimals invokes `decimals` NEP17 method on a specified contract. // NEP17Decimals invokes `decimals` NEP-17 method on a specified contract.
func (c *Client) NEP17Decimals(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP17Decimals(tokenHash util.Uint160) (int64, error) {
return c.nepDecimals(tokenHash) return c.nepDecimals(tokenHash)
} }
// NEP17Symbol invokes `symbol` NEP17 method on a specified contract. // NEP17Symbol invokes `symbol` NEP-17 method on a specified contract.
func (c *Client) NEP17Symbol(tokenHash util.Uint160) (string, error) { func (c *Client) NEP17Symbol(tokenHash util.Uint160) (string, error) {
return c.nepSymbol(tokenHash) return c.nepSymbol(tokenHash)
} }
// NEP17TotalSupply invokes `totalSupply` NEP17 method on a specified contract. // NEP17TotalSupply invokes `totalSupply` NEP-17 method on a specified contract.
func (c *Client) NEP17TotalSupply(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP17TotalSupply(tokenHash util.Uint160) (int64, error) {
return c.nepTotalSupply(tokenHash) return c.nepTotalSupply(tokenHash)
} }
// NEP17BalanceOf invokes `balanceOf` NEP17 method on a specified contract. // NEP17BalanceOf invokes `balanceOf` NEP-17 method on a specified contract.
func (c *Client) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) { func (c *Client) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) {
return c.nepBalanceOf(tokenHash, acc, nil) return c.nepBalanceOf(tokenHash, acc, nil)
} }
// NEP17TokenInfo returns full NEP17 token info. // NEP17TokenInfo returns full NEP-17 token info.
func (c *Client) NEP17TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) { func (c *Client) NEP17TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
return c.nepTokenInfo(tokenHash, manifest.NEP17StandardName) return c.nepTokenInfo(tokenHash, manifest.NEP17StandardName)
} }
// CreateNEP17TransferTx creates an invocation transaction for the 'transfer' // CreateNEP17TransferTx creates an invocation transaction for the 'transfer'
// method of a given contract (token) to move specified amount of NEP17 assets // method of a given contract (token) to move specified amount of NEP-17 assets
// (in FixedN format using contract's number of decimals) to given account and // (in FixedN format using contract's number of decimals) to given account and
// returns it. The returned transaction is not signed. // returns it. The returned transaction is not signed.
func (c *Client) CreateNEP17TransferTx(acc *wallet.Account, to util.Uint160, func (c *Client) CreateNEP17TransferTx(acc *wallet.Account, to util.Uint160,
@ -70,7 +70,7 @@ func (c *Client) CreateNEP17TransferTx(acc *wallet.Account, to util.Uint160,
} }
// CreateNEP17MultiTransferTx creates an invocation transaction for performing // CreateNEP17MultiTransferTx creates an invocation transaction for performing
// NEP17 transfers from a single sender to multiple recipients with the given // NEP-17 transfers from a single sender to multiple recipients with the given
// data and cosigners. Transaction's sender is included with the CalledByEntry // data and cosigners. Transaction's sender is included with the CalledByEntry
// scope by default. // scope by default.
func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64,
@ -134,7 +134,7 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee,
} }
// TransferNEP17 creates an invocation transaction that invokes 'transfer' method // TransferNEP17 creates an invocation transaction that invokes 'transfer' method
// on a given token to move specified amount of NEP17 assets (in FixedN format // on a given token to move specified amount of NEP-17 assets (in FixedN format
// using contract's number of decimals) to given account with data specified and // using contract's number of decimals) to given account with data specified and
// sends it to the network returning just a hash of it. Cosigners argument // sends it to the network returning just a hash of it. Cosigners argument
// specifies a set of the transaction cosigners (may be nil or may include sender) // specifies a set of the transaction cosigners (may be nil or may include sender)

View file

@ -1,6 +1,8 @@
package client package client
import ( import (
"encoding/base64"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
@ -267,6 +269,16 @@ func (c *Client) GetNativeContracts() ([]state.NativeContract, error) {
return resp, nil return resp, nil
} }
// GetNEP11Balances is a wrapper for getnep11balances RPC.
func (c *Client) GetNEP11Balances(address util.Uint160) (*result.NEP11Balances, error) {
params := request.NewRawParams(address.StringLE())
resp := new(result.NEP11Balances)
if err := c.performRequest("getnep11balances", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetNEP17Balances is a wrapper for getnep17balances RPC. // GetNEP17Balances is a wrapper for getnep17balances RPC.
func (c *Client) GetNEP17Balances(address util.Uint160) (*result.NEP17Balances, error) { func (c *Client) GetNEP17Balances(address util.Uint160) (*result.NEP17Balances, error) {
params := request.NewRawParams(address.StringLE()) params := request.NewRawParams(address.StringLE())
@ -277,13 +289,54 @@ func (c *Client) GetNEP17Balances(address util.Uint160) (*result.NEP17Balances,
return resp, nil return resp, nil
} }
// GetNEP17Transfers is a wrapper for getnep17transfers RPC. Address parameter // GetNEP11Properties is a wrapper for getnep11properties RPC. We recommend using
// is mandatory, while all the others are optional. Start and stop parameters // NEP11Properties method instead of this to receive and work with proper VM types,
// are supported since neo-go 0.77.0 and limit and page since neo-go 0.78.0. // this method is provided mostly for the sake of completeness. For well-known
// These parameters are positional in the JSON-RPC call, you can't specify limit // attributes like "description", "image", "name" and "tokenURI" it returns strings,
// and not specify start/stop for example. // while for all other ones []byte (which can be nil).
func (c *Client) GetNEP17Transfers(address string, start, stop *uint64, limit, page *int) (*result.NEP17Transfers, error) { func (c *Client) GetNEP11Properties(asset util.Uint160, token []byte) (map[string]interface{}, error) {
params := request.NewRawParams(address) params := request.NewRawParams(asset.StringLE(), hex.EncodeToString(token))
resp := make(map[string]interface{})
if err := c.performRequest("getnep11properties", params, &resp); err != nil {
return nil, err
}
for k, v := range resp {
if v == nil {
continue
}
str, ok := v.(string)
if !ok {
return nil, errors.New("value is not a string")
}
if result.KnownNEP11Properties[k] {
continue
}
val, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return nil, err
}
resp[k] = val
}
return resp, nil
}
// GetNEP11Transfers is a wrapper for getnep11transfers RPC. Address parameter
// is mandatory, while all the others are optional. Limit and page parameters are
// only supported by NeoGo servers and can only be specified with start and stop.
func (c *Client) GetNEP11Transfers(address util.Uint160, start, stop *uint64, limit, page *int) (*result.NEP11Transfers, error) {
params, err := packTransfersParams(address, start, stop, limit, page)
if err != nil {
return nil, err
}
resp := new(result.NEP11Transfers)
if err := c.performRequest("getnep11transfers", *params, resp); err != nil {
return nil, err
}
return resp, nil
}
func packTransfersParams(address util.Uint160, start, stop *uint64, limit, page *int) (*request.RawParams, error) {
params := request.NewRawParams(address.StringLE())
if start != nil { if start != nil {
params.Values = append(params.Values, *start) params.Values = append(params.Values, *start)
if stop != nil { if stop != nil {
@ -302,8 +355,21 @@ func (c *Client) GetNEP17Transfers(address string, start, stop *uint64, limit, p
} else if stop != nil || limit != nil || page != nil { } else if stop != nil || limit != nil || page != nil {
return nil, errors.New("bad parameters") return nil, errors.New("bad parameters")
} }
return &params, nil
}
// GetNEP17Transfers is a wrapper for getnep17transfers RPC. Address parameter
// is mandatory, while all the others are optional. Start and stop parameters
// are supported since neo-go 0.77.0 and limit and page since neo-go 0.78.0.
// These parameters are positional in the JSON-RPC call, you can't specify limit
// and not specify start/stop for example.
func (c *Client) GetNEP17Transfers(address util.Uint160, start, stop *uint64, limit, page *int) (*result.NEP17Transfers, error) {
params, err := packTransfersParams(address, start, stop, limit, page)
if err != nil {
return nil, err
}
resp := new(result.NEP17Transfers) resp := new(result.NEP17Transfers)
if err := c.performRequest("getnep17transfers", params, resp); err != nil { if err := c.performRequest("getnep17transfers", *params, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil

View file

@ -532,6 +532,36 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}, },
}, },
}, },
"getnep11balances": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint160DecodeStringLE("1aada0032aba1ef6d1f07bbd8bec1d85f5380fb3")
if err != nil {
panic(err)
}
return c.GetNEP11Balances(hash)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"balance":[{"assethash":"a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8","tokens":[{"tokenid":"abcdef","amount":"1","lastupdatedblock":251604}]}],"address":"NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe"}}`,
result: func(c *Client) interface{} {
hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8")
if err != nil {
panic(err)
}
return &result.NEP11Balances{
Balances: []result.NEP11AssetBalance{{
Asset: hash,
Tokens: []result.NEP11TokenBalance{{
ID: "abcdef",
Amount: "1",
LastUpdated: 251604,
}},
}},
Address: "NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe",
}
},
},
},
"getnep17balances": { "getnep17balances": {
{ {
name: "positive", name: "positive",
@ -559,13 +589,76 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}, },
}, },
}, },
"getnep11properties": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint160DecodeStringLE("1aada0032aba1ef6d1f07bbd8bec1d85f5380fb3")
if err != nil {
panic(err)
}
return c.GetNEP11Properties(hash, []byte("abcdef"))
}, // NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"name":"sometoken","field1":"c29tZXRoaW5n","field2":null}}`,
result: func(c *Client) interface{} {
return map[string]interface{}{
"name": "sometoken",
"field1": []byte("something"),
"field2": nil,
}
},
},
},
"getnep11transfers": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := address.StringToUint160("NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe")
if err != nil {
panic(err)
}
return c.GetNEP11Transfers(hash, nil, nil, nil, nil)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sent":[],"received":[{"timestamp":1555651816,"assethash":"600c4f5200db36177e3e8a09e9f18e2fc7d12a0f","transferaddress":"NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP","amount":"1","tokenid":"abcdef","blockindex":436036,"transfernotifyindex":0,"txhash":"df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58"}],"address":"NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe"}}`,
result: func(c *Client) interface{} {
assetHash, err := util.Uint160DecodeStringLE("600c4f5200db36177e3e8a09e9f18e2fc7d12a0f")
if err != nil {
panic(err)
}
txHash, err := util.Uint256DecodeStringLE("df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58")
if err != nil {
panic(err)
}
return &result.NEP11Transfers{
Sent: []result.NEP11Transfer{},
Received: []result.NEP11Transfer{
{
Timestamp: 1555651816,
Asset: assetHash,
Address: "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP",
Amount: "1",
ID: "abcdef",
Index: 436036,
NotifyIndex: 0,
TxHash: txHash,
},
},
Address: "NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe",
}
},
},
},
"getnep17transfers": { "getnep17transfers": {
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetNEP17Transfers("AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF", nil, nil, nil, nil) hash, err := address.StringToUint160("NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe")
if err != nil {
panic(err)
}
return c.GetNEP17Transfers(hash, nil, nil, nil, nil)
}, },
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sent":[],"received":[{"timestamp":1555651816,"assethash":"600c4f5200db36177e3e8a09e9f18e2fc7d12a0f","transferaddress":"AYwgBNMepiv5ocGcyNT4mA8zPLTQ8pDBis","amount":"1000000","blockindex":436036,"transfernotifyindex":0,"txhash":"df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58"}],"address":"AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF"}}`, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sent":[],"received":[{"timestamp":1555651816,"assethash":"600c4f5200db36177e3e8a09e9f18e2fc7d12a0f","transferaddress":"AYwgBNMepiv5ocGcyNT4mA8zPLTQ8pDBis","amount":"1000000","blockindex":436036,"transfernotifyindex":0,"txhash":"df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58"}],"address":"NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe"}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
assetHash, err := util.Uint160DecodeStringLE("600c4f5200db36177e3e8a09e9f18e2fc7d12a0f") assetHash, err := util.Uint160DecodeStringLE("600c4f5200db36177e3e8a09e9f18e2fc7d12a0f")
if err != nil { if err != nil {
@ -588,7 +681,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
TxHash: txHash, TxHash: txHash,
}, },
}, },
Address: "AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF", Address: "NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe",
} }
}, },
}, },
@ -1052,6 +1145,22 @@ type rpcClientErrorCase struct {
} }
var rpcClientErrorCases = map[string][]rpcClientErrorCase{ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
`{"jsonrpc":"2.0","id":1,"result":{"name":"name","bad":42}}`: {
{
name: "getnep11properties_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetNEP11Properties(util.Uint160{}, []byte{})
},
},
},
`{"jsonrpc":"2.0","id":1,"result":{"name":100500,"good":"c29tZXRoaW5n"}}`: {
{
name: "getnep11properties_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetNEP11Properties(util.Uint160{}, []byte{})
},
},
},
`{"jsonrpc":"2.0","id":1,"result":"not-a-hex-string"}`: { `{"jsonrpc":"2.0","id":1,"result":"not-a-hex-string"}`: {
{ {
name: "getblock_not_a_hex_response", name: "getblock_not_a_hex_response",
@ -1229,23 +1338,41 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
return c.GetContractStateByHash(util.Uint160{}) return c.GetContractStateByHash(util.Uint160{})
}, },
}, },
{
name: "getnep11balances_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetNEP11Balances(util.Uint160{})
},
},
{ {
name: "getnep17balances_invalid_params_error", name: "getnep17balances_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetNEP17Balances(util.Uint160{}) return c.GetNEP17Balances(util.Uint160{})
}, },
}, },
{
name: "getnep11properties_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetNEP11Properties(util.Uint160{}, []byte{})
},
},
{
name: "getnep11transfers_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetNEP11Transfers(util.Uint160{}, nil, nil, nil, nil)
},
},
{ {
name: "getnep17transfers_invalid_params_error", name: "getnep17transfers_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetNEP17Transfers("", nil, nil, nil, nil) return c.GetNEP17Transfers(util.Uint160{}, nil, nil, nil, nil)
}, },
}, },
{ {
name: "getnep17transfers_invalid_params_error 2", name: "getnep17transfers_invalid_params_error 2",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
var stop uint64 var stop uint64
return c.GetNEP17Transfers("Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn", nil, &stop, nil, nil) return c.GetNEP17Transfers(util.Uint160{}, nil, &stop, nil, nil)
}, },
}, },
{ {
@ -1253,7 +1380,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
var start uint64 var start uint64
var limit int var limit int
return c.GetNEP17Transfers("Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn", &start, nil, &limit, nil) return c.GetNEP17Transfers(util.Uint160{}, &start, nil, &limit, nil)
}, },
}, },
{ {
@ -1261,7 +1388,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
var start, stop uint64 var start, stop uint64
var page int var page int
return c.GetNEP17Transfers("Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn", &start, &stop, nil, &page) return c.GetNEP17Transfers(util.Uint160{}, &start, &stop, nil, &page)
}, },
}, },
{ {
@ -1416,16 +1543,28 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
return c.GetContractStateByHash(util.Uint160{}) return c.GetContractStateByHash(util.Uint160{})
}, },
}, },
{
name: "getnep11balances_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetNEP11Balances(util.Uint160{})
},
},
{ {
name: "getnep17balances_unmarshalling_error", name: "getnep17balances_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetNEP17Balances(util.Uint160{}) return c.GetNEP17Balances(util.Uint160{})
}, },
}, },
{
name: "getnep11transfers_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetNEP11Transfers(util.Uint160{}, nil, nil, nil, nil)
},
},
{ {
name: "getnep17transfers_unmarshalling_error", name: "getnep17transfers_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetNEP17Transfers("", nil, nil, nil, nil) return c.GetNEP17Transfers(util.Uint160{}, nil, nil, nil, nil)
}, },
}, },
{ {

View file

@ -1,36 +0,0 @@
package result
import (
"github.com/nspcc-dev/neo-go/pkg/util"
)
// NEP17Balances is a result for the getnep17balances RPC call.
type NEP17Balances struct {
Balances []NEP17Balance `json:"balance"`
Address string `json:"address"`
}
// NEP17Balance represents balance for the single token contract.
type NEP17Balance struct {
Asset util.Uint160 `json:"assethash"`
Amount string `json:"amount"`
LastUpdated uint32 `json:"lastupdatedblock"`
}
// NEP17Transfers is a result for the getnep17transfers RPC.
type NEP17Transfers struct {
Sent []NEP17Transfer `json:"sent"`
Received []NEP17Transfer `json:"received"`
Address string `json:"address"`
}
// NEP17Transfer represents single NEP17 transfer event.
type NEP17Transfer struct {
Timestamp uint64 `json:"timestamp"`
Asset util.Uint160 `json:"assethash"`
Address string `json:"transferaddress,omitempty"`
Amount string `json:"amount"`
Index uint32 `json:"blockindex"`
NotifyIndex uint32 `json:"transfernotifyindex"`
TxHash util.Uint256 `json:"txhash"`
}

View file

@ -0,0 +1,82 @@
package result
import (
"github.com/nspcc-dev/neo-go/pkg/util"
)
// NEP11Balances is a result for the getnep11balances RPC call.
type NEP11Balances struct {
Balances []NEP11AssetBalance `json:"balance"`
Address string `json:"address"`
}
// NEP11Balance is a structure holding balance of a NEP-11 asset.
type NEP11AssetBalance struct {
Asset util.Uint160 `json:"assethash"`
Tokens []NEP11TokenBalance `json:"tokens"`
}
// NEP11TokenBalance represents balance of a single NFT.
type NEP11TokenBalance struct {
ID string `json:"tokenid"`
Amount string `json:"amount"`
LastUpdated uint32 `json:"lastupdatedblock"`
}
// NEP17Balances is a result for the getnep17balances RPC call.
type NEP17Balances struct {
Balances []NEP17Balance `json:"balance"`
Address string `json:"address"`
}
// NEP17Balance represents balance for the single token contract.
type NEP17Balance struct {
Asset util.Uint160 `json:"assethash"`
Amount string `json:"amount"`
LastUpdated uint32 `json:"lastupdatedblock"`
}
// NEP11Transfers is a result for the getnep11transfers RPC.
type NEP11Transfers struct {
Sent []NEP11Transfer `json:"sent"`
Received []NEP11Transfer `json:"received"`
Address string `json:"address"`
}
// NEP11Transfer represents single NEP-11 transfer event.
type NEP11Transfer struct {
Timestamp uint64 `json:"timestamp"`
Asset util.Uint160 `json:"assethash"`
Address string `json:"transferaddress,omitempty"`
ID string `json:"tokenid"`
Amount string `json:"amount"`
Index uint32 `json:"blockindex"`
NotifyIndex uint32 `json:"transfernotifyindex"`
TxHash util.Uint256 `json:"txhash"`
}
// NEP17Transfers is a result for the getnep17transfers RPC.
type NEP17Transfers struct {
Sent []NEP17Transfer `json:"sent"`
Received []NEP17Transfer `json:"received"`
Address string `json:"address"`
}
// NEP17Transfer represents single NEP17 transfer event.
type NEP17Transfer struct {
Timestamp uint64 `json:"timestamp"`
Asset util.Uint160 `json:"assethash"`
Address string `json:"transferaddress,omitempty"`
Amount string `json:"amount"`
Index uint32 `json:"blockindex"`
NotifyIndex uint32 `json:"transfernotifyindex"`
TxHash util.Uint256 `json:"txhash"`
}
// KnownNEP11Properties contains a list of well-known NEP-11 token property names.
var KnownNEP11Properties = map[string]bool{
"description": true,
"image": true,
"name": true,
"tokenURI": true,
}

View file

@ -15,6 +15,7 @@ type (
MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"` MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"`
MaxIteratorResultItems int `yaml:"MaxIteratorResultItems"` MaxIteratorResultItems int `yaml:"MaxIteratorResultItems"`
MaxFindResultItems int `yaml:"MaxFindResultItems"` MaxFindResultItems int `yaml:"MaxFindResultItems"`
MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"`
Port uint16 `yaml:"Port"` Port uint16 `yaml:"Port"`
TLSConfig TLSConfig `yaml:"TLSConfig"` TLSConfig TLSConfig `yaml:"TLSConfig"`
} }

View file

@ -5,6 +5,7 @@ import (
"context" "context"
"crypto/elliptic" "crypto/elliptic"
"encoding/binary" "encoding/binary"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -22,10 +23,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
"github.com/nspcc-dev/neo-go/pkg/core/mpt" "github.com/nspcc-dev/neo-go/pkg/core/mpt"
"github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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/crypto/keys"
@ -115,6 +118,9 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
"getconnectioncount": (*Server).getConnectionCount, "getconnectioncount": (*Server).getConnectionCount,
"getcontractstate": (*Server).getContractState, "getcontractstate": (*Server).getContractState,
"getnativecontracts": (*Server).getNativeContracts, "getnativecontracts": (*Server).getNativeContracts,
"getnep11balances": (*Server).getNEP11Balances,
"getnep11properties": (*Server).getNEP11Properties,
"getnep11transfers": (*Server).getNEP11Transfers,
"getnep17balances": (*Server).getNEP17Balances, "getnep17balances": (*Server).getNEP17Balances,
"getnep17transfers": (*Server).getNEP17Transfers, "getnep17transfers": (*Server).getNEP17Transfers,
"getpeers": (*Server).getPeers, "getpeers": (*Server).getPeers,
@ -651,6 +657,141 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp
return result.NewApplicationLog(hash, appExecResults, trig), nil return result.NewApplicationLog(hash, appExecResults, trig), nil
} }
func (s *Server) getNEP11Tokens(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) ([]stackitem.Item, error) {
item, finalize, err := s.invokeReadOnly(bw, h, "tokensOf", acc)
if err != nil {
return nil, err
}
defer finalize()
if (item.Type() == stackitem.InteropT) && iterator.IsIterator(item) {
vals, _ := iterator.Values(item, s.config.MaxNEP11Tokens)
return vals, nil
}
return nil, fmt.Errorf("invalid `tokensOf` result type %s", item.String())
}
func (s *Server) getNEP11Balances(ps request.Params) (interface{}, *response.Error) {
u, err := ps.Value(0).GetUint160FromAddressOrHex()
if err != nil {
return nil, response.ErrInvalidParams
}
bs := &result.NEP11Balances{
Address: address.Uint160ToString(u),
Balances: []result.NEP11AssetBalance{},
}
lastUpdated, err := s.chain.GetTokenLastUpdated(u)
if err != nil {
return nil, response.NewRPCError("Failed to get NEP-11 last updated block", err.Error(), err)
}
var count int
stateSyncPoint := lastUpdated[math.MinInt32]
bw := io.NewBufBinWriter()
contract_loop:
for _, h := range s.chain.GetNEP11Contracts() {
toks, err := s.getNEP11Tokens(h, u, bw)
if err != nil {
continue
}
if len(toks) == 0 {
continue
}
cs := s.chain.GetContractState(h)
if cs == nil {
continue
}
isDivisible := (cs.Manifest.ABI.GetMethod("balanceOf", 2) != nil)
lub, ok := lastUpdated[cs.ID]
if !ok {
cfg := s.chain.GetConfig()
if !cfg.P2PStateExchangeExtensions && cfg.RemoveUntraceableBlocks {
return nil, response.NewInternalServerError(fmt.Sprintf("failed to get LastUpdatedBlock for balance of %s token", cs.Hash.StringLE()), nil)
}
lub = stateSyncPoint
}
bs.Balances = append(bs.Balances, result.NEP11AssetBalance{
Asset: h,
Tokens: make([]result.NEP11TokenBalance, 0, len(toks)),
})
curAsset := &bs.Balances[len(bs.Balances)-1]
for i := range toks {
id, err := toks[i].TryBytes()
if err != nil || len(id) > storage.MaxStorageKeyLen {
continue
}
var amount = "1"
if isDivisible {
balance, err := s.getTokenBalance(h, u, id, bw)
if err != nil {
continue
}
if balance.Sign() == 0 {
continue
}
amount = balance.String()
}
count++
curAsset.Tokens = append(curAsset.Tokens, result.NEP11TokenBalance{
ID: hex.EncodeToString(id),
Amount: amount,
LastUpdated: lub,
})
if count >= s.config.MaxNEP11Tokens {
break contract_loop
}
}
}
return bs, nil
}
func (s *Server) invokeNEP11Properties(h util.Uint160, id []byte, bw *io.BufBinWriter) ([]stackitem.MapElement, error) {
item, finalize, err := s.invokeReadOnly(bw, h, "properties", id)
if err != nil {
return nil, err
}
defer finalize()
if item.Type() != stackitem.MapT {
return nil, fmt.Errorf("invalid `properties` result type %s", item.String())
}
return item.Value().([]stackitem.MapElement), nil
}
func (s *Server) getNEP11Properties(ps request.Params) (interface{}, *response.Error) {
asset, err := ps.Value(0).GetUint160FromAddressOrHex()
if err != nil {
return nil, response.ErrInvalidParams
}
token, err := ps.Value(1).GetBytesHex()
if err != nil {
return nil, response.ErrInvalidParams
}
props, err := s.invokeNEP11Properties(asset, token, nil)
if err != nil {
return nil, response.NewRPCError("failed to get NEP-11 properties", err.Error(), err)
}
res := make(map[string]interface{})
for _, kv := range props {
key, err := kv.Key.TryBytes()
if err != nil {
continue
}
var val interface{}
if result.KnownNEP11Properties[string(key)] || kv.Value.Type() != stackitem.AnyT {
v, err := kv.Value.TryBytes()
if err != nil {
continue
}
if result.KnownNEP11Properties[string(key)] {
val = string(v)
} else {
val = v
}
}
res[string(key)] = val
}
return res, nil
}
func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Error) { func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Error) {
u, err := ps.Value(0).GetUint160FromAddressOrHex() u, err := ps.Value(0).GetUint160FromAddressOrHex()
if err != nil { if err != nil {
@ -661,14 +802,14 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
Address: address.Uint160ToString(u), Address: address.Uint160ToString(u),
Balances: []result.NEP17Balance{}, Balances: []result.NEP17Balance{},
} }
lastUpdated, err := s.chain.GetNEP17LastUpdated(u) lastUpdated, err := s.chain.GetTokenLastUpdated(u)
if err != nil { if err != nil {
return nil, response.NewRPCError("Failed to get NEP17 last updated block", err.Error(), err) return nil, response.NewRPCError("Failed to get NEP-17 last updated block", err.Error(), err)
} }
stateSyncPoint := lastUpdated[math.MinInt32] stateSyncPoint := lastUpdated[math.MinInt32]
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
for _, h := range s.chain.GetNEP17Contracts() { for _, h := range s.chain.GetNEP17Contracts() {
balance, err := s.getNEP17Balance(h, u, bw) balance, err := s.getTokenBalance(h, u, nil, bw)
if err != nil { if err != nil {
continue continue
} }
@ -696,30 +837,53 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
return bs, nil return bs, nil
} }
func (s *Server) getNEP17Balance(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) (*big.Int, error) { func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method string, params ...interface{}) (stackitem.Item, func(), error) {
if bw == nil { if bw == nil {
bw = io.NewBufBinWriter() bw = io.NewBufBinWriter()
} else { } else {
bw.Reset() bw.Reset()
} }
emit.AppCall(bw.BinWriter, h, "balanceOf", callflag.ReadStates, acc) emit.AppCall(bw.BinWriter, h, method, callflag.ReadStates|callflag.AllowCall, params...)
if bw.Err != nil { if bw.Err != nil {
return nil, fmt.Errorf("failed to create `balanceOf` invocation script: %w", bw.Err) return nil, nil, fmt.Errorf("failed to create `%s` invocation script: %w", method, bw.Err)
} }
script := bw.Bytes() script := bw.Bytes()
tx := &transaction.Transaction{Script: script} tx := &transaction.Transaction{Script: script}
v, finalize := s.chain.GetTestVM(trigger.Application, tx, nil) b, err := s.getFakeNextBlock()
defer finalize() if err != nil {
return nil, nil, err
}
v, finalize := s.chain.GetTestVM(trigger.Application, tx, b)
v.GasLimit = core.HeaderVerificationGasLimit v.GasLimit = core.HeaderVerificationGasLimit
v.LoadScriptWithFlags(script, callflag.All) v.LoadScriptWithFlags(script, callflag.All)
err := v.Run() err = v.Run()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to run `balanceOf` for %s: %w", h.StringLE(), err) finalize()
return nil, nil, fmt.Errorf("failed to run `%s` for %s: %w", method, h.StringLE(), err)
} }
if v.Estack().Len() != 1 { if v.Estack().Len() != 1 {
return nil, fmt.Errorf("invalid `balanceOf` return values count: expected 1, got %d", v.Estack().Len()) finalize()
return nil, nil, fmt.Errorf("invalid `%s` return values count: expected 1, got %d", method, v.Estack().Len())
} }
res, err := v.Estack().Pop().Item().TryInteger() return v.Estack().Pop().Item(), finalize, nil
}
func (s *Server) getTokenBalance(h util.Uint160, acc util.Uint160, id []byte, bw *io.BufBinWriter) (*big.Int, error) {
var (
item stackitem.Item
finalize func()
err error
)
if id == nil { // NEP-17 and NEP-11 generic.
item, finalize, err = s.invokeReadOnly(bw, h, "balanceOf", acc)
} else { // NEP-11 divisible.
item, finalize, err = s.invokeReadOnly(bw, h, "balanceOf", acc, id)
}
if err != nil {
return nil, err
}
finalize()
res, err := item.TryInteger()
if err != nil { if err != nil {
return nil, fmt.Errorf("unexpected `balanceOf` result type: %w", err) return nil, fmt.Errorf("unexpected `balanceOf` result type: %w", err)
} }
@ -776,7 +940,15 @@ func getTimestampsAndLimit(ps request.Params, index int) (uint64, uint64, int, i
return start, end, limit, page, nil return start, end, limit, page, nil
} }
func (s *Server) getNEP11Transfers(ps request.Params) (interface{}, *response.Error) {
return s.getTokenTransfers(ps, true)
}
func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Error) { func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Error) {
return s.getTokenTransfers(ps, false)
}
func (s *Server) getTokenTransfers(ps request.Params, isNEP11 bool) (interface{}, *response.Error) {
u, err := ps.Value(0).GetUint160FromAddressOrHex() u, err := ps.Value(0).GetUint160FromAddressOrHex()
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
@ -787,33 +959,37 @@ func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Er
return nil, response.NewInvalidParamsError(err.Error(), err) return nil, response.NewInvalidParamsError(err.Error(), err)
} }
bs := &result.NEP17Transfers{ bs := &tokenTransfers{
Address: address.Uint160ToString(u), Address: address.Uint160ToString(u),
Received: []result.NEP17Transfer{}, Received: []interface{}{},
Sent: []result.NEP17Transfer{}, Sent: []interface{}{},
} }
cache := make(map[int32]util.Uint160) cache := make(map[int32]util.Uint160)
var resCount, frameCount int var resCount, frameCount int
err = s.chain.ForEachNEP17Transfer(u, func(tr *state.NEP17Transfer) (bool, error) { // handleTransfer returns items to be added into received and sent arrays
// along with a continue flag and error.
var handleTransfer = func(tr *state.NEP17Transfer) (*result.NEP17Transfer, *result.NEP17Transfer, bool, error) {
var received, sent *result.NEP17Transfer
// Iterating from newest to oldest, not yet reached required // Iterating from newest to oldest, not yet reached required
// time frame, continue looping. // time frame, continue looping.
if tr.Timestamp > end { if tr.Timestamp > end {
return true, nil return nil, nil, true, nil
} }
// Iterating from newest to oldest, moved past required // Iterating from newest to oldest, moved past required
// time frame, stop looping. // time frame, stop looping.
if tr.Timestamp < start { if tr.Timestamp < start {
return false, nil return nil, nil, false, nil
} }
frameCount++ frameCount++
// Using limits, not yet reached required page. // Using limits, not yet reached required page.
if limit != 0 && page*limit >= frameCount { if limit != 0 && page*limit >= frameCount {
return true, nil return nil, nil, true, nil
} }
h, err := s.getHash(tr.Asset, cache) h, err := s.getHash(tr.Asset, cache)
if err != nil { if err != nil {
return false, err return nil, nil, false, err
} }
transfer := result.NEP17Transfer{ transfer := result.NEP17Transfer{
@ -827,24 +1003,51 @@ func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Er
if !tr.From.Equals(util.Uint160{}) { if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From) transfer.Address = address.Uint160ToString(tr.From)
} }
bs.Received = append(bs.Received, transfer) received = &result.NEP17Transfer{}
*received = transfer // Make a copy, transfer is to be modified below.
} else { } else {
transfer.Amount = new(big.Int).Neg(&tr.Amount).String() transfer.Amount = new(big.Int).Neg(&tr.Amount).String()
if !tr.To.Equals(util.Uint160{}) { if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To) transfer.Address = address.Uint160ToString(tr.To)
} }
bs.Sent = append(bs.Sent, transfer) sent = &result.NEP17Transfer{}
*sent = transfer
} }
resCount++ resCount++
// Using limits, reached limit. // Check limits for continue flag.
if limit != 0 && resCount >= limit { return received, sent, !(limit != 0 && resCount >= limit), nil
return false, nil }
} if !isNEP11 {
return true, nil err = s.chain.ForEachNEP17Transfer(u, func(tr *state.NEP17Transfer) (bool, error) {
}) r, s, res, err := handleTransfer(tr)
if err == nil {
if r != nil {
bs.Received = append(bs.Received, r)
}
if s != nil {
bs.Sent = append(bs.Sent, s)
}
}
return res, err
})
} else {
err = s.chain.ForEachNEP11Transfer(u, func(tr *state.NEP11Transfer) (bool, error) {
r, s, res, err := handleTransfer(&tr.NEP17Transfer)
if err == nil {
id := hex.EncodeToString(tr.ID)
if r != nil {
bs.Received = append(bs.Received, nep17TransferToNEP11(r, id))
}
if s != nil {
bs.Sent = append(bs.Sent, nep17TransferToNEP11(s, id))
}
}
return res, err
})
}
if err != nil { if err != nil {
return nil, response.NewInternalServerError("invalid NEP17 transfer log", err) return nil, response.NewInternalServerError("invalid transfer log", err)
} }
return bs, nil return bs, nil
} }
@ -1444,12 +1647,7 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx) return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx)
} }
// runScriptInVM runs given script in a new test VM and returns the invocation func (s *Server) getFakeNextBlock() (*block.Block, error) {
// result. The script is either a simple script in case of `application` trigger
// witness invocation script in case of `verification` trigger (it pushes `verify`
// arguments on stack before verification). In case of contract verification
// contractScriptHash should be specified.
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction) (*result.Invoke, *response.Error) {
// When transferring funds, script execution does no auto GAS claim, // When transferring funds, script execution does no auto GAS claim,
// because it depends on persisting tx height. // because it depends on persisting tx height.
// This is why we provide block here. // This is why we provide block here.
@ -1457,10 +1655,22 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
b.Index = s.chain.BlockHeight() + 1 b.Index = s.chain.BlockHeight() + 1
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight()))) hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight())))
if err != nil { if err != nil {
return nil, response.NewInternalServerError("can't get last block", err) return nil, err
} }
b.Timestamp = hdr.Timestamp + uint64(s.chain.GetConfig().SecondsPerBlock*int(time.Second/time.Millisecond)) b.Timestamp = hdr.Timestamp + uint64(s.chain.GetConfig().SecondsPerBlock*int(time.Second/time.Millisecond))
return b, nil
}
// runScriptInVM runs given script in a new test VM and returns the invocation
// result. The script is either a simple script in case of `application` trigger
// witness invocation script in case of `verification` trigger (it pushes `verify`
// arguments on stack before verification). In case of contract verification
// contractScriptHash should be specified.
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction) (*result.Invoke, *response.Error) {
b, err := s.getFakeNextBlock()
if err != nil {
return nil, response.NewInternalServerError("can't create fake block", err)
}
vm, finalize := s.chain.GetTestVM(t, tx, b) vm, finalize := s.chain.GetTestVM(t, tx, b)
vm.GasLimit = int64(s.config.MaxGasInvoke) vm.GasLimit = int64(s.config.MaxGasInvoke)
if t == trigger.Verification { if t == trigger.Verification {

View file

@ -68,6 +68,20 @@ const invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCG
const nameServiceContractHash = "3a602b3e7cfd760850bfac44f4a9bb0ebad3e2dc" const nameServiceContractHash = "3a602b3e7cfd760850bfac44f4a9bb0ebad3e2dc"
var NNSHash = util.Uint160{0xdc, 0xe2, 0xd3, 0xba, 0x0e, 0xbb, 0xa9, 0xf4, 0x44, 0xac, 0xbf, 0x50, 0x08, 0x76, 0xfd, 0x7c, 0x3e, 0x2b, 0x60, 0x3a}
var nep11Reg = &result.NEP11Balances{
Address: "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn",
Balances: []result.NEP11AssetBalance{{
Asset: NNSHash,
Tokens: []result.NEP11TokenBalance{{
ID: "6e656f2e636f6d",
Amount: "1",
LastUpdated: 14,
}},
}},
}
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {
{ {
@ -213,7 +227,89 @@ var rpcTestCases = map[string][]rpcTestCase{
fail: true, fail: true,
}, },
}, },
"getnep11balances": {
{
name: "no params",
params: `[]`,
fail: true,
},
{
name: "invalid address",
params: `["notahex"]`,
fail: true,
},
{
name: "positive",
params: `["` + testchain.PrivateKeyByID(0).GetScriptHash().StringLE() + `"]`,
result: func(e *executor) interface{} { return nep11Reg },
},
{
name: "positive_address",
params: `["` + address.Uint160ToString(testchain.PrivateKeyByID(0).GetScriptHash()) + `"]`,
result: func(e *executor) interface{} { return nep11Reg },
},
},
"getnep11properties": {
{
name: "no params",
params: `[]`,
fail: true,
},
{
name: "invalid address",
params: `["notahex"]`,
fail: true,
},
{
name: "no token",
params: `["` + NNSHash.StringLE() + `"]`,
fail: true,
},
{
name: "bad token",
params: `["` + NNSHash.StringLE() + `", "abcdef"]`,
fail: true,
},
{
name: "positive",
params: `["` + NNSHash.StringLE() + `", "6e656f2e636f6d"]`,
result: func(e *executor) interface{} {
return &map[string]interface{}{
"name": "neo.com",
"expiration": "bhORxoMB",
}
},
},
},
"getnep11transfers": {
{
name: "no params",
params: `[]`,
fail: true,
},
{
name: "invalid address",
params: `["notahex"]`,
fail: true,
},
{
name: "invalid timestamp",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`,
fail: true,
},
{
name: "invalid stop timestamp",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "blah"]`,
fail: true,
},
{
name: "positive",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", 0]`,
result: func(e *executor) interface{} {
return &result.NEP11Transfers{Sent: []result.NEP11Transfer{}, Received: []result.NEP11Transfer{{Timestamp: 0x17c6edfe76e, Asset: util.Uint160{0xdc, 0xe2, 0xd3, 0xba, 0xe, 0xbb, 0xa9, 0xf4, 0x44, 0xac, 0xbf, 0x50, 0x8, 0x76, 0xfd, 0x7c, 0x3e, 0x2b, 0x60, 0x3a}, Address: "", ID: "6e656f2e636f6d", Amount: "1", Index: 0xe, NotifyIndex: 0x0, TxHash: util.Uint256{0x5b, 0x5a, 0x5b, 0xae, 0xf2, 0xc5, 0x63, 0x8a, 0x2e, 0xcc, 0x77, 0x27, 0xd9, 0x6b, 0xb9, 0xda, 0x3a, 0x7f, 0x30, 0xaa, 0xcf, 0xda, 0x7f, 0x8a, 0x10, 0xd3, 0x23, 0xbf, 0xd, 0x1f, 0x28, 0x69}}}, Address: "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn"}
},
},
},
"getnep17balances": { "getnep17balances": {
{ {
name: "no params", name: "no params",

27
pkg/rpc/server/tokens.go Normal file
View file

@ -0,0 +1,27 @@
package server
import (
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
)
// tokenTransfers is a generic type used to represent NEP-11 and NEP-17 transfers.
type tokenTransfers struct {
Sent []interface{} `json:"sent"`
Received []interface{} `json:"received"`
Address string `json:"address"`
}
// nep17TransferToNEP11 adds an ID to provided NEP-17 transfer and returns a new
// NEP-11 structure.
func nep17TransferToNEP11(t17 *result.NEP17Transfer, id string) result.NEP11Transfer {
return result.NEP11Transfer{
Timestamp: t17.Timestamp,
Asset: t17.Asset,
Address: t17.Address,
ID: id,
Amount: t17.Amount,
Index: t17.Index,
NotifyIndex: t17.NotifyIndex,
TxHash: t17.TxHash,
}
}

View file

@ -15,9 +15,9 @@ const (
// MaxManifestSize is a max length for a valid contract manifest. // MaxManifestSize is a max length for a valid contract manifest.
MaxManifestSize = math.MaxUint16 MaxManifestSize = math.MaxUint16
// NEP11StandardName represents the name of NEP11 smartcontract standard. // NEP11StandardName represents the name of NEP-11 smartcontract standard.
NEP11StandardName = "NEP-11" NEP11StandardName = "NEP-11"
// NEP17StandardName represents the name of NEP17 smartcontract standard. // NEP17StandardName represents the name of NEP-17 smartcontract standard.
NEP17StandardName = "NEP-17" NEP17StandardName = "NEP-17"
// NEP11Payable represents the name of contract interface which can receive NEP-11 tokens. // NEP11Payable represents the name of contract interface which can receive NEP-11 tokens.
NEP11Payable = "NEP-11-Payable" NEP11Payable = "NEP-11-Payable"

View file

@ -14,9 +14,9 @@ func TestToken_MarshalJSON(t *testing.T) {
h, err := util.Uint160DecodeStringLE("f8d448b227991cf07cb96a6f9c0322437f1599b9") h, err := util.Uint160DecodeStringLE("f8d448b227991cf07cb96a6f9c0322437f1599b9")
require.NoError(t, err) require.NoError(t, err)
tok := NewToken(h, "NEP17 Standard", "NEP17", 8, manifest.NEP17StandardName) tok := NewToken(h, "NEP-17 standard token", "NEPT", 8, manifest.NEP17StandardName)
require.Equal(t, "NEP17 Standard", tok.Name) require.Equal(t, "NEP-17 standard token", tok.Name)
require.Equal(t, "NEP17", tok.Symbol) require.Equal(t, "NEPT", tok.Symbol)
require.EqualValues(t, 8, tok.Decimals) require.EqualValues(t, 8, tok.Decimals)
require.Equal(t, h, tok.Hash) require.Equal(t, h, tok.Hash)
require.Equal(t, "NcqKahsZ93ZyYS5bep8G2TY1zRB7tfUPdK", tok.Address()) require.Equal(t, "NcqKahsZ93ZyYS5bep8G2TY1zRB7tfUPdK", tok.Address())