Skip to content

Commit

Permalink
Semantic Blackbox Testing (#249)
Browse files Browse the repository at this point in the history
Graviton in PyTeal
  • Loading branch information
tzaffi authored Apr 28, 2022
1 parent 95513a3 commit cc86572
Show file tree
Hide file tree
Showing 68 changed files with 2,497 additions and 285 deletions.
3 changes: 1 addition & 2 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ per-file-ignores =
examples/signature/recurring_swap_deploy.py: F403, F405
pyteal/__init__.py: F401, F403
pyteal/ir/ops.py: E221
tests/module_test.py: F401, F403
tests/*.py: I252
tests/unit/module_test.py: F401, F403

# from flake8-tidy-imports
ban-relative-imports = true
53 changes: 51 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
container: python:${{ matrix.python }}
strategy:
matrix:
python: ['3.10']
python: ["3.10"]
steps:
- run: python3 --version
- name: Check out code
Expand All @@ -25,19 +25,68 @@ jobs:
- name: Build and Test
run: make build-and-test

run-integration-tests:
runs-on: ubuntu-20.04
strategy:
matrix:
python: [ "3.10" ]
steps:
- name: Check out code
uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v3
with:
python-version: "${{ matrix.python }}"
- name: Test Python version
run: |
installed="$(python --version)"
expected="${{ matrix.python }}"
echo $installed
[[ $installed =~ "Python ${expected}" ]] && echo "Configured Python" || (echo "Failed to configure Python" && exit 1)
- name: Local ACT Only - Install required os level applications
if: ${{ env.ACT }}
run: |
sudo apt update -y
sudo apt install -y curl
sudo apt -y install ca-certificates curl gnupg lsb-release
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose --version
- name: Create sandbox
run: make sandbox-dev-up
- name: Install python dependencies
run: make setup-development
- name: Build, Unit Tests and Integration Tests
run: make all-tests
- name: Stop running images
run: make sandbox-dev-stop

build-docset:
runs-on: ubuntu-20.04
container: python:3.10 # Needs `make`, can't be slim
strategy:
matrix:
python: [ "3.10" ]
steps:
- name: Check out code
uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v3
with:
python-version: "${{ matrix.python }}"
- name: Install python dependencies
run: make setup-docs
- name: Make docs
run: make bundle-docs
- name: Archive docset
if: ${{ !env.ACT }}
uses: actions/upload-artifact@v2
with:
name: pyteal.docset
Expand Down
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ coverage.xml
.hypothesis/
.pytest_cache/

# Tests generating TEAL output to compared against an expected example.
tests/**/*.teal
!tests/**/*_expected.teal
# Generated by unit tests - usually for diffs against expected:
**/generated

# Translations
*.mo
Expand Down Expand Up @@ -133,5 +132,9 @@ dmypy.json
.idea
.vscode

# comma seperated vals report files
*.csv
!tests/**/*_example.csv

# mac OS
.DS_Store
52 changes: 46 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ---- Setup ---- #

setup-development:
pip install -e.[development]
pip install -e .[development]

setup-docs: setup-development
pip install -r docs/requirements.txt
Expand All @@ -8,6 +10,11 @@ setup-docs: setup-development
setup-wheel:
pip install wheel

generate-init:
python -m scripts.generate_init

# ---- Docs and Distribution ---- #

bdist-wheel:
python setup.py sdist bdist_wheel

Expand All @@ -20,30 +27,63 @@ bundle-docs: bundle-docs-clean
doc2dash --name pyteal --index-page index.html --online-redirect-url https://pyteal.readthedocs.io/en/ _build/html && \
tar -czvf pyteal.docset.tar.gz pyteal.docset

generate-init:
python -m scripts.generate_init
# ---- Code Quality ---- #

check-generate-init:
python -m scripts.generate_init --check

ALLPY = docs examples pyteal scripts tests *.py
ALLPY = docs examples pyteal scripts tests utils *.py
black:
black --check $(ALLPY)

flake8:
flake8 $(ALLPY)

# TODO: add `tests` and `utils` to $MYPY when graviton respects mypy (version 🐗)
MYPY = pyteal scripts
mypy:
mypy $(MYPY)

lint: black flake8 mypy

# ---- Unit Tests (no algod) ---- #

# TODO: add blackbox_test.py to multithreaded tests when following issue has been fixed https://github.com/algorand/pyteal/issues/199
NUM_PROCS = auto
test-unit:
pytest
pytest -n $(NUM_PROCS) --durations=10 -sv pyteal tests/unit --ignore tests/unit/blackbox_test.py
pytest -n 1 -sv tests/unit/blackbox_test.py


build-and-test: check-generate-init lint test-unit

# Extras:
# ---- Integration Test (algod required) ---- #

sandbox-dev-up:
docker-compose up -d algod

sandbox-dev-stop:
docker-compose stop algod

integration-run:
pytest -n $(NUM_PROCS) --durations=10 -sv tests/integration

test-integration: integration-run

all-tests: build-and-test test-integration

# ---- Local Github Actions Simulation via `act` ---- #
# assumes act is installed, e.g. via `brew install act`

ACT_JOB = run-integration-tests
local-gh-job:
act -j $(ACT_JOB)

local-gh-simulate:
act


# ---- Extras ---- #

coverage:
pytest --cov-report html --cov=pyteal
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,30 @@ Pip install PyTeal in editable state with dependencies:
* `pip install -e.[development]`
* Note, that if you're using `zsh` you'll need to escape the brackets: `pip install -e.\[development\]`

Format code:

* `black .`

Lint using flake8:

* `flake8 docs examples pyteal scripts tests utils *.py`

Type checking using mypy:

* `mypy pyteal`
* `mypy pyteal scripts`

Run tests:
Run unit tests:

* `pytest`
* `pytest pyteal tests/unit utils`

Format code:
Run integration tests (assumes a developer-mode `algod` is available on port 4001):

* `black .`
* `pytest tests/integration`

Lint using flake8:
Stand up developer-mode algod on ports 4001, 4002 and `tealdbg` on port 9392 (assumes [Docker](https://www.docker.com/) is available on your system):

* `docker-compose up -d`

Tear down and clean up resources for the developer-mode algod stood up above:

* `flake8 docs examples pyteal scripts tests *.py`
* `docker-compose down`
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: '3'

services:
algod:
image: makerxau/algorand-sandbox-dev:latest
ports:
- "4001:4001"
- "4002:4002"
- "9392:9392"
2 changes: 1 addition & 1 deletion docs/opup.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _opup:

OpUp: Budget Increase Utility
========================
=================================

Some opcode budget is consumed during execution of every Algorand Smart Contract because every TEAL
instruction has a corresponding cost. In order for the evaluation to succeed, the budget consumed must not
Expand Down
86 changes: 86 additions & 0 deletions examples/signature/factorizer_game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# WARNING: this logic sig is for demo purposes only

from pyteal import (
And,
Arg,
Btoi,
Bytes,
Expr,
Global,
If,
Int,
Pop,
ScratchVar,
Seq,
Subroutine,
TealType,
Txn,
TxnType,
)

ONE_ALGO = Int(1_000_000)


@Subroutine(TealType.uint64)
def root_closeness(A, B, C, X):
left = ScratchVar(TealType.uint64)
right = ScratchVar(TealType.uint64)
return Seq(
left.store(A * X * X + C),
right.store(B * X),
If(left.load() < right.load())
.Then(right.load() - left.load())
.Else(left.load() - right.load()),
)


@Subroutine(TealType.uint64)
def calculate_prize(closeness):
return (
If(closeness + Int(1) < Int(20))
.Then(ONE_ALGO * (Int(10) - (closeness + Int(1)) / Int(2)))
.Else(Int(0))
)


def logicsig(a: int, p: int, q: int) -> Expr:
"""
Choices
* (a, p, q) = (1, 5, 7)
* compiling on TEAL version 5 and
* with assembleConstants = True
results in Logic-Sig Contract Account Address:
WO3TQD3WBSDKB6WEHUMSEBFH53GZVVXYGPWYDWKUZCKEXTVCDNDHJGG6II
"""
assert all(
isinstance(x, int) and p < q and a > 0 and x >= 0 for x in (a, p, q)
), f"require non-negative ints a, p, q with p < q but got {a, p, q}"

b, c = a * (p + q), a * p * q
msg = Bytes(f"Can you factor {a} * x^2 - {b} * x + {c} ?")

A, B, C = Int(a), Int(b), Int(c)
X1 = Btoi(Arg(0))
X2 = Btoi(Arg(1))
C1 = ScratchVar(TealType.uint64)
C2 = ScratchVar(TealType.uint64)
SUM = ScratchVar(TealType.uint64)
PRIZE = ScratchVar(TealType.uint64)
return Seq(
Pop(msg),
C1.store(root_closeness(A, B, C, X1)),
C2.store(root_closeness(A, B, C, X2)),
SUM.store(C1.load() + C2.load()),
PRIZE.store(calculate_prize(SUM.load())),
And(
Txn.type_enum() == TxnType.Payment,
Txn.close_remainder_to() == Global.zero_address(),
X1 != X2,
PRIZE.load(),
Txn.amount() == PRIZE.load(),
),
)


def create(a, b, c):
return logicsig(*map(lambda x: int(x), (a, b, c)))
Loading

0 comments on commit cc86572

Please sign in to comment.