Skip to content

Commit

Permalink
Merge branch 'release/v1.12.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
algojack committed Apr 21, 2022
2 parents 51e0364 + 1658237 commit e6f5aa9
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 23 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

# v1.12.0
## Fixed
- Catch TypeError and ValueError in verify functions (#309)
## Added
- Dryrun response (#283)

# v1.11.0
## Added
- Support unlimited assets REST API changes. (#295)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
UNITS = "@unit.abijson or @unit.algod or @unit.applications or @unit.atomic_transaction_composer or @unit.dryrun or @unit.feetest or @unit.indexer or @unit.indexer.logs or @unit.offline or @unit.rekey or @unit.transactions.keyreg or @unit.responses or @unit.responses.231 or @unit.tealsign or @unit.transactions or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.indexer.ledger_refactoring or @unit.algod.ledger_refactoring"
UNITS = "@unit.abijson or @unit.algod or @unit.algod.ledger_refactoring or @unit.applications or @unit.atomic_transaction_composer or @unit.dryrun or @unit.dryrun.trace.application or @unit.feetest or @unit.indexer or @unit.indexer.ledger_refactoring or @unit.indexer.logs or @unit.offline or @unit.rekey or @unit.transactions.keyreg or @unit.responses or @unit.responses.231 or @unit.tealsign or @unit.transactions or @unit.transactions.payment or @unit.responses.unlimited_assets"
unit:
behave --tags=$(UNITS) test -f progress2

Expand Down
1 change: 1 addition & 0 deletions algosdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from . import algod
from . import auction
from . import constants
from . import dryrun_results
from . import encoding
from . import error
from . import future
Expand Down
6 changes: 3 additions & 3 deletions algosdk/abi/tuple_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class TupleType(ABIType):
"""

def __init__(self, arg_types: List[Any]) -> None:
if len(arg_types) >= 2 ** 16:
if len(arg_types) >= 2**16:
raise error.ABITypeError(
"tuple args cannot exceed a uint16: {}".format(len(arg_types))
)
Expand Down Expand Up @@ -143,7 +143,7 @@ def encode(self, values: Union[List[Any], bytes, bytearray]) -> bytes:
Returns:
bytes: encoded bytes of the tuple
"""
if len(self.child_types) >= (2 ** 16):
if len(self.child_types) >= (2**16):
raise error.ABIEncodingError(
"length of tuple array should not exceed a uint16: {}".format(
len(self.child_types)
Expand Down Expand Up @@ -198,7 +198,7 @@ def encode(self, values: Union[List[Any], bytes, bytearray]) -> bytes:
for i in range(len(heads)):
if is_dynamic_index[i]:
head_value = head_length + tail_curr_length
if head_value >= 2 ** 16:
if head_value >= 2**16:
raise error.ABIEncodingError(
"byte length {} should not exceed a uint16".format(
head_value
Expand Down
2 changes: 1 addition & 1 deletion algosdk/abi/ufixed_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def encode(self, value: int) -> bytes:
"""
if (
not isinstance(value, int)
or value >= (2 ** self.bit_size)
or value >= (2**self.bit_size)
or value < 0
):
raise error.ABIEncodingError(
Expand Down
2 changes: 1 addition & 1 deletion algosdk/abi/uint_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def encode(self, value: int) -> bytes:
"""
if (
not isinstance(value, int)
or value >= (2 ** self.bit_size)
or value >= (2**self.bit_size)
or value < 0
):
raise error.ABIEncodingError(
Expand Down
221 changes: 221 additions & 0 deletions algosdk/dryrun_results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import base64
from typing import List


class StackPrinterConfig:
DEFAULT_MAX_VALUE_WIDTH: int = 30

def __init__(
self, max_value_width=DEFAULT_MAX_VALUE_WIDTH, top_of_stack_first=True
):
self.max_value_width = max_value_width
self.top_of_stack_first = top_of_stack_first


class DryrunResponse:
def __init__(self, drrjson: dict):
for param in ["error", "protocol-version", "txns"]:
assert (
param in drrjson
), f"expecting dryrun response object to have key '{param}' but it is missing"

# These are all required fields
self.error = drrjson["error"]
self.protocol = drrjson["protocol-version"]
self.txns = [DryrunTransactionResult(txn) for txn in drrjson["txns"]]


class DryrunTransactionResult:
def __init__(self, dr):
assert (
"disassembly" in dr
), "expecting dryrun transaction result to have key 'disassembly' but its missing"

self.disassembly = dr["disassembly"]

optionals = [
"app-call-messages",
"local-deltas",
"global-delta",
"cost",
"logic-sig-messages",
"logic-sig-disassembly",
"logs",
]

def attrname(field):
return field.replace("-", "_")

for field in optionals:
setattr(self, attrname(field), dr.get(field))

traces = ["app-call-trace", "logic-sig-trace"]
for trace_field in traces:
if trace_field in dr:
setattr(
self,
attrname(trace_field),
DryrunTrace(dr[trace_field]),
)

def app_call_rejected(self) -> bool:
return (
False
if self.app_call_messages is None
else "REJECT" in self.app_call_messages
)

def logic_sig_rejected(self) -> bool:
if self.logic_sig_messages is not None:
return "REJECT" in self.logic_sig_messages
return False

@classmethod
def trace(
cls,
dr_trace: "DryrunTrace",
disassembly: List[str],
spc: StackPrinterConfig,
) -> str:

# 16 for length of the header up to spaces
lines = [["pc#", "ln#", "source", "scratch", "stack"]]
for idx in range(len(dr_trace.trace)):

trace_line = dr_trace.trace[idx]

src = disassembly[trace_line.line]
if trace_line.error != "":
src = "!! {} !!".format(trace_line.error)

prev_scratch = []
if idx > 0:
prev_scratch = dr_trace.trace[idx - 1].scratch

scratch = scratch_to_string(prev_scratch, trace_line.scratch)
stack = stack_to_string(trace_line.stack, spc.top_of_stack_first)
lines.append(
[
"{}".format(trace_line.pc),
"{}".format(trace_line.line),
truncate(src, spc.max_value_width),
truncate(scratch, spc.max_value_width),
truncate(stack, spc.max_value_width),
]
)

cols = len(lines[0])
max_widths = [0] * cols
for line in lines:
for i in range(cols):
if len(line[i]) > max_widths[i]:
max_widths[i] = len(line[i])

trace = []
for line in lines:
trace.append(
" |".join(
[str(line[i]).ljust(max_widths[i]) for i in range(cols)]
).strip()
)

return "\n".join(trace) + "\n"

def app_trace(self, spc: StackPrinterConfig = None) -> str:
if not hasattr(self, "app_call_trace"):
return ""

if spc == None:
spc = StackPrinterConfig(top_of_stack_first=False)

return self.trace(self.app_call_trace, self.disassembly, spc=spc)

def lsig_trace(self, spc: StackPrinterConfig = None) -> str:
if not hasattr(self, "logic_sig_trace"):
return ""

if getattr(self, "logic_sig_disassembly", None) is None:
return ""

if spc is None:
spc = StackPrinterConfig(top_of_stack_first=False)

return self.trace(
self.logic_sig_trace, self.logic_sig_disassembly, spaces=spc
)


class DryrunTrace:
def __init__(self, trace: List[dict]):
self.trace = [DryrunTraceLine(line) for line in trace]

def get_trace(self) -> List[str]:
return [line.trace_line() for line in self.trace]


class DryrunTraceLine:
def __init__(self, tl):
self.line = tl["line"]
self.pc = tl["pc"]

self.error = ""
if "error" in tl:
self.error = tl["error"]

self.scratch = []
if "scratch" in tl:
self.scratch = [DryrunStackValue(sv) for sv in tl["scratch"]]

self.stack = [DryrunStackValue(sv) for sv in tl["stack"]]


class DryrunStackValue:
def __init__(self, v):
self.type = v["type"]
self.bytes = v["bytes"]
self.int = v["uint"]

def __str__(self) -> str:
if len(self.bytes) > 0:
return "0x" + base64.b64decode(self.bytes).hex()
return str(self.int)

def __eq__(self, other: "DryrunStackValue"):
return (
self.type == other.type
and self.bytes == other.bytes
and self.int == other.int
)


def truncate(s: str, max_width: int) -> str:
if len(s) > max_width and max_width > 0:
return s[:max_width] + "..."
return s


def scratch_to_string(
prev_scratch: List[DryrunStackValue], curr_scratch: List[DryrunStackValue]
) -> str:
if not curr_scratch:
return ""

new_idx = None
for idx in range(len(curr_scratch)):
if idx >= len(prev_scratch):
new_idx = idx
continue

if curr_scratch[idx] != prev_scratch[idx]:
new_idx = idx

if new_idx == None:
return ""

return "{} = {}".format(new_idx, curr_scratch[new_idx])


def stack_to_string(stack: List[DryrunStackValue], reverse: bool) -> str:
if reverse:
stack.reverse()
return "[{}]".format(", ".join([str(sv) for sv in stack]))
4 changes: 2 additions & 2 deletions algosdk/future/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2393,7 +2393,7 @@ def verify(self, message):
try:
verify_key.verify(message, subsig.signature)
verified_count += 1
except BadSignatureError:
except (BadSignatureError, ValueError, TypeError):
return False

if verified_count < self.threshold:
Expand Down Expand Up @@ -2562,7 +2562,7 @@ def verify(self, public_key):
try:
verify_key.verify(to_sign, base64.b64decode(self.sig))
return True
except (BadSignatureError, ValueError):
except (BadSignatureError, ValueError, TypeError):
return False

return self.msig.verify(to_sign)
Expand Down
2 changes: 1 addition & 1 deletion algosdk/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def verify_bytes(message, signature, public_key):
try:
verify_key.verify(prefixed_message, base64.b64decode(signature))
return True
except BadSignatureError:
except (BadSignatureError, ValueError, TypeError):
return False


Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.
black==21.9b0
black==22.3.0
glom==20.11.0
pytest==6.2.5
git+https://github.com/behave/behave
8 changes: 6 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@
description="Algorand SDK in Python",
author="Algorand",
author_email="[email protected]",
version="v1.11.0",
version="v1.12.0",
long_description=long_description,
long_description_content_type="text/markdown",
license="MIT",
project_urls={
"Source": "https://github.com/algorand/py-algorand-sdk",
},
install_requires=["pynacl", "pycryptodomex>=3.6.0", "msgpack"],
install_requires=[
"pynacl>=1.4.0,<2",
"pycryptodomex>=3.6.0,<4",
"msgpack>=1.0.0,<2",
],
packages=setuptools.find_packages(),
python_requires=">=3.5",
package_data={"": ["data/langspec.json"]},
Expand Down
12 changes: 6 additions & 6 deletions test/steps/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -984,34 +984,34 @@ def add_rekey_to_address(context, rekey):
context.txn.rekey_to = rekey


@given(u'base64 encoded data to sign "{data_enc}"')
@given('base64 encoded data to sign "{data_enc}"')
def set_base64_encoded_data(context, data_enc):
context.data = base64.b64decode(data_enc)


@given(u'program hash "{contract_addr}"')
@given('program hash "{contract_addr}"')
def set_program_hash(context, contract_addr):
context.address = contract_addr


@when(u"I perform tealsign")
@when("I perform tealsign")
def perform_tealsign(context):
context.sig = logic.teal_sign(context.sk, context.data, context.address)


@then(u'the signature should be equal to "{sig_enc}"')
@then('the signature should be equal to "{sig_enc}"')
def check_tealsign(context, sig_enc):
expected = base64.b64decode(sig_enc)
assert expected == context.sig


@given(u'base64 encoded program "{program_enc}"')
@given('base64 encoded program "{program_enc}"')
def set_program_hash_from_program(context, program_enc):
program = base64.b64decode(program_enc)
context.address = logic.address(program)


@given(u'base64 encoded private key "{sk_enc}"')
@given('base64 encoded private key "{sk_enc}"')
def set_sk_from_encoded_seed(context, sk_enc):
seed = base64.b64decode(sk_enc)
key = SigningKey(seed)
Expand Down
Loading

0 comments on commit e6f5aa9

Please sign in to comment.