mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 03:41:45 +00:00
Merge pull request #2266 from nspcc-dev/nep-11-tracking
NEP-11 tracking
This commit is contained in:
commit
aa06770b3d
41 changed files with 1343 additions and 534 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
4
cli/testdata/verify.go
vendored
4
cli/testdata/verify.go
vendored
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
51
docs/rpc.md
51
docs/rpc.md
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
204
pkg/core/state/tokens.go
Normal 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)
|
||||||
|
}
|
141
pkg/core/state/tokens_test.go
Normal file
141
pkg/core/state/tokens_test.go
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -29,6 +29,9 @@ Supported methods
|
||||||
getconnectioncount
|
getconnectioncount
|
||||||
getcontractstate
|
getcontractstate
|
||||||
getnativecontracts
|
getnativecontracts
|
||||||
|
getnep11balances
|
||||||
|
getnep11properties
|
||||||
|
getnep11transfers
|
||||||
getnep17balances
|
getnep17balances
|
||||||
getnep17transfers
|
getnep17transfers
|
||||||
getpeers
|
getpeers
|
||||||
|
|
|
@ -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{{
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 ¶ms, 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
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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"`
|
|
||||||
}
|
|
82
pkg/rpc/response/result/tokens.go
Normal file
82
pkg/rpc/response/result/tokens.go
Normal 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,
|
||||||
|
}
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
27
pkg/rpc/server/tokens.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue