Skip to content

Commit 3b231ac

Browse files
committed
Merge branch 'release/v1.20.0'
2 parents 671e136 + c2e9b23 commit 3b231ac

16 files changed

+630
-78
lines changed

CHANGELOG.md

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

3+
# v1.20.0
4+
5+
## What's Changed
6+
### New Features
7+
* Boxes: Add support for Boxes by @algochoi in https://github.com/algorand/py-algorand-sdk/pull/348
8+
* `class StateSchema`'s method `undictify()` now returns a `StateSchema` object instead of a python `dict`
9+
10+
**Full Changelog**: https://github.com/algorand/py-algorand-sdk/compare/v1.19.0...v1.20.0
11+
12+
# v1.19.0
13+
14+
## What's Changed
15+
### Enhancements
16+
* REST API: Add algod block hash endpoint, add indexer block header-only param. by @winder in https://github.com/algorand/py-algorand-sdk/pull/390
17+
18+
**Full Changelog**: https://github.com/algorand/py-algorand-sdk/compare/v1.18.0...v1.19.0
19+
320
# v1.18.0
421
### Enhancements
522
* Deprecation: Add deprecation warnings on v1 algod API and old transaction format by @algochoi in https://github.com/algorand/py-algorand-sdk/pull/381

algosdk/atomic_transaction_composer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from abc import ABC, abstractmethod
21
import base64
32
import copy
3+
from abc import ABC, abstractmethod
44
from enum import IntEnum
5-
from typing import Any, List, Optional, TypeVar, Union
5+
from typing import Any, List, Optional, Tuple, TypeVar, Union
66

77
from algosdk import abi, error
88
from algosdk.abi.address_type import AddressType
@@ -173,6 +173,7 @@ def add_method_call(
173173
note: bytes = None,
174174
lease: bytes = None,
175175
rekey_to: str = None,
176+
boxes: List[Tuple[int, bytes]] = None,
176177
) -> "AtomicTransactionComposer":
177178
"""
178179
Add a smart contract method call to this atomic group.
@@ -210,6 +211,7 @@ def add_method_call(
210211
with the same sender and lease can be confirmed in this
211212
transaction's valid rounds
212213
rekey_to (str, optional): additionally rekey the sender to this address
214+
boxes (list[(int, bytes)], optional): list of tuples specifying app id and key for boxes the app may access
213215
214216
"""
215217
if self.status != AtomicTransactionComposerStatus.BUILDING:
@@ -259,6 +261,7 @@ def add_method_call(
259261
accounts = accounts[:] if accounts else []
260262
foreign_apps = foreign_apps[:] if foreign_apps else []
261263
foreign_assets = foreign_assets[:] if foreign_assets else []
264+
boxes = boxes[:] if boxes else []
262265

263266
app_args = []
264267
raw_values = []
@@ -350,6 +353,7 @@ def add_method_call(
350353
lease=lease,
351354
rekey_to=rekey_to,
352355
extra_pages=extra_pages,
356+
boxes=boxes,
353357
)
354358
txn_with_signer = TransactionWithSigner(method_txn, signer)
355359
txn_list.append(txn_with_signer)

algosdk/box_reference.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from collections import OrderedDict
2+
from typing import List, Tuple, Union
3+
4+
from algosdk import encoding, error
5+
6+
7+
class BoxReference:
8+
"""
9+
Represents a box reference with a foreign app index and the box name.
10+
11+
Args:
12+
app_index (int): index of the application in the foreign app array
13+
name (bytes): key for the box in bytes
14+
"""
15+
16+
def __init__(self, app_index: int, name: bytes):
17+
if app_index < 0:
18+
raise ValueError(
19+
f"Box app index must be a non-negative integer: {app_index}"
20+
)
21+
self.app_index = app_index
22+
self.name = name
23+
24+
@staticmethod
25+
def translate_box_reference(
26+
ref: Tuple[int, Union[bytes, bytearray, str, int]],
27+
foreign_apps: List[int],
28+
this_app_id: int,
29+
) -> "BoxReference":
30+
# Try checking reference id and name type.
31+
ref_id, ref_name = ref[0], encoding.encode_as_bytes(ref[1])
32+
if not isinstance(ref_id, int):
33+
raise TypeError("Box reference ID must be an int")
34+
35+
index = 0
36+
try:
37+
# Foreign apps start from index 1; index 0 is its own app ID.
38+
index = foreign_apps.index(ref_id) + 1
39+
except (ValueError, AttributeError):
40+
# Check if the app referenced is itself after checking the
41+
# foreign apps array (in case its own app id is in its own
42+
# foreign apps array).
43+
if ref_id != 0 and ref_id != this_app_id:
44+
raise error.InvalidForeignIndexError(
45+
f"Box ref with appId {ref_id} not in foreign-apps"
46+
)
47+
return BoxReference(index, ref_name)
48+
49+
@staticmethod
50+
def translate_box_references(
51+
references: List[Tuple[int, Union[bytes, bytearray, str, int]]],
52+
foreign_apps: List[int],
53+
this_app_id: int,
54+
) -> List["BoxReference"]:
55+
"""
56+
Translates a list of tuples with app IDs and names into an array of
57+
BoxReferences with foreign indices.
58+
59+
Args:
60+
references (list[(int, bytes)]): list of tuples specifying app id
61+
and key for boxes the app may access
62+
foreign_apps (list[int]): list of other applications in appl call
63+
this_app_id (int): app ID of the box being references
64+
"""
65+
if not references:
66+
return []
67+
68+
return [
69+
BoxReference.translate_box_reference(
70+
ref, foreign_apps, this_app_id
71+
)
72+
for ref in references
73+
]
74+
75+
def dictify(self):
76+
d = dict()
77+
if self.app_index:
78+
d["i"] = self.app_index
79+
if self.name:
80+
d["n"] = self.name
81+
od = OrderedDict(sorted(d.items()))
82+
return od
83+
84+
@staticmethod
85+
def undictify(d):
86+
return BoxReference(
87+
d["i"] if "i" in d else 0,
88+
d["n"] if "n" in d else b"",
89+
)
90+
91+
def __eq__(self, other):
92+
if not isinstance(other, BoxReference):
93+
return False
94+
return self.app_index == other.app_index and self.name == other.name

algosdk/encoding.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import base64
22
import warnings
33
from collections import OrderedDict
4+
from typing import Union
45

56
import msgpack
67
from Cryptodome.Hash import SHA512
78

8-
from . import auction, constants, error, future, transaction
9+
from algosdk import auction, constants, error, future, transaction
910

1011

1112
def msgpack_encode(obj):
@@ -246,3 +247,17 @@ def checksum(data):
246247
chksum = SHA512.new(truncate="256")
247248
chksum.update(data)
248249
return chksum.digest()
250+
251+
252+
def encode_as_bytes(
253+
e: Union[bytes, bytearray, str, int]
254+
) -> Union[bytes, bytearray]:
255+
"""Confirm or coerce element to bytes."""
256+
if isinstance(e, (bytes, bytearray)):
257+
return e
258+
if isinstance(e, str):
259+
return e.encode()
260+
if isinstance(e, int):
261+
# Uses 8 bytes, big endian to match TEAL's btoi
262+
return e.to_bytes(8, "big") # raises for negative or too big
263+
raise TypeError("{} is not bytes, bytearray, str, or int".format(e))

algosdk/error.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ def __init__(self, msg):
224224
super().__init__(msg)
225225

226226

227+
class InvalidForeignIndexError(Exception):
228+
def __init__(self, msg):
229+
super().__init__(msg)
230+
231+
227232
class SourceMapVersionError(Exception):
228233
def __init__(self, version):
229234
Exception.__init__(

0 commit comments

Comments
 (0)