Skip to content
This repository was archived by the owner on Nov 15, 2021. It is now read-only.

[refactor-prompt] add token approve and token allowance #769

Merged
merged 1 commit into from
Dec 24, 2018
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
145 changes: 109 additions & 36 deletions neo/Prompt/Commands/Tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def __init__(self):
self.register_sub_command(CommandTokenSend())
self.register_sub_command(CommandTokenSendFrom())
self.register_sub_command(CommandTokenHistory())
self.register_sub_command(CommandTokenApprove())
self.register_sub_command(CommandTokenAllowance())

def command_desc(self):
return CommandDesc('token', 'various token operations')
Expand Down Expand Up @@ -263,6 +265,113 @@ def command_desc(self):
return CommandDesc('history', 'show transaction history', [p1])


class CommandTokenApprove(CommandBase):

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

def execute(self, arguments):
wallet = PromptData.Wallet

if not len(arguments) in [4, 5]:
print("Please specify the required parameters")
return False

token_str = arguments[0]
from_addr = arguments[1]
to_addr = arguments[2]
amount = arguments[3]

prompt_passwd = True
if len(arguments) == 5:
try:
prompt_passwd = bool(strtobool(arguments[4]))
except ValueError:
print("Invalid value for showing prompt parameter. Must be True or False if supplied")
return False

try:
token = _validate_nep5_args(wallet, token_str, from_addr, to_addr, amount)
except ValueError as e:
print(str(e))
return False

decimal_amount = amount_from_string(token, amount)

tx, fee, results = token.Approve(wallet, from_addr, to_addr, decimal_amount)

if tx and results:
if results[0].GetBigInteger() == 1:
print("\n-----------------------------------------------------------")
print(f"Approve allowance of {amount} {token.symbol} from {from_addr} to {to_addr}")
print(f"Transfer fee: {fee.value / Fixed8.D}")
print("-------------------------------------------------------------\n")

if prompt_passwd:
passwd = prompt("[Password]> ", is_password=True)

if not wallet.ValidatePassword(passwd):
print("incorrect password")
return False

return InvokeContract(wallet, tx, fee)

print("Failed to approve tokens. Make sure you are entitled for approving.")
return False

def command_desc(self):
p1 = ParameterDesc('symbol', 'token symbol')
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('ask_for_passw', 'prompt for a password before sending to the actual network. Default is True', optional=True)

return CommandDesc('approve', 'approve an allowance', [p1, p2, p3, p4, p5])

def handle_help(self, arguments):
super().handle_help(arguments)
print(
"\nThis is an optional NEP-5 command (now legacy).\nFor more information see https://github.com/neo-project/proposals/blob/c357f5965afc2155615b6b96c7d15da688f81982/nep-5.mediawiki#approve_optional")


class CommandTokenAllowance(CommandBase):

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

def execute(self, arguments):
wallet = PromptData.Wallet

if len(arguments) != 3:
print("Please specify the required parameters")
return False

token_str = arguments[0]
from_addr = arguments[1]
to_addr = arguments[2]

try:
token = PromptUtils.get_token(wallet, token_str)
except ValueError as e:
print(str(e))
return False

try:
allowance = token_get_allowance(wallet, token_str, from_addr, to_addr)
print(f"{token.symbol} allowance for {from_addr} from {to_addr} : {allowance} ")
return True
except ValueError as e:
print(str(e))
return False

def command_desc(self):
p1 = ParameterDesc('symbol', 'token symbol')
p2 = ParameterDesc('from_addr', 'address to send token from')
p3 = ParameterDesc('to_addr', 'address to send token to')

return CommandDesc('allowance', 'get the amount an account can transfer from another acount', [p1, p2, p3])


def _validate_nep5_args(wallet, token_str, from_addr, to_addr, amount):
"""
A helper function to validate common arguments used in NEP-5 functions
Expand Down Expand Up @@ -379,42 +488,6 @@ def test_token_send_from(wallet, token_str, from_addr, to_addr, amount):
return token, tx, fees, results


def token_approve_allowance(wallet, args, prompt_passwd=True):
if len(args) != 4:
print("please provide a token symbol, from address, to address, and amount")
return False

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

approve_from = args[1]
approve_to = args[2]
amount = amount_from_string(token, args[3])

tx, fee, results = token.Approve(wallet, approve_from, approve_to, amount)

if tx is not None and results is not None and len(results) > 0:
if results[0].GetBigInteger() == 1:
print("\n-----------------------------------------------------------")
print("Approve allowance of %s %s from %s to %s" % (string_from_amount(token, amount), token.symbol, approve_from, approve_to))
print("Transfer fee: %s " % (fee.value / Fixed8.D))
print("-------------------------------------------------------------\n")

if prompt_passwd:
passwd = prompt("[Password]> ", is_password=True)

if not wallet.ValidatePassword(passwd):
print("incorrect password")
return False

return InvokeContract(wallet, tx, fee)

print("could not transfer tokens")
return False


def token_get_allowance(wallet, token_str, from_addr, to_addr, verbose=False):
"""
Query the smart contract for the amount from_addr is allowed to send to to_addr
Expand Down
181 changes: 119 additions & 62 deletions neo/Prompt/Commands/tests/test_token_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from neo.Implementations.Wallets.peewee.UserWallet import UserWallet
from neo.Core.Blockchain import Blockchain
from neo.Prompt.Commands.Wallet import ImportToken, CommandWallet
from neo.Prompt.Commands.Tokens import token_get_allowance, token_approve_allowance, \
token_send, token_history, token_mint, token_crowdsale_register, amount_from_string
from neo.Prompt.Commands.Tokens import token_get_allowance, token_send, token_history, token_mint, token_crowdsale_register, amount_from_string
import shutil
from neocore.IO.BinaryWriter import BinaryWriter
from neo.IO.MemoryStream import StreamManager
Expand Down Expand Up @@ -210,66 +209,6 @@ def test_token_send_bad_password(self):

self.assertFalse(send)

def test_token_approve_good(self):
with patch('neo.Prompt.Commands.Tokens.prompt', side_effect=[UserWalletTestCase.wallet_1_pass()]):
wallet = self.GetWallet1(recreate=True)
token = self.get_token(wallet)
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

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

self.assertTrue(send)
res = send.ToJson()
self.assertEqual(res["vout"][0]["address"], "AJQ6FoaSXDFzA6wLnyZ1nFN7SGSN2oNTc3")
self.assertEqual(res["net_fee"], "0.0001")

def test_token_approve_bad_args(self): # too few args
wallet = self.GetWallet1(recreate=True)
token = self.get_token(wallet)
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

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

self.assertFalse(send)

def test_token_approve_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, '123']
send = token_approve_allowance(wallet, args, prompt_passwd=False)

self.assertFalse(send)

def test_token_approve_no_tx(self):
with patch('neo.Wallets.NEP5Token.NEP5Token.Approve', return_value=(None, 0, None)):
wallet = self.GetWallet1(recreate=True)
token = self.get_token(wallet)
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

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

self.assertFalse(send)

def test_token_approve_bad_password(self):
with patch('neo.Prompt.Commands.Tokens.prompt', side_effect=["blah"]):
wallet = self.GetWallet1(recreate=True)
token = self.get_token(wallet)
addr_from = wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

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

self.assertFalse(send)

def test_token_allowance_good(self):
with patch('neo.Wallets.NEP5Token.NEP5Token.Allowance', return_value=(self.Approve_Allowance())):
wallet = self.GetWallet1(recreate=True)
Expand Down Expand Up @@ -618,6 +557,124 @@ def test_token_history(self):
self.assertTrue(res)
self.assertIn("Received 1000.00000000 NXT4 from AJQ6FoaSXDFzA6wLnyZ1nFN7SGSN2oNTc3", mock_print.getvalue())

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

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

# test with invalid token argument
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'approve', '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', 'approve', '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', 'approve', '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', 'approve', 'NXT4', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'AZfFBeBqtJvaTK9JqG8uk6N7FppQY6byEg', 'invalid_amount']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("not a valid amount", mock_print.getvalue())

# setup some variables that are re-used in the tests below
token = list(PromptData.Wallet.GetTokens().values())[0]
addr_from = PromptData.Wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

# test for invalid prompt argument. Must true/false
with patch('sys.stdout', new=StringIO()) as mock_print:
res = CommandWallet().execute(['token', 'approve', token.symbol, addr_from, addr_to, 10000000, 'invalid_prompt_bool'])
self.assertFalse(res)
self.assertIn("Invalid value for showing prompt parameter", mock_print.getvalue())

# test approving with invalid password
with patch('sys.stdout', new=StringIO()) as mock_print:
with patch('neo.Prompt.Commands.Tokens.token_get_allowance', return_value=12300000000):
with patch('neo.Wallets.NEP5Token.NEP5Token.TransferFrom', return_value=(self.Approve_Allowance())):
with patch('neo.Prompt.Commands.Tokens.prompt', side_effect=['blah']):
args = ['token', 'approve', 'NXT4', addr_from, addr_to, '123', 'True']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("incorrect password", mock_print.getvalue())

# test failed to approve
with patch('sys.stdout', new=StringIO()) as mock_print:
with patch('neo.Wallets.NEP5Token.NEP5Token.Approve', return_value=(None, None, None)):
args = ['token', 'approve', 'NXT4', addr_from, addr_to, '123', 'True']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("Failed to approve tokens", mock_print.getvalue())

# test successful approval
args = ['token', 'approve', 'NXT4', addr_from, addr_to, '123', 'False']
res = CommandWallet().execute(args)
self.assertTrue(res)

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

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

# test with an invalid token argument
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'allowance', 'invalid_token_name', 'arg2', 'arg3']
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 (as that tests exceptions from the internal _validate_nep5_args() function)
# that function is tested enough in other command tests
with patch('sys.stdout', new=StringIO()) as mock_print:
args = ['token', 'allowance', 'NXT4', 'invalid_from_addr', 'arg3']
res = CommandWallet().execute(args)
self.assertFalse(res)
self.assertIn("not a valid address", mock_print.getvalue())

with patch('neo.Prompt.Commands.Tokens.token_get_allowance', return_value=12300000000):
with patch('sys.stdout', new=StringIO()) as mock_print:
addr_from = PromptData.Wallet.GetDefaultContract().Address
addr_to = self.watch_addr_str

args = ['token', 'allowance', 'NXT4', addr_from, addr_to]
res = CommandWallet().execute(args)
self.assertTrue(res)

# utility function
def Approve_Allowance(self):
wallet = self.GetWallet1(recreate=True)
Expand Down