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

Week 06 #43

Merged
merged 84 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 79 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
8aae2ec
create lecture dir with negative r timed
juliusfrost Apr 23, 2023
b2fd4f2
create mock chain context and mock user
juliusfrost Apr 23, 2023
b7ed9f9
attempt to use code from script context builder to evaluate script
juliusfrost Apr 23, 2023
bcc20b0
Fix data generation
nielstron Apr 23, 2023
d857c38
make pyaiken optional
juliusfrost Apr 23, 2023
4f94168
install all optional dependencies in the test workflow
juliusfrost Apr 23, 2023
782f834
create pyaiken extra dependency
juliusfrost Apr 23, 2023
51615cc
update poetry.lock
juliusfrost Apr 23, 2023
5182718
use default max tx memsteps and max tx cpu steps
juliusfrost Apr 23, 2023
c83368c
Add some logic to generate the tx context from the tx and context alone
nielstron Apr 23, 2023
e3b9318
Attach script to tx, catch iteration error
nielstron Apr 23, 2023
49aedda
Formatting, do not attach script in UTxO
nielstron Apr 23, 2023
47a9b4a
Handle UTxO attached scripts
nielstron Apr 23, 2023
0ba761f
Small fix
nielstron Apr 23, 2023
62fcf5a
Take into account all scripts from all inputs
nielstron Apr 23, 2023
8cd7be7
Restructuring
nielstron Apr 23, 2023
8d59746
Set execution steps to maximum if 0 and should_estimate
nielstron Apr 23, 2023
da485cd
Print script debug logs a bit nicer
nielstron Apr 23, 2023
1039eb0
Remove unused mockcontext creator
nielstron Apr 23, 2023
529f113
Fix redeemer assignment
nielstron Apr 23, 2023
8b7752c
Fix consumed vs avail cpu
nielstron Apr 23, 2023
8abbaff
use correct submit methods
juliusfrost Apr 23, 2023
81e0400
fix redeemer list
juliusfrost Apr 23, 2023
1f8c5b8
use pycardano main branch with fixes until 0.9.0
juliusfrost Apr 24, 2023
7bb5c5a
fix submit_tx call
juliusfrost Apr 25, 2023
a2629d4
fix context.utxos
juliusfrost Apr 25, 2023
e552cae
fix parameter order and make redeemer purpose
juliusfrost Apr 25, 2023
3dd39fa
Make bytes actually bytes
nielstron Apr 25, 2023
1839094
use pycardano serialization fix
juliusfrost Apr 25, 2023
1efdcc6
add posix time conversion methods
juliusfrost Apr 26, 2023
b59f5ac
fix property tests
juliusfrost Apr 26, 2023
a3c23d7
fix dependency installation step
juliusfrost Apr 26, 2023
1e8c60b
keep extras installed
juliusfrost Apr 26, 2023
3866fb1
update pycardano
juliusfrost Apr 26, 2023
8882c52
only build script once
juliusfrost Apr 26, 2023
573746f
cache unflat
juliusfrost Apr 27, 2023
5cd7ddf
update pyaiken and hypothesis, add pytest-xdist
juliusfrost Apr 27, 2023
cd5950e
use maximum cores for pytest
juliusfrost Apr 27, 2023
82e7760
use python 3.11 to run tests
juliusfrost Apr 27, 2023
f48e607
require python 3.9+ for newest features
juliusfrost Apr 27, 2023
1b90b9c
reset cache
juliusfrost Apr 27, 2023
468c375
test remove hypothesis deadline
juliusfrost Apr 27, 2023
e063144
create exploitable_swap.py
juliusfrost Apr 27, 2023
0d5725a
add compile test
juliusfrost Apr 27, 2023
8d72380
add unit tests
juliusfrost Apr 27, 2023
b121521
A very simple setup to invoke the actual python script when submitting
nielstron Apr 27, 2023
a2b8614
Merge pull request #48 from OpShin/feat/invoke_opshin_script_when_bui…
juliusfrost Apr 27, 2023
a37e25f
black format
juliusfrost Apr 27, 2023
bdb28e1
catch assertion error for opshin validator
juliusfrost Apr 27, 2023
8dc70ab
refactor transactions into individual methods
juliusfrost Apr 27, 2023
5c3d5a3
fix context hash, add arbitrary user funds
juliusfrost Apr 27, 2023
28d1880
Revert "test remove hypothesis deadline"
juliusfrost Apr 27, 2023
0c96f87
Fix minting and spending redeemer
nielstron Apr 27, 2023
ab4e2e9
fix hash (again)
juliusfrost Apr 27, 2023
1b4cea2
Merge branch 'week06' of https://github.com/OpShin/opshin-pioneer-pro…
juliusfrost Apr 27, 2023
6e657ee
Fix reported step usage in new pyaiken version
nielstron Apr 27, 2023
dc5f554
remove unused import
juliusfrost Apr 27, 2023
4b4d8b0
init homework test
juliusfrost Apr 27, 2023
f8ab967
fix multiasset
juliusfrost Apr 27, 2023
2698768
add missing minting datum
juliusfrost Apr 27, 2023
bc516de
correct hash
juliusfrost Apr 27, 2023
15daaf3
use deepcopy and single setup, use methods instead of module for opsh…
juliusfrost Apr 28, 2023
1f62e90
add callable arguments (force 3)
juliusfrost Apr 28, 2023
b010cf3
fix hw test validator
juliusfrost Apr 28, 2023
8416b0b
use single cached setup
juliusfrost Apr 28, 2023
ce28144
fix hw tests
juliusfrost Apr 28, 2023
5fcc3be
speed up typeguard with PYTHONOPTIMIZE=2
juliusfrost Apr 28, 2023
8dc7679
set PYTHONOPTIMIZE=1 to pass tests
juliusfrost Apr 28, 2023
5aac5f6
Revert "set PYTHONOPTIMIZE=1 to pass tests"
juliusfrost Apr 28, 2023
db37f83
Revert "speed up typeguard with PYTHONOPTIMIZE=2"
juliusfrost Apr 28, 2023
1663954
add double spending test
juliusfrost Apr 29, 2023
be3eefb
typo
juliusfrost Apr 29, 2023
7247ae1
create and test fixed_swap_solution.py
juliusfrost Apr 29, 2023
d1387c3
rename test
juliusfrost Apr 29, 2023
9f5a1e9
add homework template
juliusfrost Apr 29, 2023
67315f3
catch value error
juliusfrost Apr 29, 2023
8eaae06
compile test, rename to solved
juliusfrost Apr 29, 2023
50973b0
create test mock to introduce the mock api
juliusfrost Apr 29, 2023
305d5c2
week06 instructions
juliusfrost Apr 29, 2023
68b0f53
Merge branch 'main' into week06
juliusfrost Apr 30, 2023
659268c
remove week06 solutions
juliusfrost Apr 30, 2023
b972aa0
instructions for installing pyaiken
juliusfrost Apr 30, 2023
89f67f6
update pre-commit
juliusfrost May 3, 2023
f8c91cd
update dependencies
juliusfrost May 4, 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
10 changes: 5 additions & 5 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v4
with:
python-version: '3.8'
python-version: '3.11'
#----------------------------------------------
# ----- install & configure poetry -----
#----------------------------------------------
Expand All @@ -27,7 +27,7 @@ jobs:
uses: actions/cache@v3
with:
path: ~/.local # the path depends on the OS
key: poetry-1 # increment to reset cache
key: poetry-3 # increment to reset cache
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
Expand All @@ -49,12 +49,12 @@ jobs:
#----------------------------------------------
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root
run: poetry install --all-extras --no-interaction --no-root
#----------------------------------------------
# install your root project, if required
#----------------------------------------------
- name: Install project
run: poetry install --no-interaction
run: poetry install --all-extras --no-interaction
#----------------------------------------------
# run linter
#----------------------------------------------
Expand All @@ -66,6 +66,6 @@ jobs:
#----------------------------------------------
- name: Run pytest
run: |
poetry run pytest
poetry run pytest -n auto
env:
TEST_SOLVED: 1
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Join the opshin [discord server](https://discord.com/invite/umR3A2g4uw) for Q/A

## Installation

1. Install Python 3.8, 3.9, 3.10 or 3.11 (if it not already installed on your operating system).
1. Install Python 3.9, 3.10 or 3.11 (if it not already installed on your operating system).
Python3.11 Installer [download](https://www.python.org/downloads/release/python-3112/).

2. Install python poetry.
Expand Down Expand Up @@ -169,11 +169,22 @@ We use pycardano, but you can compare and contrast alternatives.
### [Lecture 6](https://www.youtube.com/playlist?list=PLNEK_Ejlx3x08fHgl_ZTlowVO8bjqITEh)

- [The State Monad in practice](https://www.youtube.com/watch?v=8tWzG0ML6Z4&list=PLNEK_Ejlx3x08fHgl_ZTlowVO8bjqITEh&index=1)
- You can skip this for opshin.
- [Introduction to the Plutus Simple Model library](https://youtu.be/Sft02LeXA_U)
- We implement `MockChainContext` and `MockUser` in `src/utils/mock.py`.
These classes allow us to easily test and evaluate our opshin contracts without the Cardano Node!
- We implement a simple test in `src/week06/tests/test_mock.py` with simulated spending and multiple users.
- [Unit Testing a Smart Contract](https://youtu.be/vB8hyVq3HVo)
- Unit tests located in `src/week06/tests/test_negative_r_timed.py`
- [Property Testing a Smart Contract](https://youtu.be/pF8HpKmaQi4)
- Property tests also located in `src/week06/tests/test_negative_r_timed.py`
- Read the documentation on [hypothesis](https://hypothesis.readthedocs.io/en/latest/)
to get familiar with property testing in Python.
- [Testing Smart Contracts with Lucid](https://youtu.be/aUrIuDQgg5c)
- N/A.
- [Double Spending and Homework](https://youtu.be/AZVpkwRhEaY)
- Complete the following test `src/week06/homework/test_exploitable_swap.py`
- Use your completed test to implement a fix to the swap script: `src/week06/homework/fixed_swap.py`


### [Lecture 7](https://www.youtube.com/playlist?list=PLNEK_Ejlx3x0wH_y1lQp4xtrkuaYSWi6V)
Expand Down
252 changes: 163 additions & 89 deletions poetry.lock

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ readme = "README.md"
packages = [{include = "src"}]

[tool.poetry.dependencies]
python = ">=3.8,<3.12"
opshin = "^0.12.5"
pycardano = "^0.8.0"
python = ">=3.9,<3.12"
opshin = "^0.13.0"
pycardano = {git = "https://github.com/juliusfrost/pycardano.git", rev = "d873e56e0aed2bcd92964f19928254510573fe5f"}
click = "^8.1.3"
pyaiken = {version = "^0.5.0", optional = true}


[tool.poetry.group.dev.dependencies]
black = "^23.3.0"
pre-commit = "^3.2.2"
pytest = "^7.3.1"
hypothesis = "^6.72.0"
hypothesis = "^6.74.0"
pytest-xdist = "^3.2.1"

[tool.poetry.extras]
pyaiken = ["pyaiken"]

[build-system]
requires = ["poetry-core"]
Expand Down
189 changes: 189 additions & 0 deletions src/utils/mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
from collections import defaultdict
Copy link

Choose a reason for hiding this comment

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

Nice! I think it may be a good idea to port this to pycardano.

from typing import Any, Callable, Dict, List, Optional, Union

from pycardano import (
Address,
ChainContext,
ExecutionUnits,
GenesisParameters,
Network,
PaymentSigningKey,
PaymentVerificationKey,
ProtocolParameters,
ScriptType,
Transaction,
TransactionId,
TransactionInput,
TransactionOutput,
UTxO,
Value,
)

from src.utils.protocol_params import (
DEFAULT_GENESIS_PARAMETERS,
DEFAULT_PROTOCOL_PARAMETERS,
)
from src.utils.tx_tools import evaluate_script, generate_script_contexts_resolved


class MockChainContext(ChainContext):
def __init__(
self,
protocol_param: Optional[ProtocolParameters] = None,
genesis_param: Optional[GenesisParameters] = None,
):
self._protocol_param = (
protocol_param if protocol_param else DEFAULT_PROTOCOL_PARAMETERS
)
self._genesis_param = (
genesis_param if genesis_param else DEFAULT_GENESIS_PARAMETERS
)
self._utxo_state: Dict[str, List[UTxO]] = defaultdict(list)
self._address_lookup: Dict[UTxO, str] = {}
self._utxo_from_txid: Dict[TransactionId, Dict[int, UTxO]] = defaultdict(dict)
self._network = Network.TESTNET
self._epoch = 0
self._last_block_slot = 0
self.opshin_scripts: Dict[ScriptType, Callable[[Any, Any, Any], Any]] = {}

@property
def protocol_param(self) -> ProtocolParameters:
return self._protocol_param

@property
def genesis_param(self) -> GenesisParameters:
return self._genesis_param

@property
def network(self) -> Network:
return self._network

@property
def epoch(self) -> int:
return self._epoch

@property
def last_block_slot(self) -> int:
return self._last_block_slot

def _utxos(self, address: str) -> List[UTxO]:
return self._utxo_state.get(address, [])

def add_utxo(self, utxo: UTxO):
address = str(utxo.output.address)
self._utxo_state[address].append(utxo)
self._address_lookup[utxo] = address
self._utxo_from_txid[utxo.input.transaction_id][utxo.input.index] = utxo

def get_address(self, utxo: UTxO) -> str:
return self._address_lookup[utxo]

def remove_utxo(self, utxo: UTxO):
del self._utxo_from_txid[utxo.input.transaction_id][utxo.input.index]
address = self._address_lookup[utxo]
del self._address_lookup[utxo]
i = self._utxo_state[address].index(utxo)
self._utxo_state[address].pop(i)

def get_utxo_from_txid(self, transaction_id: TransactionId, index: int) -> UTxO:
return self._utxo_from_txid[transaction_id][index]

def submit_tx(self, tx: Transaction):
self.evaluate_tx(tx)
self.submit_tx_mock(tx)

def submit_tx_mock(self, tx: Transaction):
for input in tx.transaction_body.inputs:
utxo = self.get_utxo_from_txid(input.transaction_id, input.index)
self.remove_utxo(utxo)
for i, output in enumerate(tx.transaction_body.outputs):
utxo = UTxO(TransactionInput(tx.id, i), output)
self.add_utxo(utxo)

def submit_tx_cbor(self, cbor: Union[bytes, str]):
return self.submit_tx(Transaction.from_cbor(cbor))

def evaluate_tx(self, tx: Transaction) -> Dict[str, ExecutionUnits]:
input_utxos = [
self.get_utxo_from_txid(input.transaction_id, input.index)
for input in tx.transaction_body.inputs
]
ref_input_utxos = (
[
self.get_utxo_from_txid(input.transaction_id, input.index)
for input in tx.transaction_body.reference_inputs
]
if tx.transaction_body.reference_inputs is not None
else []
)
script_invocations = generate_script_contexts_resolved(
tx, input_utxos, ref_input_utxos, lambda s: self.posix_from_slot(s)
)
ret = {}
for invocation in script_invocations:
# run opshin script if available
if self.opshin_scripts.get(invocation.script) is not None:
opshin_validator = self.opshin_scripts[invocation.script]
opshin_validator(
invocation.datum,
invocation.redeemer.data,
invocation.script_context,
)
redeemer = invocation.redeemer
if redeemer.ex_units.steps <= 0 and redeemer.ex_units.mem <= 0:
redeemer.ex_units = ExecutionUnits(
self.protocol_param.max_tx_ex_mem,
self.protocol_param.max_tx_ex_steps,
)

(suc, err), (cpu, mem), logs = evaluate_script(invocation)
if err:
raise ValueError(err, logs)
key = f"{redeemer.tag.name.lower()}:{redeemer.index}"
ret[key] = ExecutionUnits(mem, cpu)
return ret

def evaluate_tx_cbor(self, cbor: Union[bytes, str]) -> Dict[str, ExecutionUnits]:
return self.evaluate_tx(Transaction.from_cbor(cbor))

def wait(self, slots):
self._last_block_slot += slots

def posix_from_slot(self, slot: int) -> int:
"""Convert a slot to POSIX time (seconds)"""
return self.genesis_param.system_start + self.genesis_param.slot_length * slot

def slot_from_posix(self, posix: int) -> int:
"""Convert POSIX time (seconds) to the last slot"""
return (
posix - self.genesis_param.system_start
) // self.genesis_param.slot_length


class MockUser:
def __init__(self, context: MockChainContext):
self.context = context
self.signing_key = PaymentSigningKey.generate()
self.verification_key = PaymentVerificationKey.from_signing_key(
self.signing_key
)
self.network = Network.TESTNET
self.address = Address(
payment_part=self.verification_key.hash(), network=self.network
)

def fund(self, amount: Union[int, Value]):
if isinstance(amount, int):
value = Value(coin=amount)
else:
value = amount
self.context.add_utxo(
# not sure what the correct genesis transaction is
UTxO(
TransactionInput(TransactionId(self.verification_key.payload), 0),
TransactionOutput(self.address, value),
),
)

def utxos(self):
return self.context.utxos(self.address)
Loading