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

Prepare for Testing the ABI-Router in PyTeal #49

Merged
merged 129 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 119 commits
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
4b78ce2
with cost no longer coming back, and the coming need to support simul…
Dec 22, 2022
d28308b
remove error prone method Invariant.inputs_and_invariants
Dec 22, 2022
f6f301a
need to tranform via `Invariant.as_invariants()`
Dec 22, 2022
026926f
more control over validation and whether a method selector is provide…
Dec 24, 2022
6ed2789
deprecate the majority of the original remaning dryrun.py
Dec 24, 2022
df452d5
small typing fix + EncodingType
Dec 24, 2022
fa09d15
type ignores
Dec 26, 2022
26d47e3
Small type fix (#43)
tzaffi Dec 26, 2022
b92911c
changelog
Dec 27, 2022
9b18a63
good place to commit as passing tests (except one error in mypy)
Dec 30, 2022
a573529
another good place to stop
Dec 30, 2022
b3e2ce3
DryRunInspector typing
Dec 30, 2022
9499253
passing
Dec 30, 2022
34801b3
green stop
Dec 30, 2022
9dcc532
orphan `execute_one_dryryrun` while still passing
Dec 30, 2022
9ea7247
refactor orphans dryrun_logicsig()
Dec 31, 2022
e312964
DryRunTranaactionParams.for_app()
Dec 31, 2022
dccdc74
Refactor orphans DryRunExecution.dryrun_app()
Dec 31, 2022
37c1e5b
Refactor orphans DryRunExecutor.dryrun_logicsig_on_sequence()
Dec 31, 2022
1cee24c
refactor orphans DryRunExecutor.dryrun_app_on_sequence()
Dec 31, 2022
854089b
allow maps as well as list for multiple inputs
Dec 31, 2022
ee89949
Refactor orphans dryrun_app_pair_on_sequence
Dec 31, 2022
e67f692
deprecated section
Dec 31, 2022
faf824c
deprecated_ prefix
Dec 31, 2022
b015ec3
deprecated_ prefix
Dec 31, 2022
e69ef17
remove deprecated
Dec 31, 2022
c032a16
convenience methods run_one and run_sequence
Dec 31, 2022
96bb5fd
v 0.7.0
Dec 31, 2022
1ec5213
remove unused DryRunnable
Dec 31, 2022
a6277fc
trim stale comments
Dec 31, 2022
9d2d4ec
better typing in csv_report()
Dec 31, 2022
a9f9984
prefer run_one/sequnce over bare run()
Dec 31, 2022
0818c16
prefer run_one/sequnce over bare run()
Dec 31, 2022
9e92339
Merge remote-tracking branch 'algorand/api-tightening' into exec-mult…
Dec 31, 2022
ca1a454
prefer run_one/sequence over plain run()
Dec 31, 2022
8b9c6c9
changelog
Dec 31, 2022
403278b
notebook as well
Jan 1, 2023
95f767c
addressing #38 mostly
Jan 1, 2023
87152ac
addressing #38 mostly
Jan 1, 2023
a68208e
test CI on py 3.11 as well
Jan 1, 2023
bb99b2d
Update CHANGELOG.md
tzaffi Jan 3, 2023
4015202
Update graviton/blackbox.py
tzaffi Jan 3, 2023
7aed927
remove todo's
Jan 3, 2023
6a82ebb
Merge branch 'main' into exec-multi-dispatch
Jan 3, 2023
cceea26
fix bad merge
Jan 3, 2023
4d4f869
Update CHANGELOG.md
tzaffi Jan 3, 2023
6f8ba8b
version bump
Jan 3, 2023
73a0c78
Test against Py 11 + Reordering of Classes in blackbox.py
Jan 12, 2023
ba1e0ea
break up into inspectory.py
Jan 12, 2023
d4c8c11
finish refactor?
Jan 12, 2023
a6ac0b2
remove commentary
Jan 12, 2023
a264ec7
refer to 48 in changelog
Jan 12, 2023
8d2fa35
Merge branch 'prelude-to-45' into exec-multi-dispatch
Jan 12, 2023
eca2e51
merge in #48
Jan 13, 2023
5e30838
fix bad merge
Jan 13, 2023
8c87d53
Generic `OneOrMany[T]` and privates run
Jan 13, 2023
c08adcc
Update graviton/blackbox.py
tzaffi Jan 13, 2023
14c7d04
Update graviton/blackbox.py
tzaffi Jan 13, 2023
6e993bf
lint
Jan 13, 2023
53f44f1
run the wwinder dockerized algod
Jan 13, 2023
55b0572
faster better integration test environment
Jan 13, 2023
23615c1
better build
Jan 13, 2023
d4fa910
Merge branch 'prelude-to-45' into exec-multi-dispatch
Jan 13, 2023
5752e84
better build
Jan 13, 2023
4ef8ec8
Merge branch 'prelude-to-45' into exec-multi-dispatch
Jan 13, 2023
0fed894
Update .github/workflows/build.yml
tzaffi Jan 13, 2023
f4cb00f
Merge branch 'prelude-to-45' into exec-multi-dispatch
Jan 13, 2023
1da27b1
better build
Jan 13, 2023
41c4b09
Merge branch 'prelude-to-45' into exec-multi-dispatch
Jan 13, 2023
6d8a7e6
Merge branch 'algorand/main' into exec-multi-dispatch
Jan 13, 2023
ec3e059
Apply suggestions from code review
tzaffi Jan 13, 2023
c83018d
Update CHANGELOG.md
tzaffi Jan 13, 2023
ec2446c
changelog
Jan 13, 2023
4ca02c2
changelog
Jan 13, 2023
2e54952
add init typing
Jan 14, 2023
b891a5c
wrong type
Jan 14, 2023
e354e5b
break out ace.py from blackbox.py
Jan 14, 2023
18bf654
small refactorings
Jan 14, 2023
eb586f0
pin to a py-algorand-sdk branch with better type exports
Jan 15, 2023
6b28d01
modernize
Jan 15, 2023
fff32a2
class Simulation
Jan 15, 2023
dabe966
major trimming
Jan 16, 2023
1a5adab
rename ace_test --> abi_router_test and ace -> abi_args_strategy
Jan 16, 2023
ef00b14
removing validate_inputs() and run_sequence() from abi_args_ strateg…
Jan 16, 2023
0e4ec69
ready for pyteal
Jan 16, 2023
b91f9ce
removing
Jan 16, 2023
98f9032
addressed comment about untested pathway
Jan 17, 2023
96a5e28
addressed comment about untested pathway
Jan 17, 2023
b43c66a
Merge branch 'exec-multi-dispatch' into ace-for-pyteal
Jan 17, 2023
4dfdcde
Update tests/unit/encode_test.py
tzaffi Jan 17, 2023
1945d85
Merge remote-tracking branch 'origin/exec-multi-dispatch' into ace-fo…
Jan 17, 2023
7bf103d
correct a comment
Jan 17, 2023
4c93051
Update tests/unit/encode_test.py
tzaffi Jan 17, 2023
abaeaf8
move ABIMethodCallStrategy into ab_strategy.py
Jan 17, 2023
31fc8c0
test AbiMethodCallStrategy's constructor
Jan 17, 2023
14045b8
Merge branch 'exec-multi-dispatch' into ace-for-pyteal
Jan 17, 2023
17fd8ac
improve after CR feedback
Jan 17, 2023
a316388
per CR: No need for `Optional[EncodingType]` as it already unions in …
Jan 17, 2023
0f7a363
rename obsolete objects in commentary
Jan 17, 2023
8bbd71f
Merge branch 'exec-multi-dispatch' into ace-for-pyteal
Jan 17, 2023
203e196
improve per CR observation
Jan 17, 2023
aede5ed
Merge branch 'exec-multi-dispatch' into ace-for-pyteal
Jan 17, 2023
d329fe8
better comment
Jan 17, 2023
97e2394
incorporate suggestion in CR
Jan 17, 2023
f7814c3
Merge branch 'exec-multi-dispatch' into ace-for-pyteal
Jan 17, 2023
3056799
Merge branch 'algorand/main' into ace-for-pyteal
Jan 17, 2023
862335b
ABIMethodCallStrategy doesn't actually need any teal!
Jan 17, 2023
f0dcd9d
reduce API surface area and CHANGELOG
Jan 17, 2023
6495cb3
ABIMethodCallStrategy -> ABICallStrategy bc we can't forget about bar…
Jan 18, 2023
4f8ef35
point to py-algorand-sdk@develop
Jan 18, 2023
77f92f4
DryRunTransactionParams.update()
Jan 19, 2023
2167435
unfreeze
Jan 19, 2023
1d53eb7
revert to pipy's py-algorand-sdk
Jan 19, 2023
94a7973
Update CHANGELOG.md
tzaffi Feb 2, 2023
d8541f2
improve update per CR suggestoin + add/refactor unit tests
Feb 2, 2023
9a735a5
better comment
Feb 2, 2023
809d544
update -> update_fields
Feb 2, 2023
dfc2f7b
pr CR biggest changes: ABICallStrategy generate + _inputs(), get -> g…
Feb 3, 2023
032f4e3
Apply suggestions from code review
tzaffi Feb 3, 2023
8fb7b7f
change error message expectation for negative router tests
Feb 6, 2023
8f3e4fa
abstract CallStrategy + RandomArgLengthCallStrategyfor clear programs
Feb 6, 2023
95df768
improved typing
Feb 6, 2023
66b6c32
changelog
Feb 6, 2023
ec54e19
report the algod version (with commit hash)
Feb 6, 2023
05443eb
bug fix: dry run errors now in `app-call-messsages`
Feb 6, 2023
f286519
bug fix: dry run errors now in `app-call-messsages`
Feb 6, 2023
d42f18a
typo
Feb 6, 2023
371b248
comment per CR discussion
Feb 6, 2023
a1f5b37
changelog
Feb 6, 2023
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
<!-- markdownlint-disable MD024 -->
# Changelog

## `v0.9.0` (_aka_ 🐐)

### Breaking changes

* `class ABIContractExecutor` renamed to `ABICallStrategy` and moved from `graviton/blackbox.py` to `graviton/abi_strategy.py`. Some of the methods have been renamed as well.

### Added
* `class Simulation` in `graviton/sim.py` unifies the ability to run an argument strategy and check that invariants hold using its `run_and_assert()` method
* `class DryRunTransactionParameters` has a new method `update_fields()`

## `v0.8.0` (_aka_ 🦛)

Expand Down
182 changes: 181 additions & 1 deletion graviton/abi_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,28 @@
TODO: Leverage Hypothesis!
"""
from abc import ABC, abstractmethod
from enum import Enum, auto
import random
import string
from typing import List, Optional, Sequence, cast
from typing import List, Optional, Sequence, Type, cast

from algosdk import abi, encoding


from graviton.models import PyTypes


class ABIStrategy(ABC):
"""
TODO: when incorporating hypothesis strategies, we'll need a more holistic
approach that looks at relationships amongst various args.
Current approach only looks at each argument as a completely independent entity.
Comment on lines +20 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no action needed, I see hypothesis cross the PR, together with TODO, are you referring to hypothesis https://hypothesis.readthedocs.io/en/latest/data.html?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""

@abstractmethod
def __init__(self, abi_instance: abi.ABIType, dynamic_length: Optional[int] = None):
pass

@abstractmethod
def get(self) -> PyTypes:
pass
Expand Down Expand Up @@ -167,6 +179,15 @@ def address_logic(x):


class RandomABIStrategyHalfSized(RandomABIStrategy):
"""
This strategy only generates data that is half the size that _ought_ to be possible.
This is useful in the case that operations involving the generated arguments
could overflow due to multiplication.

Since this only makes sense for `abi.UintType`, it degenerates to the standard
`RandomABIStrategy` for other types.
"""

def __init__(
self,
abi_instance: abi.ABIType,
Expand All @@ -183,3 +204,162 @@ def get(self) -> PyTypes:
return cast(int, full_random) % (
1 << (cast(abi.UintType, self.abi_type).bit_size // 2)
)


class ABIArgsMod(Enum):
selector_byte_insert = auto()
selector_byte_delete = auto()
selector_byte_replace = auto()
parameter_delete = auto()
parameter_append = auto()


class ABICallStrategy:
"""
TODO: refactor to comport with ABIStrategy + Hypothesis
TODO: make this generic on the strategy type
"""

append_args_type: abi.ABIType = abi.ByteType()

def __init__(
self,
contract: str,
argument_strategy: Type[ABIStrategy] = RandomABIStrategy,
*,
num_dryruns: int = 1,
handle_selector: bool = True,
abi_args_mod: Optional[ABIArgsMod] = None,
):
"""
contract - ABI Contract JSON

argument_strategy (default=RandomABIStrategy) - ABI strategy for generating arguments

num_dry_runs (default=1) - the number of dry runs to run
(generates different inputs each time)

handle_selector (default=True) - usually we'll want to let
`ABIContractExecutor.run_sequence()`
handle adding the method selector so this param.
But if set False: when providing `inputs`
ensure that the 0'th argument for method calls is the selector.
And when set True: when NOT providing `inputs`, the selector arg
at index 0 will be added automatically.

abi_args_mod (optional) - when desiring to mutate the args, provide an ABIArgsMod value
"""
self.contract: abi.Contract = abi.Contract.from_json(contract)
self.argument_strategy: Type[ABIStrategy] = argument_strategy
self.num_dryruns = num_dryruns
self.handle_selector = handle_selector
self.abi_args_mod = abi_args_mod

def abi_method(self, method: Optional[str]) -> abi.Method:
assert method, "cannot get abi.Method for bare app call"

return self.contract.get_method_by_name(method)

def method_signature(self, method: Optional[str]) -> Optional[str]:
"""Returns None, for a bare app call (method=None signals this)"""
if method is None:
return None

return self.abi_method(method).get_signature()

def method_selector(self, method: Optional[str]) -> bytes:
assert method, "cannot get method_selector for bare app call"

return self.abi_method(method).get_selector()

def argument_types(self, method: Optional[str]) -> List[abi.ABIType]:
"""
Argument types (excluding selector)
"""
if method is None:
return []

return [cast(abi.ABIType, arg.type) for arg in self.abi_method(method).args]

def num_args(self, method: Optional[str]) -> int:
return len(self.argument_types(method))

def generate_inputs(self, method: Optional[str]) -> List[Sequence[PyTypes]]:
"""
Generates inputs appropriate for bare app calls and method calls
according to available argument_strategy.
"""
assert (
self.argument_strategy
), "cannot generate inputs without an argument_strategy"

mutating = self.abi_args_mod is not None

if not (method or mutating):
# bare calls receive no arguments (unless mutating)
return [tuple() for _ in range(self.num_dryruns)]

arg_types = self.argument_types(method)

prefix: List[bytes] = []
if self.handle_selector and method:
prefix = [self.method_selector(method)]

modify_selector = False
if (action := self.abi_args_mod) in (
ABIArgsMod.selector_byte_delete,
ABIArgsMod.selector_byte_insert,
ABIArgsMod.selector_byte_replace,
):
assert (
prefix
), f"{self.abi_args_mod=} which means we need to modify the selector, but we don't have one available to modify"
tzaffi marked this conversation as resolved.
Show resolved Hide resolved
modify_selector = True

def selector_mod(prefix):
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
assert isinstance(prefix, list) and len(prefix) <= 1
if not (prefix and modify_selector):
return prefix

selector = prefix[0]
idx = random.randint(0, 4)
x, y = selector[:idx], selector[idx:]
if action == ABIArgsMod.selector_byte_insert:
selector = x + random.randbytes(1) + y
elif action == ABIArgsMod.selector_byte_delete:
selector = (x[:-1] + y) if x else y[:-1]
else:
assert (
action == ABIArgsMod.selector_byte_replace
), f"expected action={ABIArgsMod.selector_byte_replace} but got [{action}]"
idx = random.randint(0, 3)
selector = (
selector[:idx]
+ bytes([(selector[idx] + 1) % 256])
+ selector[idx + 1 :]
)
return [selector]

def args_mod(args):
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
if action not in (ABIArgsMod.parameter_append, ABIArgsMod.parameter_delete):
return args

if action == ABIArgsMod.parameter_delete:
return args if not args else tuple(args[:-1])

assert action == ABIArgsMod.parameter_append
return args + (self.generate_value(self.append_args_type),)

def gen_args():
# TODO: when incorporating hypothesis strategies, we'll need a more holistic
# approach that looks at relationships amongst various args
args = tuple(
selector_mod(prefix)
+ [self.generate_value(atype) for atype in arg_types]
)
return args_mod(args)

return [gen_args() for _ in range(self.num_dryruns)]

def generate_value(self, gen_type: abi.ABIType) -> PyTypes:
return cast(Type[ABIStrategy], self.argument_strategy)(gen_type).get()
Loading