Repository: algorand/py-algorand-sdk

Issue:
Python SDK should detect the meaningless schema and not msgpack it.
If schemas are defined as 0s a signature validation error occurs. This should be checked as described below:

These tests:
        if self.local_schema:
            d["apls"] = self.local_schema.dictify()
        if self.global_schema:
            d["apgs"] = self.global_schema.dictify()
Should be something like if self.schema and not self.schema.empty():
(with empty() defined to return True if both ints and slices are 0)

Discussion:

Patch:
diff --git a/algosdk/future/transaction.py b/algosdk/future/transaction.py
index ce6e283..155826d 100644
--- a/algosdk/future/transaction.py
+++ b/algosdk/future/transaction.py
@@ -703,8 +703,8 @@ class AssetCreateTxn(AssetConfigTxn):
     """
     def __init__(self, sender, sp, total, decimals,
                  default_frozen, *,
-                 manager, reserve, freeze, clawback,
-                 unit_name, asset_name, url,
+                 manager=None, reserve=None, freeze=None, clawback=None,
+                 unit_name="", asset_name="", url="",
                  metadata_hash=None,
                  note=None, lease=None, rekey_to=None):
         super().__init__(sender=sender, sp=sp, total=total, decimals=decimals,
@@ -1151,8 +1151,8 @@ class ApplicationCallTxn(Transaction):
     @staticmethod
     def state_schema(schema):
         """Confirm the argument is a StateSchema, or false which is coerced to None"""
-        if not schema:
-            return None         # Coerce false values to None, to help __eq__
+        if not schema or not schema.dictify():
+            return None         # Coerce false/empty values to None, to help __eq__
         assert isinstance(schema, StateSchema), f"{schema} is not a StateSchema"
         return schema
 
diff --git a/algosdk/v2client/algod.py b/algosdk/v2client/algod.py
index 67c6bd2..322d8fa 100644
--- a/algosdk/v2client/algod.py
+++ b/algosdk/v2client/algod.py
@@ -169,6 +169,8 @@ class AlgodClient:
         Returns:
             str: transaction ID
         """
+        assert not isinstance(txn, future.transaction.Transaction), \
+            f"Attempt to send UNSIGNED transaction {txn}"
         return self.send_raw_transaction(encoding.msgpack_encode(txn),
                                          **kwargs)
 
@@ -243,6 +245,8 @@ class AlgodClient:
         """
         serialized = []
         for txn in txns:
+            assert not isinstance(txn, future.transaction.Transaction), \
+                f"Attempt to send UNSIGNED transaction {txn}"
             serialized.append(base64.b64decode(encoding.msgpack_encode(txn)))
 
         return self.send_raw_transaction(base64.b64encode(


Test patch:
diff --git a/test_unit.py b/test_unit.py
index 7f30d64..a0fa345 100644
--- a/test_unit.py
+++ b/test_unit.py
@@ -852,7 +852,25 @@ class TestApplicationTransactions(unittest.TestCase):
             self.assertEqual(create.dictify(), call.dictify())
             self.assertEqual(create, call)
             self.assertEqual(call, create)
-
+    
+    def test_application_create_schema(self):
+        approve = b"\0"
+        clear = b"\1"
+        zero_schema = transaction.StateSchema(0, 0)
+        params = transaction.SuggestedParams(0, 1, 100, self.genesis)
+        for oc in transaction.OnComplete:
+            # verify that a schema with 0 uints and 0 bytes behaves the same as no schema
+            txn_zero_schema = transaction.ApplicationCreateTxn(self.sender, params, oc,
+                                                      approve, clear,
+                                                      zero_schema, zero_schema)
+            txn_none_schema = transaction.ApplicationCreateTxn(self.sender, params, oc,
+                                                      approve, clear,
+                                                      None, None)
+            # Check the dict first, it's important on it's own, and it
+            # also gives more a meaningful error if they're not equal.
+            self.assertEqual(txn_zero_schema.dictify(), txn_none_schema.dictify())
+            self.assertEqual(txn_zero_schema, txn_none_schema)
+            self.assertEqual(txn_none_schema, txn_zero_schema)
 
     def test_application_update(self):
         empty = b""