Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Deprecating use of langspec #371

Merged
merged 15 commits into from
Aug 26, 2022
Merged
3 changes: 2 additions & 1 deletion .test-env
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Configs for testing repo download:
SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing"
SDK_TESTING_BRANCH="master"
# TODO revert back to "master" after sdk-testing is merged
SDK_TESTING_BRANCH="deprecate-langspec"
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
SDK_TESTING_HARNESS="test-harness"

VERBOSE_HARNESS=0
Expand Down
5 changes: 2 additions & 3 deletions algosdk/future/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2505,8 +2505,7 @@ class LogicSig:
"""

def __init__(self, program, args=None):
if not program or not logic.check_program(program, args):
raise error.InvalidProgram()
logic._sanity_check_program(program)
self.logic = program
self.args = args
self.sig = None
Expand Down Expand Up @@ -2548,7 +2547,7 @@ def verify(self, public_key):
return False

try:
logic.check_program(self.logic, self.args)
logic._sanity_check_program(self.logic)
except error.InvalidProgram:
return False

Expand Down
88 changes: 88 additions & 0 deletions algosdk/logic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64
import binascii
import json
import os

Expand All @@ -12,8 +13,54 @@
opcodes = None


def _sanity_check_program(program):
"""
Performs heuristic program validation:
check if passed in bytes are Algorand address, or they are B64 encoded, rather than Teal bytes

Args:
program (bytes): compiled program
"""

def is_ascii_printable(program_bytes):
return all(
map(
lambda x: x == ord("\n") or (ord(" ") <= x <= ord("~")),
program_bytes,
)
)

if not program:
raise error.InvalidProgram("empty program")

if is_ascii_printable(program):
try:
encoding.decode_address(program.decode("utf-8"))
raise error.InvalidProgram(
"requesting program bytes, get Algorand address"
)
except error.WrongChecksumError:
pass
except error.WrongKeyLengthError:
pass

try:
base64.b64decode(program.decode("utf-8"))
raise error.InvalidProgram("program should not be b64 encoded")
except binascii.Error:
pass

raise error.InvalidProgram(
"program bytes are all ASCII printable characters, not looking like Teal byte code"
)


def check_program(program, args=None):
"""
NOTE: This class is deprecated:
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions.
The behavior of `check_program` relies on `langspec.json`. Thus, this method is being deprecated.

Performs program checking for max length and cost

Args:
Expand All @@ -31,6 +78,12 @@ def check_program(program, args=None):


def read_program(program, args=None):
"""
NOTE: This class is deprecated:
Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions.
The behavior of `read_program` relies on `langspec.json`. Thus, this method is being deprecated.
"""

global spec, opcodes
intcblock_opcode = 32
bytecblock_opcode = 38
Expand Down Expand Up @@ -107,11 +160,17 @@ def read_program(program, args=None):


def check_int_const_block(program, pc):
"""
NOTE: This class is deprecated
"""
size, _ = read_int_const_block(program, pc)
return size


def read_int_const_block(program, pc):
"""
NOTE: This class is deprecated
"""
size = 1
ints = []
num_ints, bytes_used = parse_uvarint(program[pc + size :])
Expand All @@ -134,11 +193,17 @@ def read_int_const_block(program, pc):


def check_byte_const_block(program, pc):
"""
NOTE: This class is deprecated
"""
size, _ = read_byte_const_block(program, pc)
return size


def read_byte_const_block(program, pc):
"""
NOTE: This class is deprecated
"""
size = 1
bytearrays = []
num_ints, bytes_used = parse_uvarint(program[pc + size :])
Expand All @@ -164,11 +229,17 @@ def read_byte_const_block(program, pc):


def check_push_int_block(program, pc):
"""
NOTE: This class is deprecated
"""
size, _ = read_push_int_block(program, pc)
return size


def read_push_int_block(program, pc):
"""
NOTE: This class is deprecated
"""
size = 1
single_int, bytes_used = parse_uvarint(program[pc + size :])
if bytes_used <= 0:
Expand All @@ -180,11 +251,17 @@ def read_push_int_block(program, pc):


def check_push_byte_block(program, pc):
"""
NOTE: This class is deprecated
"""
size, _ = read_push_byte_block(program, pc)
return size


def read_push_byte_block(program, pc):
"""
NOTE: This class is deprecated
"""
size = 1
item_len, bytes_used = parse_uvarint(program[pc + size :])
if bytes_used <= 0:
Expand All @@ -200,6 +277,9 @@ def read_push_byte_block(program, pc):


def parse_uvarint(buf):
"""
NOTE: This class is deprecated
"""
x = 0
s = 0
for i, b in enumerate(buf):
Expand All @@ -215,6 +295,8 @@ def parse_uvarint(buf):

def address(program):
"""
NOTE: This class is deprecated

Return the address of the program.

Args:
Expand All @@ -230,6 +312,8 @@ def address(program):

def teal_sign(private_key, data, contract_addr):
"""
NOTE: This class is deprecated

Return the signature suitable for ed25519verify TEAL opcode

Args:
Expand All @@ -254,6 +338,8 @@ def teal_sign(private_key, data, contract_addr):

def teal_sign_from_program(private_key, data, program):
"""
NOTE: This class is deprecated

Return the signature suitable for ed25519verify TEAL opcode

Args:
Expand All @@ -270,6 +356,8 @@ def teal_sign_from_program(private_key, data, program):

def get_application_address(appID: int) -> str:
"""
NOTE: This class is deprecated

Return the escrow address of an application.

Args:
Expand Down
2 changes: 1 addition & 1 deletion algosdk/testing/dryrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ def build_dryrun_request(
):
"""
Helper function for creation DryrunRequest object from a program.
By default it uses logic sig mode
By default, it uses logic sig mode
and if app_idx / on_complete are set then application call is made

Args:
Expand Down
5 changes: 2 additions & 3 deletions algosdk/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1318,8 +1318,7 @@ class LogicSig:
"""

def __init__(self, program, args=None):
if not program or not logic.check_program(program, args):
raise error.InvalidProgram()
logic._sanity_check_program(program)
self.logic = program
self.args = args
self.sig = None
Expand Down Expand Up @@ -1361,7 +1360,7 @@ def verify(self, public_key):
return False

try:
logic.check_program(self.logic, self.args)
logic._sanity_check_program(self.logic)
except error.InvalidProgram:
return False

Expand Down
37 changes: 36 additions & 1 deletion tests/steps/steps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import random
import time
import parse

from algosdk import (
account,
Expand All @@ -14,9 +15,18 @@
wallet,
)
from algosdk.future import transaction
from behave import given, then, when
from behave import given, then, when, register_type
from nacl.signing import SigningKey


@parse.with_pattern(r".*")
def parse_string(text):
return text


register_type(MaybeString=parse_string)


token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
algod_port = 60000
kmd_port = 60001
Expand Down Expand Up @@ -984,3 +994,28 @@ def buildTxn(t, sender, params):
elif "nonparticipation" in t:
txn = transaction.KeyregNonparticipatingTxn(sender, params)
return txn


@given(
'a base64 encoded program bytes for heuristic sanity check "{b64encoded:MaybeString}"'
)
def take_b64_encoded_bytes(context, b64encoded):
context.seemingly_program = base64.b64decode(b64encoded)


@when("I start heuristic sanity check over the bytes")
def heuristic_check_over_bytes(context):
context.sanity_check_err = ""

try:
transaction.LogicSig(context.seemingly_program)
except Exception as e:
context.sanity_check_err = str(e)


@then('if there exists an error, the error contains "{err_msg:MaybeString}"')
def check_error_if_matching(context, err_msg: str = None):
if len(err_msg) > 0:
assert err_msg in context.sanity_check_err
else:
assert len(context.sanity_check_err) == 0
3 changes: 2 additions & 1 deletion tests/unit.tags
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@unit.indexer.ledger_refactoring
@unit.indexer.logs
@unit.offline
@unit.program_sanity_check
@unit.rekey
@unit.responses
@unit.responses.231
Expand All @@ -20,4 +21,4 @@
@unit.tealsign
@unit.transactions
@unit.transactions.keyreg
@unit.transactions.payment
@unit.transactions.payment