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

Dev Tools: Source map decoder #353

Merged
merged 38 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
240759e
Merge branch 'release/v1.10.0b1'
algobarb Feb 15, 2022
9d5ec1b
Merge branch 'release/v1.10.0'
onetechnical Mar 2, 2022
23354e9
Merge branch 'release/v1.11.0b1'
onetechnical Mar 18, 2022
51e0364
Merge branch 'release/v1.11.0'
algobarb Mar 23, 2022
e6f5aa9
Merge branch 'release/v1.12.0'
algojack Apr 21, 2022
896d2aa
Merge branch 'release/v1.13.0'
onetechnical May 2, 2022
de80435
Merge branch 'release/v1.13.1'
onetechnical May 4, 2022
760d892
Merge branch 'release/v1.14.0'
onetechnical Jun 2, 2022
671aeb6
Merge branch 'release/v1.15.0'
algojack Jun 16, 2022
c03a53d
adding src map decoder
barnjamin Jun 20, 2022
cb7a735
fmt
barnjamin Jun 20, 2022
5a1bf5d
reorg, fmt, add comments
barnjamin Jun 20, 2022
5ca54c6
Merge branch 'src-map-decoder' into develop
barnjamin Jun 20, 2022
5bc92ab
remove comments from src map
barnjamin Jun 23, 2022
f547df8
move SourceMap to its own file, add source_map arg in client for requ…
barnjamin Jun 24, 2022
a617d2c
adding tests for parsing source map from file
barnjamin Jun 24, 2022
abbf604
Merge branch 'develop' of github.com:algorand/py-algorand-sdk into de…
barnjamin Jun 24, 2022
58e60f0
Merge branch 'develop' into src-map-decoder
barnjamin Jun 24, 2022
a968807
fmt
barnjamin Jun 24, 2022
92d197c
implement new test
barnjamin Jun 24, 2022
d4618d6
fmt
barnjamin Jun 24, 2022
d0ec5c1
cr
barnjamin Jun 24, 2022
90333ca
cr
barnjamin Jun 24, 2022
c217a96
Merge branch 'src-map-decoder' of github.com:algorand/py-algorand-sdk…
barnjamin Jun 24, 2022
fe2aef6
cr changes, mostly naming fixes
barnjamin Jun 27, 2022
1d4c007
removing delimited, adding exception if version != 3, checking for ma…
barnjamin Jun 28, 2022
8ed7979
updating to new format
barnjamin Jul 1, 2022
94b59c0
remove initialized 0s
barnjamin Jul 1, 2022
be41089
fmt
barnjamin Jul 1, 2022
14b6658
Update algosdk/source_map.py
barnjamin Jul 1, 2022
7c4309c
use on dict lookup, returning None if not set
barnjamin Jul 11, 2022
ab087ff
Merge branch 'src-map-decoder' of github.com:algorand/py-algorand-sdk…
barnjamin Jul 11, 2022
ece278c
adding commments about None line and tweaking logic to append to the …
barnjamin Jul 11, 2022
689ad74
Update algosdk/source_map.py
barnjamin Jul 11, 2022
5a72f99
adding new tests
barnjamin Jul 12, 2022
e5a3ee8
Merge branch 'src-map-decoder' of github.com:algorand/py-algorand-sdk…
barnjamin Jul 12, 2022
0339df9
fmt
barnjamin Jul 12, 2022
c7cd30a
revert to master
barnjamin Jul 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.abijson.byname or @unit.algod or @unit.algod.ledger_refactoring or @unit.applications or @unit.atc_method_args 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"
UNITS = "@unit.abijson or @unit.abijson.byname or @unit.algod or @unit.algod.ledger_refactoring or @unit.applications or @unit.atc_method_args 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 or @unit.sourcemap.pctoline"
unit:
behave --tags=$(UNITS) tests -f progress2

barnjamin marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
1 change: 1 addition & 0 deletions algosdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
from . import v2client
from . import wallet
from . import wordlist
from . import source_map

name = "algosdk"
2 changes: 2 additions & 0 deletions algosdk/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import json
import os

michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
from typing import Tuple, Dict, List, Any
barnjamin marked this conversation as resolved.
Show resolved Hide resolved

from . import constants
from . import error
from . import encoding
Expand Down
97 changes: 97 additions & 0 deletions algosdk/source_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from typing import Dict, Any, List, Tuple


class SourceMap:
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
"""
Decodes a VLQ-encoded source mapping between PC values and TEAL source code lines.
Spec available here: https://sourcemaps.info/spec.html

Args:
source_map (dict(str, Any)): source map JSON from algod
delimiter (str, optional): delimiter for mappings
"""

def __init__(self, source_map: Dict[str, Any], delimiter: str = ";"):
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
self.delimter = delimiter

self.version: int = source_map["version"]
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
self.sources: List[str] = source_map["sources"]
self.mapping: str = source_map["mapping"]
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved

pc_list = [
_decode_int_value(raw_val)
for raw_val in self.mapping.split(delimiter)
]

# Initialize with 0,0 for pc/line
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
self.pc_to_line: Dict[int, int] = {0: 0}
self.line_to_pc: Dict[int, List[int]] = {0: [0]}
barnjamin marked this conversation as resolved.
Show resolved Hide resolved

last_line = 0
for index, line_num in enumerate(pc_list):
if line_num is not None: # be careful for '0' checks!
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
if line_num not in self.line_to_pc:
self.line_to_pc[line_num] = []
self.line_to_pc[line_num].append(index)
last_line = line_num

self.pc_to_line[index] = last_line

def get_line_for_pc(self, pc: int) -> int:
return self.pc_to_line[pc]
barnjamin marked this conversation as resolved.
Show resolved Hide resolved

def get_pcs_for_line(self, line: int) -> List[int]:
return self.line_to_pc[line]


def _decode_int_value(value: str) -> int:
# Mappings may have up to 5 segments:
# Third segment represents the zero-based starting line in the original source represented.
decoded_value = base64vlq_decode(value)
return decoded_value[2] if decoded_value else None
barnjamin marked this conversation as resolved.
Show resolved Hide resolved


"""
Source taken from: https://gist.github.com/mjpieters/86b0d152bb51d5f5979346d11005588b
"""

barnjamin marked this conversation as resolved.
Show resolved Hide resolved
_b64chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
_b64table = [None] * (max(_b64chars) + 1)
for i, b in enumerate(_b64chars):
_b64table[b] = i

_shiftsize, _flag, _mask = 5, 1 << 5, (1 << 5) - 1


def base64vlq_decode(vlqval: str) -> Tuple[int]:
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
"""Decode Base64 VLQ value"""
results = []
add = results.append
shiftsize, flag, mask = _shiftsize, _flag, _mask
shift = value = 0
# use byte values and a table to go from base64 characters to integers
for v in map(_b64table.__getitem__, vlqval.encode("ascii")):
value += (v & mask) << shift
if v & flag:
shift += shiftsize
continue
# determine sign and add to results
add((value >> 1) * (-1 if value & 1 else 1))
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
shift = value = 0
return results


def base64vlq_encode(*values: int) -> str:
"""Encode integers to a VLQ value"""
results = []
add = results.append
shiftsize, flag, mask = _shiftsize, _flag, _mask
for v in values:
# add sign bit
v = (abs(v) << 1) | int(v < 0)
while True:
toencode, v = v & mask, v >> shiftsize
add(toencode | (v and flag))
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
if not v:
break
return bytes(map(_b64chars.__getitem__, results)).decode()
6 changes: 3 additions & 3 deletions algosdk/v2client/algod.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def suggested_params(self, **kwargs):
res["min-fee"],
)

def compile(self, source, **kwargs):
def compile(self, source, source_map=False, **kwargs):
"""
Compile TEAL source with remote algod.

Expand All @@ -365,9 +365,9 @@ def compile(self, source, **kwargs):
{"Content-Type": "application/x-binary"},
)
kwargs["headers"] = headers

params = {"sourcemap": source_map}
return self.algod_request(
"POST", req, data=source.encode("utf-8"), **kwargs
"POST", req, params=params, data=source.encode("utf-8"), **kwargs
)

def dryrun(self, drr, **kwargs):
Expand Down
21 changes: 15 additions & 6 deletions tests/steps/other_v2_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@
from glom import glom
import parse

from algosdk import (
dryrun_results,
encoding,
error,
mnemonic,
)
from algosdk import dryrun_results, encoding, error, mnemonic, source_map
from algosdk.error import AlgodHTTPError
from algosdk.future import transaction
from algosdk.v2client import *
Expand Down Expand Up @@ -1707,3 +1702,17 @@ def glom_app_eval_delta(context, i, path, field):
assert field == str(
actual_field
), f"path [{path}] expected value [{field}] but got [{actual_field}] instead"


@given('a source map json file "{sourcemap_file}"')
def parse_source_map(context, sourcemap_file):
jsmap = json.loads(load_resource(sourcemap_file, is_binary=False))
context.source_map = source_map.SourceMap(jsmap)


@then('the string composed of pc:line number equals "{pc_to_line}"')
def check_source_map(context, pc_to_line):
buff = [
f"{pc}:{line}" for pc, line in context.source_map.pc_to_line.items()
]
assert ";".join(buff) == pc_to_line