Skip to content

Commit e6f5aa9

Browse files
committed
Merge branch 'release/v1.12.0'
2 parents 51e0364 + 1658237 commit e6f5aa9

File tree

14 files changed

+301
-23
lines changed

14 files changed

+301
-23
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
# v1.12.0
4+
## Fixed
5+
- Catch TypeError and ValueError in verify functions (#309)
6+
## Added
7+
- Dryrun response (#283)
8+
39
# v1.11.0
410
## Added
511
- Support unlimited assets REST API changes. (#295)

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
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"
1+
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"
22
unit:
33
behave --tags=$(UNITS) test -f progress2
44

algosdk/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from . import algod
44
from . import auction
55
from . import constants
6+
from . import dryrun_results
67
from . import encoding
78
from . import error
89
from . import future

algosdk/abi/tuple_type.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class TupleType(ABIType):
1717
"""
1818

1919
def __init__(self, arg_types: List[Any]) -> None:
20-
if len(arg_types) >= 2 ** 16:
20+
if len(arg_types) >= 2**16:
2121
raise error.ABITypeError(
2222
"tuple args cannot exceed a uint16: {}".format(len(arg_types))
2323
)
@@ -143,7 +143,7 @@ def encode(self, values: Union[List[Any], bytes, bytearray]) -> bytes:
143143
Returns:
144144
bytes: encoded bytes of the tuple
145145
"""
146-
if len(self.child_types) >= (2 ** 16):
146+
if len(self.child_types) >= (2**16):
147147
raise error.ABIEncodingError(
148148
"length of tuple array should not exceed a uint16: {}".format(
149149
len(self.child_types)
@@ -198,7 +198,7 @@ def encode(self, values: Union[List[Any], bytes, bytearray]) -> bytes:
198198
for i in range(len(heads)):
199199
if is_dynamic_index[i]:
200200
head_value = head_length + tail_curr_length
201-
if head_value >= 2 ** 16:
201+
if head_value >= 2**16:
202202
raise error.ABIEncodingError(
203203
"byte length {} should not exceed a uint16".format(
204204
head_value

algosdk/abi/ufixed_type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def encode(self, value: int) -> bytes:
6969
"""
7070
if (
7171
not isinstance(value, int)
72-
or value >= (2 ** self.bit_size)
72+
or value >= (2**self.bit_size)
7373
or value < 0
7474
):
7575
raise error.ABIEncodingError(

algosdk/abi/uint_type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def encode(self, value: int) -> bytes:
5454
"""
5555
if (
5656
not isinstance(value, int)
57-
or value >= (2 ** self.bit_size)
57+
or value >= (2**self.bit_size)
5858
or value < 0
5959
):
6060
raise error.ABIEncodingError(

algosdk/dryrun_results.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import base64
2+
from typing import List
3+
4+
5+
class StackPrinterConfig:
6+
DEFAULT_MAX_VALUE_WIDTH: int = 30
7+
8+
def __init__(
9+
self, max_value_width=DEFAULT_MAX_VALUE_WIDTH, top_of_stack_first=True
10+
):
11+
self.max_value_width = max_value_width
12+
self.top_of_stack_first = top_of_stack_first
13+
14+
15+
class DryrunResponse:
16+
def __init__(self, drrjson: dict):
17+
for param in ["error", "protocol-version", "txns"]:
18+
assert (
19+
param in drrjson
20+
), f"expecting dryrun response object to have key '{param}' but it is missing"
21+
22+
# These are all required fields
23+
self.error = drrjson["error"]
24+
self.protocol = drrjson["protocol-version"]
25+
self.txns = [DryrunTransactionResult(txn) for txn in drrjson["txns"]]
26+
27+
28+
class DryrunTransactionResult:
29+
def __init__(self, dr):
30+
assert (
31+
"disassembly" in dr
32+
), "expecting dryrun transaction result to have key 'disassembly' but its missing"
33+
34+
self.disassembly = dr["disassembly"]
35+
36+
optionals = [
37+
"app-call-messages",
38+
"local-deltas",
39+
"global-delta",
40+
"cost",
41+
"logic-sig-messages",
42+
"logic-sig-disassembly",
43+
"logs",
44+
]
45+
46+
def attrname(field):
47+
return field.replace("-", "_")
48+
49+
for field in optionals:
50+
setattr(self, attrname(field), dr.get(field))
51+
52+
traces = ["app-call-trace", "logic-sig-trace"]
53+
for trace_field in traces:
54+
if trace_field in dr:
55+
setattr(
56+
self,
57+
attrname(trace_field),
58+
DryrunTrace(dr[trace_field]),
59+
)
60+
61+
def app_call_rejected(self) -> bool:
62+
return (
63+
False
64+
if self.app_call_messages is None
65+
else "REJECT" in self.app_call_messages
66+
)
67+
68+
def logic_sig_rejected(self) -> bool:
69+
if self.logic_sig_messages is not None:
70+
return "REJECT" in self.logic_sig_messages
71+
return False
72+
73+
@classmethod
74+
def trace(
75+
cls,
76+
dr_trace: "DryrunTrace",
77+
disassembly: List[str],
78+
spc: StackPrinterConfig,
79+
) -> str:
80+
81+
# 16 for length of the header up to spaces
82+
lines = [["pc#", "ln#", "source", "scratch", "stack"]]
83+
for idx in range(len(dr_trace.trace)):
84+
85+
trace_line = dr_trace.trace[idx]
86+
87+
src = disassembly[trace_line.line]
88+
if trace_line.error != "":
89+
src = "!! {} !!".format(trace_line.error)
90+
91+
prev_scratch = []
92+
if idx > 0:
93+
prev_scratch = dr_trace.trace[idx - 1].scratch
94+
95+
scratch = scratch_to_string(prev_scratch, trace_line.scratch)
96+
stack = stack_to_string(trace_line.stack, spc.top_of_stack_first)
97+
lines.append(
98+
[
99+
"{}".format(trace_line.pc),
100+
"{}".format(trace_line.line),
101+
truncate(src, spc.max_value_width),
102+
truncate(scratch, spc.max_value_width),
103+
truncate(stack, spc.max_value_width),
104+
]
105+
)
106+
107+
cols = len(lines[0])
108+
max_widths = [0] * cols
109+
for line in lines:
110+
for i in range(cols):
111+
if len(line[i]) > max_widths[i]:
112+
max_widths[i] = len(line[i])
113+
114+
trace = []
115+
for line in lines:
116+
trace.append(
117+
" |".join(
118+
[str(line[i]).ljust(max_widths[i]) for i in range(cols)]
119+
).strip()
120+
)
121+
122+
return "\n".join(trace) + "\n"
123+
124+
def app_trace(self, spc: StackPrinterConfig = None) -> str:
125+
if not hasattr(self, "app_call_trace"):
126+
return ""
127+
128+
if spc == None:
129+
spc = StackPrinterConfig(top_of_stack_first=False)
130+
131+
return self.trace(self.app_call_trace, self.disassembly, spc=spc)
132+
133+
def lsig_trace(self, spc: StackPrinterConfig = None) -> str:
134+
if not hasattr(self, "logic_sig_trace"):
135+
return ""
136+
137+
if getattr(self, "logic_sig_disassembly", None) is None:
138+
return ""
139+
140+
if spc is None:
141+
spc = StackPrinterConfig(top_of_stack_first=False)
142+
143+
return self.trace(
144+
self.logic_sig_trace, self.logic_sig_disassembly, spaces=spc
145+
)
146+
147+
148+
class DryrunTrace:
149+
def __init__(self, trace: List[dict]):
150+
self.trace = [DryrunTraceLine(line) for line in trace]
151+
152+
def get_trace(self) -> List[str]:
153+
return [line.trace_line() for line in self.trace]
154+
155+
156+
class DryrunTraceLine:
157+
def __init__(self, tl):
158+
self.line = tl["line"]
159+
self.pc = tl["pc"]
160+
161+
self.error = ""
162+
if "error" in tl:
163+
self.error = tl["error"]
164+
165+
self.scratch = []
166+
if "scratch" in tl:
167+
self.scratch = [DryrunStackValue(sv) for sv in tl["scratch"]]
168+
169+
self.stack = [DryrunStackValue(sv) for sv in tl["stack"]]
170+
171+
172+
class DryrunStackValue:
173+
def __init__(self, v):
174+
self.type = v["type"]
175+
self.bytes = v["bytes"]
176+
self.int = v["uint"]
177+
178+
def __str__(self) -> str:
179+
if len(self.bytes) > 0:
180+
return "0x" + base64.b64decode(self.bytes).hex()
181+
return str(self.int)
182+
183+
def __eq__(self, other: "DryrunStackValue"):
184+
return (
185+
self.type == other.type
186+
and self.bytes == other.bytes
187+
and self.int == other.int
188+
)
189+
190+
191+
def truncate(s: str, max_width: int) -> str:
192+
if len(s) > max_width and max_width > 0:
193+
return s[:max_width] + "..."
194+
return s
195+
196+
197+
def scratch_to_string(
198+
prev_scratch: List[DryrunStackValue], curr_scratch: List[DryrunStackValue]
199+
) -> str:
200+
if not curr_scratch:
201+
return ""
202+
203+
new_idx = None
204+
for idx in range(len(curr_scratch)):
205+
if idx >= len(prev_scratch):
206+
new_idx = idx
207+
continue
208+
209+
if curr_scratch[idx] != prev_scratch[idx]:
210+
new_idx = idx
211+
212+
if new_idx == None:
213+
return ""
214+
215+
return "{} = {}".format(new_idx, curr_scratch[new_idx])
216+
217+
218+
def stack_to_string(stack: List[DryrunStackValue], reverse: bool) -> str:
219+
if reverse:
220+
stack.reverse()
221+
return "[{}]".format(", ".join([str(sv) for sv in stack]))

algosdk/future/transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,7 +2393,7 @@ def verify(self, message):
23932393
try:
23942394
verify_key.verify(message, subsig.signature)
23952395
verified_count += 1
2396-
except BadSignatureError:
2396+
except (BadSignatureError, ValueError, TypeError):
23972397
return False
23982398

23992399
if verified_count < self.threshold:
@@ -2562,7 +2562,7 @@ def verify(self, public_key):
25622562
try:
25632563
verify_key.verify(to_sign, base64.b64decode(self.sig))
25642564
return True
2565-
except (BadSignatureError, ValueError):
2565+
except (BadSignatureError, ValueError, TypeError):
25662566
return False
25672567

25682568
return self.msig.verify(to_sign)

algosdk/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def verify_bytes(message, signature, public_key):
6969
try:
7070
verify_key.verify(prefixed_message, base64.b64decode(signature))
7171
return True
72-
except BadSignatureError:
72+
except (BadSignatureError, ValueError, TypeError):
7373
return False
7474

7575

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.
2-
black==21.9b0
2+
black==22.3.0
33
glom==20.11.0
44
pytest==6.2.5
55
git+https://github.com/behave/behave

setup.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
description="Algorand SDK in Python",
1010
author="Algorand",
1111
author_email="[email protected]",
12-
version="v1.11.0",
12+
version="v1.12.0",
1313
long_description=long_description,
1414
long_description_content_type="text/markdown",
1515
license="MIT",
1616
project_urls={
1717
"Source": "https://github.com/algorand/py-algorand-sdk",
1818
},
19-
install_requires=["pynacl", "pycryptodomex>=3.6.0", "msgpack"],
19+
install_requires=[
20+
"pynacl>=1.4.0,<2",
21+
"pycryptodomex>=3.6.0,<4",
22+
"msgpack>=1.0.0,<2",
23+
],
2024
packages=setuptools.find_packages(),
2125
python_requires=">=3.5",
2226
package_data={"": ["data/langspec.json"]},

test/steps/steps.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -984,34 +984,34 @@ def add_rekey_to_address(context, rekey):
984984
context.txn.rekey_to = rekey
985985

986986

987-
@given(u'base64 encoded data to sign "{data_enc}"')
987+
@given('base64 encoded data to sign "{data_enc}"')
988988
def set_base64_encoded_data(context, data_enc):
989989
context.data = base64.b64decode(data_enc)
990990

991991

992-
@given(u'program hash "{contract_addr}"')
992+
@given('program hash "{contract_addr}"')
993993
def set_program_hash(context, contract_addr):
994994
context.address = contract_addr
995995

996996

997-
@when(u"I perform tealsign")
997+
@when("I perform tealsign")
998998
def perform_tealsign(context):
999999
context.sig = logic.teal_sign(context.sk, context.data, context.address)
10001000

10011001

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

10071007

1008-
@given(u'base64 encoded program "{program_enc}"')
1008+
@given('base64 encoded program "{program_enc}"')
10091009
def set_program_hash_from_program(context, program_enc):
10101010
program = base64.b64decode(program_enc)
10111011
context.address = logic.address(program)
10121012

10131013

1014-
@given(u'base64 encoded private key "{sk_enc}"')
1014+
@given('base64 encoded private key "{sk_enc}"')
10151015
def set_sk_from_encoded_seed(context, sk_enc):
10161016
seed = base64.b64decode(sk_enc)
10171017
key = SigningKey(seed)

0 commit comments

Comments
 (0)