Skip to content
This repository was archived by the owner on Nov 15, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion neo/Prompt/Commands/Invoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def test_invoke(script, wallet, outputs, withdrawal_tx=None,
if context.Completed:
wallet_tx.scripts = context.GetScripts()
else:
logger.warn("Not gathering signatures for test build. For a non-test invoke that would occur here.")
logger.warning("Not gathering signatures for test build. For a non-test invoke that would occur here.")
# if not gather_signatures(context, wallet_tx, owners):
# return None, [], 0, None

Expand Down
111 changes: 97 additions & 14 deletions neo/Prompt/Commands/Tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
from neocore.UInt160 import UInt160
from prompt_toolkit import prompt
from decimal import Decimal
from neo.Core.TX.TransactionAttribute import TransactionAttribute, TransactionAttributeUsage
from neo.Core.TX.TransactionAttribute import TransactionAttribute
import binascii
from neo.Prompt.CommandBase import CommandBase, CommandDesc, ParameterDesc
from neo.Prompt.PromptData import PromptData
from neo.Prompt.Utils import get_arg
from neo.Implementations.Wallets.peewee.Models import NEP5Token as ModelNEP5Token
from neo.Core.TX.TransactionAttribute import TransactionAttributeUsage
from neocore.Utils import isValidPublicAddress
import peewee


class CommandWalletToken(CommandBase):
def __init__(self):
super().__init__()
self.register_sub_command(CommandTokenDelete())
self.register_sub_command(CommandTokenSend())

def command_desc(self):
return CommandDesc('token', 'various token operations')
Expand Down Expand Up @@ -77,23 +80,103 @@ def command_desc(self):
return CommandDesc('delete', 'remove a token from the wallet', [p1])


def token_send(wallet, args, prompt_passwd=True):
if len(args) < 4:
print("please provide a token symbol, from address, to address, and amount")
return False
class CommandTokenSend(CommandBase):

def __init__(self):
super().__init__()

user_tx_attributes = None
if len(args) > 4:
args, user_tx_attributes = get_tx_attr_from_args(args)
def execute(self, arguments):
wallet = PromptData.Wallet

if len(arguments) < 4:
print("Please specify the required parameters")
return False

if len(arguments) > 5:
# the 5th argument is the optional attributes,
print("Too many parameters supplied. Please check your command")
return False

_, user_tx_attributes = get_tx_attr_from_args(arguments)

token = arguments[0]
send_from = arguments[1]
send_to = arguments[2]
try:
amount = float(arguments[3])
except ValueError:
print(f"{arguments[3]} is not a valid amount")
return False

try:
success = token_send(wallet, token, send_from, send_to, amount, user_tx_attributes)
except ValueError as e:
# occurs if arguments are invalid
print(str(e))
success = False

return success

def command_desc(self):
p1 = ParameterDesc('token', 'token symbol name or script_hash')
p2 = ParameterDesc('from_addr', 'address to send token from')
p3 = ParameterDesc('to_addr', 'address to send token to')
p4 = ParameterDesc('amount', 'number of tokens to send')
p5 = ParameterDesc('--tx-attr', f"a list of transaction attributes to attach to the transaction\n\n"
f"{' ':>17} See: http://docs.neo.org/en-us/network/network-protocol.html section 4 for a description of possible attributes\n\n" # noqa: E128 ignore indentation
f"{' ':>17} Example:\n"
f"{' ':>20} --tx-attr=[{{\"usage\": <value>,\"data\":\"<remark>\"}}, ...]\n"
f"{' ':>20} --tx-attr=[{{\"usage\": 0x90,\"data\":\"my brief description\"}}]\n", optional=True)

return CommandDesc('send', 'send a token from the wallet', [p1, p2, p3, p4, p5])


def token_send(wallet, token_str, send_from, send_to, amount, prompt_passwd=True, user_tx_attributes=None):
"""

Args:
wallet (Wallet): a UserWallet instance
token_str (str): symbol name or script_hash
send_from (str): a wallet address
send_to (str): a wallet address
amount (float): the number of tokens to send
prompt_passwd (bool): (optional) whether to prompt for a password before sending it to the network
user_tx_attributes (list): a list of ``TransactionAttribute``s.

Returns:
a Transaction object if successful, False otherwise.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's None if it fails

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do_token_transfer return False. See:

return False
return InvokeContract(wallet, tx, fee)
print("could not transfer tokens")
return False

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad!
I checked something and it was returning None, I don't know what I checked 🤔

"""
if not user_tx_attributes:
user_tx_attributes = []

token = None
for t in wallet.GetTokens().values():
if token_str == t.symbol:
token = t
break
elif token_str == t.ScriptHash.ToString():
token = t
break

token = get_asset_id(wallet, args[0])
if not isinstance(token, NEP5Token):
print("The given symbol does not represent a loaded NEP5 token")
return False
raise ValueError("The given token argument does not represent a known NEP5 token")

send_from = args[1]
send_to = args[2]
amount = amount_from_string(token, args[3])
if not isValidPublicAddress(send_from):
raise ValueError("send_from is not a valid address")

if not isValidPublicAddress(send_to):
raise ValueError("send_to is not a valid address")

try:
# internally this function uses the `Decimal` class which will parse the float amount to its required format.
# the name is a bit misleading /shrug
amount = amount_from_string(token, amount)
except Exception:
raise ValueError(f"{amount} is not a valid amount")

for attr in user_tx_attributes:
if not isinstance(attr, TransactionAttribute):
raise ValueError(f"{attr} is not a valid transaction attribute")

return do_token_transfer(token, wallet, send_from, send_to, amount, prompt_passwd=prompt_passwd, tx_attributes=user_tx_attributes)

Expand Down
96 changes: 79 additions & 17 deletions neo/Prompt/Commands/tests/test_token_commands.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from neo.Utils.WalletFixtureTestCase import WalletFixtureTestCase
from neo.Wallets.utils import to_aes_key
from neo.Prompt.Utils import get_asset_id
from neo.Prompt.Utils import get_asset_id, get_tx_attr_from_args
from neo.Implementations.Wallets.peewee.UserWallet import UserWallet
from neo.Implementations.Notifications.LevelDB.NotificationDB import NotificationDB
from neo.Core.Blockchain import Blockchain
Expand Down Expand Up @@ -115,8 +115,7 @@ def test_token_send_good(self):
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

args = [token.symbol, addr_from, addr_to, '1300']
send = token_send(wallet, args, prompt_passwd=True)
send = token_send(wallet, token.symbol, addr_from, addr_to, 1300, prompt_passwd=True)

self.assertTrue(send)
res = send.ToJson()
Expand All @@ -129,9 +128,9 @@ def test_token_send_with_user_attributes(self):
token = self.get_token(wallet)
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str
_, attributes = get_tx_attr_from_args(['--tx-attr=[{"usage":241,"data":"This is a remark"},{"usage":242,"data":"This is a remark 2"}]'])

args = [token.symbol, addr_from, addr_to, '1300', '--tx-attr=[{"usage":241,"data":"This is a remark"},{"usage":242,"data":"This is a remark 2"}]']
send = token_send(wallet, args, prompt_passwd=True)
send = token_send(wallet, token.symbol, addr_from, addr_to, 1300, user_tx_attributes=attributes, prompt_passwd=True)

self.assertTrue(send)
res = send.ToJson()
Expand All @@ -146,8 +145,8 @@ def test_token_send_bad_user_attributes(self):
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

args = [token.symbol, addr_from, addr_to, '100', '--tx-attr=[{"usa:241,"data":"This is a remark"}]']
send = token_send(wallet, args, prompt_passwd=True)
_, attributes = get_tx_attr_from_args(['--tx-attr=[{"usa:241,"data":"This is a remark"}]'])
send = token_send(wallet, token.symbol, addr_from, addr_to, 100, user_tx_attributes=attributes, prompt_passwd=True)

self.assertTrue(send)
res = send.ToJson()
Expand All @@ -160,20 +159,20 @@ def test_token_send_bad_args(self): # too few args
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

args = [token.symbol, addr_from, addr_to]
send = token_send(wallet, args, prompt_passwd=False)
with self.assertRaises(ValueError) as context:
token_send(wallet, token.symbol, addr_from, addr_to, None, prompt_passwd=False)

self.assertFalse(send)
self.assertIn("not a valid amount", str(context.exception))

def test_token_send_bad_token(self):
wallet = self.GetWallet1(recreate=True)
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

args = ["Blah", addr_from, addr_to, '1300']
send = token_send(wallet, args, prompt_passwd=False)
with self.assertRaises(ValueError) as context:
token_send(wallet, "Blah", addr_from, addr_to, 1300, prompt_passwd=False)

self.assertFalse(send)
self.assertIn("does not represent a known NEP5 token", str(context.exception))

def test_token_send_no_tx(self):
with patch('neo.Wallets.NEP5Token.NEP5Token.Transfer', return_value=(None, 0, None)):
Expand All @@ -182,8 +181,7 @@ def test_token_send_no_tx(self):
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

args = [token.symbol, addr_from, addr_to, '1300']
send = token_send(wallet, args, prompt_passwd=False)
send = token_send(wallet, token.symbol, addr_from, addr_to, 1300, prompt_passwd=False)

self.assertFalse(send)

Expand All @@ -194,8 +192,7 @@ def test_token_send_bad_password(self):
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

args = [token.symbol, addr_from, addr_to, '1300']
send = token_send(wallet, args)
send = token_send(wallet, token.symbol, addr_from, addr_to, 1300)

self.assertFalse(send)

Expand Down Expand Up @@ -617,6 +614,71 @@ def test_wallet_token_delete(self):
self.assertTrue(res)
self.assertIn("deleted", mock_print.getvalue())

def test_wallet_token_send(self):

with self.OpenWallet1():
# test with no parameters
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'send']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("specify the required parameter", mock_print.getvalue())

# test with insufficient parameters
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'send', 'arg1']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("specify the required parameter", mock_print.getvalue())

# test with too many parameters (max is 4 mandatory + 1 optional)
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'send', 'arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("Too many parameters supplied", mock_print.getvalue())

# test with invalid token argument
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'send', 'invalid_token_name', 'arg2', 'arg3', '10']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("does not represent a known NEP5 token", mock_print.getvalue())

# test with valid token arg, but invalid from_addr
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'send', 'NXT4', 'invalid_from_addr', 'arg3', '10']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("not a valid address", mock_print.getvalue())

# test with valid token and from_addr, but invalid to_addr
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'send', 'NXT4', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'invalid_to_addr', '10']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("not a valid address", mock_print.getvalue())

# test with invalid amount
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'send', 'NXT4', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'invalid_amount']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("not a valid amount", mock_print.getvalue())

# Note that there is no test for invalid tx-attributes. Invalid attributes result in an empty attribute list being
# passed to the underlying function thus having no effect.

# test with a good transfer
# we don't really send anything. Testing `do_token_transfer` already happens in `test_token_send_good()`
with patch('neo.Prompt.Commands.Tokens.do_token_transfer', side_effect=[object()]):
token = self.get_token(PromptData.Wallet)
addr_from = PromptData.Wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

send = token_send(PromptData.Wallet, token.symbol, addr_from, addr_to, 13, prompt_passwd=False)
self.assertTrue(send)

# utility function
def Approve_Allowance(self):
wallet = self.GetWallet1(recreate=True)
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ logzero==1.5.0
memory-profiler==0.54.0
mmh3==2.5.1
mock==2.0.0
mpmath==1.0.0
mpmath==1.1.0
neo-boa==0.5.6
neo-python-rpc==0.2.1
neocore==0.5.4
neocore==0.5.5
pbr==4.2.0
peewee==3.6.4
pexpect==4.6.0
Expand Down