Repository: xian-network/xian-contracting Branch: master Commit: ded6171d529f Files: 195 Total size: 461.8 KB Directory structure: gitextract_tbpetck0/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples/ │ ├── 01 A very simple Counter contract.ipynb │ ├── 02 Ingredients of a Smart Contract.ipynb │ ├── 03 Interacting with the Client.ipynb │ ├── 04 Standard Library and Extending Contracting.ipynb │ ├── 05 Imports and Advanced Data Storage.ipynb │ └── Rock Paper Scissors Tutorial.ipynb ├── pyproject.toml ├── release.sh ├── src/ │ └── contracting/ │ ├── __init__.py │ ├── client.py │ ├── compilation/ │ │ ├── __init__.py │ │ ├── compiler.py │ │ ├── linter.py │ │ ├── parser.py │ │ └── whitelists.py │ ├── constants.py │ ├── contracts/ │ │ ├── __init__.py │ │ ├── proxythis.py │ │ ├── submission.s.py │ │ └── thistest2.py │ ├── execution/ │ │ ├── __init__.py │ │ ├── executor.py │ │ ├── module.py │ │ ├── runtime.py │ │ └── tracer.py │ ├── stdlib/ │ │ ├── __init__.py │ │ ├── bridge/ │ │ │ ├── __init__.py │ │ │ ├── access.py │ │ │ ├── crypto.py │ │ │ ├── decimal.py │ │ │ ├── hashing.py │ │ │ ├── imports.py │ │ │ ├── orm.py │ │ │ ├── random.py │ │ │ └── time.py │ │ └── env.py │ └── storage/ │ ├── __init__.py │ ├── contract.py │ ├── driver.py │ ├── encoder.py │ ├── hdf5.py │ └── orm.py └── tests/ ├── __init__.py ├── integration/ │ ├── __init__.py │ ├── test_atomic_swap.py │ ├── test_builtins_locked_off.py │ ├── test_complex_contracts.py │ ├── test_complex_object_setting.py │ ├── test_constructor_args.py │ ├── test_contracts/ │ │ ├── __init__.py │ │ ├── atomic_swaps.s.py │ │ ├── bad_time.s.py │ │ ├── bastardcoin.s.py │ │ ├── builtin_lib.s.py │ │ ├── child_test.s.py │ │ ├── client.py │ │ ├── con_pass_hash.s.py │ │ ├── construct_function_works.s.py │ │ ├── constructor_args_contract.s.py │ │ ├── contracting.s.py │ │ ├── currency.s.py │ │ ├── dater.py │ │ ├── dynamic_import.py │ │ ├── dynamic_import.s.py │ │ ├── dynamic_importing.s.py │ │ ├── erc20_clone.s.py │ │ ├── exception.py │ │ ├── float_issue.s.py │ │ ├── foreign_thing.s.py │ │ ├── hashing_works.s.py │ │ ├── i_use_env.s.py │ │ ├── import_test.s.py │ │ ├── import_this.s.py │ │ ├── importing_that.s.py │ │ ├── inf_loop.s.py │ │ ├── json_tests.s.py │ │ ├── leaky.s.py │ │ ├── mathtime.s.py │ │ ├── modules/ │ │ │ ├── all_in_one.s.py │ │ │ ├── dynamic_import.s.py │ │ │ ├── module1.s.py │ │ │ ├── module2.s.py │ │ │ ├── module3.s.py │ │ │ ├── module4.s.py │ │ │ ├── module5.s.py │ │ │ ├── module6.s.py │ │ │ ├── module7.s.py │ │ │ └── module8.s.py │ │ ├── orm_foreign_hash_contract.s.py │ │ ├── orm_foreign_key_contract.s.py │ │ ├── orm_hash_contract.s.py │ │ ├── orm_no_contract_access.s.py │ │ ├── orm_variable_contract.s.py │ │ ├── owner_stuff.s.py │ │ ├── parent_test.s.py │ │ ├── pass_hash.s.py │ │ ├── private_methods.s.py │ │ ├── stubucks.s.py │ │ ├── submission.s.py │ │ ├── tejastokens.s.py │ │ ├── thing.s.py │ │ ├── time.s.py │ │ └── time_storage.s.py │ ├── test_datetime_contracts.py │ ├── test_dynamic_imports.py │ ├── test_executor_submission_process.py │ ├── test_executor_transaction_writes.py │ ├── test_memory_clean_up_after_execution.py │ ├── test_misc_contracts.py │ ├── test_pixel_game.py │ ├── test_rich_ctx_calling.py │ ├── test_run_private_function.py │ ├── test_senecaCompiler_integration.py │ ├── test_seneca_client_randoms.py │ ├── test_seneca_client_replaces_executor.py │ └── test_stamp_deduction.py ├── performance/ │ ├── __init__.py │ ├── prof_transfer.py │ ├── test_contracts/ │ │ ├── __init__.py │ │ ├── erc20_clone.s.py │ │ ├── modules/ │ │ │ ├── all_in_one.s.py │ │ │ ├── dynamic_import.s.py │ │ │ ├── module1.s.py │ │ │ ├── module2.s.py │ │ │ ├── module3.s.py │ │ │ ├── module4.s.py │ │ │ ├── module5.s.py │ │ │ ├── module6.s.py │ │ │ ├── module7.s.py │ │ │ └── module8.s.py │ │ └── submission.s.py │ └── test_transfer.py ├── security/ │ ├── __init__.py │ ├── contracts/ │ │ ├── builtin_hack_token.s.py │ │ ├── call_infinate_loop.s.py │ │ ├── con_inf_writes.s.py │ │ ├── constructor_infinate_loop.s.py │ │ ├── double_spend_gas_attack.s.py │ │ ├── erc20_clone.s.py │ │ ├── get_set_driver.py │ │ ├── get_set_driver_2.py │ │ ├── hack_tokens.s.py │ │ ├── import_hash_from_contract.s.py │ │ ├── infinate_loop.s.py │ │ └── submission.s.py │ └── test_erc20_token_hacks.py └── unit/ ├── __init__.py ├── contracts/ │ ├── currency.s.py │ ├── exception.s.py │ ├── proxythis.py │ ├── submission.s.py │ └── thistest2.py ├── loop_client_test.sh ├── precompiled/ │ ├── __init__.py │ ├── compiled_token.py │ └── updated_submission.py ├── test_client.py ├── test_client_keys_prefix.py ├── test_context_data_struct.py ├── test_datetime.py ├── test_decimal.py ├── test_driver_tombstones.py ├── test_encode.py ├── test_imports_stdlib.py ├── test_linter.py ├── test_module.py ├── test_new_driver.py ├── test_orm.py ├── test_parser.py ├── test_revert_on_exception.py ├── test_runtime.py ├── test_state_management.py ├── test_stdlib_hashing.py ├── test_sys_contracts/ │ ├── __init__.py │ ├── bad_lint.s.py │ ├── compile_this.s.py │ ├── currency.s.py │ ├── good_lint.s.py │ ├── module1.py │ ├── module2.py │ ├── module3.py │ ├── module4.py │ ├── module5.py │ ├── module6.py │ ├── module7.py │ ├── module8.py │ └── module_func.py └── test_timedelta.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Description Please add a brief description of the feature / change / bug-fix you want to merge. ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would require a resync of blockchain state) ## Checklist - [ ] I have performed a self-review of my own code - [ ] I have tested this change in my development environment. - [ ] I have added tests to prove that this change works - [ ] All existing tests pass after this change - [ ] I have added / updated documentation related to this change ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "pip" # Type of package ecosystem (e.g., "npm", "maven") directory: "/" # Location of package manifests schedule: interval: "daily" # How often to check for updates open-pull-requests-limit: 10 # Maximum number of open pull requests Dependabot should have ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish to PyPI and GitHub Release on: push: tags: - 'v*' # Trigger on version tags jobs: build-and-publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.11 uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install Poetry run: | curl -sSL https://install.python-poetry.org | python3 - - name: Build and Publish to PyPI env: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} run: | poetry config pypi-token.pypi $PYPI_TOKEN poetry build poetry publish - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: files: dist/* generate_release_notes: true ================================================ FILE: .gitignore ================================================ # Python __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Virtual Environment .env .venv env/ venv/ ENV/ # IDE .idea/ .vscode/ *.swp *.swo # Testing .coverage .pytest_cache/ htmlcov/ # Poetry poetry.lock__pycache__/ dist xian_contracting.egg-info contracting.egg-info build .idea logs *.so .ccls *.pyc ================================================ FILE: LICENSE ================================================ Creative Commons Attribution‑NonCommercial 4.0 International Copyright © 2025 XIAN.org This work is licensed under CC BY‑NC 4.0. You may copy, modify, and share it **for non‑commercial purposes only**, provided that you give appropriate credit. Full legal text: https://creativecommons.org/licenses/by-nc/4.0/legalcode ================================================ FILE: README.md ================================================ # Xian Contracting Xian Contracting is a Python-based smart contract development and execution framework. Unlike traditional blockchain platforms like Ethereum, Xian Contracting leverages Python's VM to create a more accessible and familiar environment for developers to write smart contracts. ## Features - **Python-Native**: Write smart contracts in standard Python with some additional decorators and constructs - **Storage System**: Built-in ORM-like system with `Variable` and `Hash` data structures - **Runtime Security**: Secure execution environment with memory and computation limitations - **Metering System**: Built-in computation metering to prevent infinite loops and resource abuse - **Event System**: Built-in logging and event system for contract state changes - **Import Controls**: Secure import system that prevents access to dangerous system modules ## Installation ```bash pip install xian-contracting ``` ## Quick Start Here's a complete token contract example with approval system: ```python def token_contract(): balances = Hash() owner = Variable() @construct def seed(): owner.set(ctx.caller) @export def approve(amount: float, to: str): assert amount > 0, 'Cannot send negative balances.' balances[ctx.caller, to] += amount @export def transfer_from(amount: float, to: str, main_account: str): approved = allowances[main_account, ctx.caller] assert amount > 0, 'Cannot send negative balances!' assert approved >= amount, f'You approved {approved} but need {amount}' assert balances[main_account] >= amount, 'Not enough tokens to send!' allowances[main_account, ctx.caller] -= amount balances[main_account] -= amount balances[to] += amount @export def transfer(amount: float, to: str): assert amount > 0, 'Cannot send negative balances.' assert balances[ctx.caller] >= amount, 'Not enough coins to send.' balances[ctx.caller] -= amount balances[to] += amount @export def mint(to, amount): assert ctx.caller == owner.get(), 'Only the original contract author can mint!' balances[to] += amount ``` ## Core Concepts ### Storage Types - **Variable**: Single-value storage ```python counter = Variable() counter.set(0) # Set value current = counter.get() # Get value ``` - **Hash**: Key-value storage with support for complex and multi-level keys ```python balances = Hash() # Single-level key balances['alice'] = 100 alice_balance = balances['alice'] # Multi-level keys for complex relationships balances['alice', 'bob'] = 50 # e.g., alice approves bob to spend 50 tokens approved_amount = balances['alice', 'bob'] # Get the approved amount # You can use up to 16 dimensions in key tuples data['user', 'preferences', 'theme'] = 'dark' ``` ### Contract Decorators - **@construct**: Initializes contract state (can only be called once) ```python @construct def seed(): owner.set(ctx.caller) ``` - **@export**: Makes function callable from outside the contract ```python @export def increment(amount: int): counter.set(counter.get() + amount) ``` ### Contract Context The `ctx` object provides important runtime information: - `ctx.caller`: Address of the account calling the contract - `ctx.this`: Current contract's address - `ctx.signer`: Original transaction signer - `ctx.owner`: Contract owner's address ## Using the ContractingClient The `ContractingClient` class is your main interface for deploying and interacting with contracts: ```python from contracting.client import ContractingClient # Initialize the client client = ContractingClient() # Submit a contract with open('token.py', 'r') as f: contract = f.read() client.submit(name='con_token', code=contract) # Get contract instance token = client.get_contract('con_token') # Call contract methods token.transfer(amount=100, to='bob') ``` ## Storage Driver The framework includes a powerful storage system: ```python from contracting.storage.driver import Driver driver = Driver() # Direct storage operations driver.set('key', 'value') driver.get('key') # Contract storage driver.set_contract(name='contract_name', code=contract_code) driver.get_contract('contract_name') ``` ## Event System Contracts can emit events which can be tracked by external systems: ```python def token_contract(): transfer_event = LogEvent( 'transfer', { 'sender': {'type': str, 'idx': True}, 'receiver': {'type': str, 'idx': True}, 'amount': {'type': float} } ) @export def transfer(amount: float, to: str): # ... transfer logic ... # Emit event transfer_event({ 'sender': ctx.caller, 'receiver': to, 'amount': amount }) ``` ## Security Features - Restricted imports to prevent malicious code execution - Memory usage tracking and limitations - Computation metering to prevent infinite loops - Secure runtime environment - Type checking and validation - Private method protection ## Development and Testing When developing contracts, you can use the linter to check for common issues: ```python from contracting.client import ContractingClient client = ContractingClient() violations = client.lint(contract_code) ``` ## License This project is licensed under the Creative Commons Attribution‑NonCommercial 4.0 International - see the [LICENSE](LICENSE) file for details. Non‑commercial use only. See LICENSE for details. ================================================ FILE: examples/01 A very simple Counter contract.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# A very simple Counter contract\n", "\n", "Let's start writing a contract in python that stores a number which everyone can increment:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def counter_contract():\n", " # introduce a state called count which holds a single value\n", " count = Variable()\n", " \n", " # @construct means that this is the function that will be called when the smart contract is created\n", " @construct\n", " def constructor():\n", " count.set(0)\n", " \n", " # @export makes this function public, so it can be called by anyone on a deployed contract \n", " @export\n", " def increment():\n", " count.set(count.get() + 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To interact with smart contracts we need a client:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from contracting.client import ContractingClient\n", "client = ContractingClient(signer='ren')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we will submit the contract to the client:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "client.submit(counter_contract)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can get the submitted contract to interact with it:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "contract = client.get_contract('counter_contract')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's investigate the counter:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "contract.count.get()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is 0 as expected because we initialized it to 0 in the constructor function.\n", "\n", "Everyone can increment the counter by calling the public increment function:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "contract.increment()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's investigate the counter again:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "contract.count.get()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Seems like our increment function works.\n", "\n", "This concludes our first look into writing smart contracts in Python on Lamden. Dive deeper by looking at the next example. " ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: examples/02 Ingredients of a Smart Contract.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# What is inside a smart contract?\n", "\n", "Every smart contract has a set of methods that can be exposed to outside users, or kept internally for the operations of the smart contract. Each smart contract also has a data namespace on which it can read and write data too. Smart contracts can read data from other smart contracts, but they cannot write to other smart contracts." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def basic_contract():\n", " # data section\n", " owner = Variable()\n", " balances = Hash(default_value=0)\n", " \n", " @construct\n", " def seed():\n", " owner.set('stuart')\n", " balances['stuart'] = 1000000\n", "\n", " @export\n", " def mint(amount, to):\n", " assert_is_owner()\n", " \n", " balances[to] += amount\n", " \n", " def assert_is_owner():\n", " assert ctx.caller == owner.get(), 'You are not the owner! {} is!'.format(owner.get())\n", " \n", " @export\n", " def send(amount, to):\n", " assert balances[ctx.caller] >= amount, 'You do not have enough to send! {} is less than {}'.format(\n", " balances[ctx.caller], amount\n", " )\n", " \n", " balances[ctx.caller] -= amount\n", " balances[to] += amount" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions\n", "This looks like a lot to chew over, but let's go over it piece by piece. There are 3 types of functions:\n", "\n", "* @construct\n", " \n", " This function is run once on contract submission. It 'constructs' the initial state of the smart contract. Use this for setting initial variables. You can only have one @construct function, but you can call it whatever you want.\n", " \n", " \n", "* @export\n", " \n", " These functions are callable by outside users and other smart contracts. Therefore, you have to pay extra attention to the code you write in them. If @export functions do things that you do not want every user to do, you should put them in a private function, or add access control features.\n", " \n", " \n", "* everything else\n", " \n", " Any other function without a decorator is a private method that can only be called by the contract itself. These functions are good for complex contracts that need to abstract and reuse some logic over and over again. It also allows you to set apart the pieces of the smart contract logic between core controller code, and external interaction functions." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "from contracting.client import ContractingClient\n", "client = ContractingClient(signer='stuart')" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "client.submit(basic_contract)\n", "contract = client.get_contract('basic_contract')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because @constructor sets `stuart`'s balance to 1,000,000, we can access the variable directly from our contract object to check." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1000000" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "contract.balances['stuart']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because `mint` is an @export function, we can call it from the outside. Remember, the `client` was initialized with the `signer` equal to `stuart`." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "20000" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "contract.mint(amount=10000, to='raghu')\n", "contract.balances['raghu']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we override the signer keyword to someone who is `not_stuart`, the function will fail because of the assertation. The internal function is called here." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You are not the owner! stuart is!\n" ] } ], "source": [ "try:\n", " contract.mint(amount=500, to='raghu', signer='not_stuart')\n", "except AssertionError as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, if you try to access the function normally (and remember to prepend the `__` before it), you can see that the function is not available to us to call." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "module 'basic_contract' has no attribute '__assert_is_owner'\n" ] } ], "source": [ "try:\n", " contract.__assert_is_owner()\n", "except Exception as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data\n", "You will also notice a `Variable` object and `Hash` object initialized in the beginning of the smart contract. These are data variables that are used to store data on the smart contract. You cannot modify these variables directly. Instead, the @export functions you write determine how data is modified in these variables. This allows you to create extremely robust and secure data models for your application.\n", "\n", "There are also `ForeignVariable` and `ForeignHash` objects. These are 'read-only' variables that allow your smart contract to import the namespace of another smart contract for internal use, but prevents writing to them. For example, your function might only pass given certain conditions on another smart contract that it is watching. Foreign varibles would be used in this case.\n", "\n", "The Contracting client exposes these variables into Python objects and *does* allow modification. This is so that the developer can test various situations more easily. It does not reflect how users will be able to modify data in a smart contract namespace." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "def data_contract():\n", " basic_contract_owner = ForeignVariable(foreign_contract='basic_contract', foreign_name='owner')\n", " \n", " variable = Variable()\n", " hash_ = Hash()\n", " \n", " # Demonstration of returning a value in another smart contract's namespace\n", " @export\n", " def whos_the_owner():\n", " return basic_contract_owner.get()\n", " \n", " @export\n", " def set_var(x):\n", " variable.set(x)\n", " \n", " @export\n", " def set_hash(k, v):\n", " hash_[k] = v" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "client.submit(data_contract)\n", "d_contract = client.get_contract('data_contract')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Read the Foreign Variable" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'stuart'" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d_contract.whos_the_owner()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Set the Variable via Smart Contract and via Client" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1000" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d_contract.set_var(x=1000)\n", "d_contract.variable.get()" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "123" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d_contract.set_var(x=123)\n", "d_contract.variable.get()" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "555" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Overriding our method to modify the variable directly\n", "d_contract.variable.set(555)\n", "d_contract.variable.get()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Set the Hash via Smart Contract and via Client" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'world'" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d_contract.set_hash(k='hello', v='world')\n", "d_contract.hash_['hello']" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'there'" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d_contract.hash_['hello'] = 'there'\n", "d_contract.hash_['hello']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Behavior Note\n", "\n", "Variables and hashes only take up space in the database if they are initialized. Therefore, you cannot access a hash that has no values. Because you cannot access this hash (or variable), you cannot manually set values on it either. Make sure that you always either @construct your variables, or provide methods in your smart contract to interact with them." ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "def variable_behavior():\n", " invisible_variable = Variable()\n", " \n", " @export\n", " def do_nothing():\n", " return 0" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [], "source": [ "client.submit(variable_behavior)\n", "v_contract = client.get_contract('variable_behavior')" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'AbstractContract' object has no attribute 'invisible_variable'\n" ] } ], "source": [ "try:\n", " v_contract.invisible_variable\n", "except AttributeError as e:\n", " print(e)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: examples/03 Interacting with the Client.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The Main Contracting Client\n", "Contracting comes with a robust client that abstracts most of the underpinnings of the smart contracting system so that it feels like regular Python. This lets you worry about your programming rather than the mechanics of the protocol. To start, create an instance of the `ContractingClient` like so. You can set the default `signer` who will then automatically be the `signer` of the subsequent function calls (or transactions, in blockchain terms).\n", "\n", "You can change the signer by setting this property on the client, or by overriding the keyword `sender` when calling a function." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from contracting.client import ContractingClient\n", "client = ContractingClient(signer='stu')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Viewing contracts\n", "\n", "The client preloads a set of 'genesis' contracts that are required for the system to operate. These genesis contracts bypass some of the system protections to allow a channel between the low level protocol and the higher level programming interface to occur without exposing everything to the developer. This allows us to create powerful system level contracts while maintaining tight security on user level contracts.\n", "\n", "One of these genesis contracts is the contract submission contract. Ironically, we have a smart contract to submit smart contracts. This is for purposes of conflict resolution, which is a later topic. For now, you can see the contracts by calling the function below." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['submission']" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "client.get_contracts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interacting with existing smart contracts\n", "\n", "Contracts can be fetched into their own objects with the `get_contract` method." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "client.get_contract('submission')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can see all available functions you can call on this smart contract by accessing the `self.functions` property. This returns a list of tuples made up of function names and the list of keyword arguments they require." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('submit_contract', ['name', 'code'])]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "submission = client.get_contract('submission')\n", "submission.functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submitting new smart contracts\n", "\n", "Let's use the `submit_contract` function to submit a new smart contract!" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "code = '''\n", "@export\n", "def hello_there():\n", " print('howdy')\n", "'''\n", "\n", "name = 'howdy_time'\n", "\n", "submission.submit_contract(name=name, code=code)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can check to make sure that the smart contract made it into the `submission` contract's \"namespace.\" Each smart contract has its own namespace where it stores data which isolates it from other smart contracts securely." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "howdy_time = client.get_contract(name)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['howdy_time.__author__',\n", " 'howdy_time.__code__',\n", " 'howdy_time.__compiled__',\n", " 'howdy_time.__type__']" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "howdy_time.keys()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def hello_there():\n", " print('howdy')\n", "\n" ] } ], "source": [ "print(howdy_time.__code__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It worked! But why was the @export decorator deleted? This actually is a lower level Contracting function that parses submitted code and transforms it into proper Python. In this process, it also checks to make sure that the code contains all of the correct formatting and features. This is what a contract looks like if you include a private function as well." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "code = '''\n", "@export\n", "def secret():\n", " return abcd()\n", " \n", "def abcd():\n", " return 5\n", "'''\n", "\n", "name = 'secret'\n", "\n", "submission.submit_contract(name=name, code=code)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "secret = client.get_contract('secret')" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('secret', []), ('__abcd', [])]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "secret.functions" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def secret():\n", " return __abcd()\n", "\n", "\n", "def __abcd():\n", " return 5\n", "\n" ] } ], "source": [ "print(secret.__code__)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "secret.secret()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice how methods without @export on them get prepended with `__`. This prevents people from calling these methods in their own smart contracts, making them effectively private. The Contracting client, however, is designed for the maximal developer experience, so you may be able to call these functions from the client so that you can write unit tests and integration tests around their functionality." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Closures and direct submission\n", "\n", "Instead of pulling out the `submission` contract every time before a session, you can access it via the client on the `submit` method. This method also allows you to submit closure functions (functions that have other functions inside of them). As long as the code inside of the closure can be considered a valid smart contract, the client will convert it into a code string and submit it to the `submission` smart contract.\n", "\n", "Contracts submitted this way will be named whatever their closure function is." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def closure_example():\n", " @export\n", " def hello():\n", " return 'How are you?'\n", " \n", " def shh():\n", " return 10" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "client.submit(closure_example)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "closure = client.get_contract('closure_example')" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def hello():\n", " return 'How are you?'\n", "\n", "\n", "def __shh():\n", " return 10\n", "\n" ] } ], "source": [ "print(closure.__code__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that you have an idea on how to use the client, let's dive into the mechanics behind smart contracts in the next notebook." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: examples/04 Standard Library and Extending Contracting.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The Contracting Standard Library\n", "There are unique variables and functions available for smart contracts at runtime. In the last notebook, we used the ORM variables that are provided via the `stdlib` to interact with the database. We will explore the other methods in this notebook and the concept of `environment` and `ctx`.\n", "\n", "To see the basic standard library included at runtime, use `gather()` from the `env` module." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from contracting.stdlib import env" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'Variable': contracting.db.orm.Variable,\n", " 'Hash': contracting.db.orm.Hash,\n", " 'ForeignVariable': contracting.db.orm.ForeignVariable,\n", " 'ForeignHash': contracting.db.orm.ForeignHash,\n", " '__Contract': contracting.db.contract.Contract,\n", " 'sha3': ,\n", " 'sha256': ,\n", " 'datetime': contracting.stdlib.bridge.time.Datetime}" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "env.gather()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All of these functions are available at runtime. You can extend the standard library by updating the `env` dictionary. If you want to pass something more dynamic through, like the time that a transaction was submitted to the network, you would pass in an environment object." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def stdlib_environment_examples():\n", " \n", " @export\n", " def sha3_data(s):\n", " return sha3(s)\n", " \n", " @export\n", " def return_env_variable():\n", " return this_will_be_defined_later" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, let's try to access the `sha3` function exposed in the `stdlib`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "from contracting.client import ContractingClient\n", "client = ContractingClient(signer='stu')\n", "client.submit(stdlib_environment_examples)\n", "contract = client.get_contract('stdlib_environment_examples')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'a0d5f1e1000980a0ae98cffb12072a41328bbdfebf3f6012aa021b428daea5b7'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "contract.sha3_data(s='00ff00ff00ff')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, if we try to call `return_env_variable()`, we will get an error. This is because the variable is not included in the `stdlib` nor has been defined elsewhere in the contract." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "name 'this_will_be_defined_later' is not defined\n" ] } ], "source": [ "try:\n", " contract.return_env_variable()\n", "except Exception as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But if we pass it in the environment, it becomes accessible. This function is used by our blockchain to pass contextual information such as block height, block hash, transaction time, etc." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "environment = {'this_will_be_defined_later': 42}\n", "contract.return_env_variable(environment=environment)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Runtime Context `ctx`\n", "At runtime, there is a constant defined called `ctx`. `ctx` contains three fields:\n", "\n", "* `ctx.signer`\n", "\n", " This is the signer of the initial transaction. This variable never changes. It should not be used for access control.\n", "\n", "---\n", "\n", "* `ctx.caller`\n", " \n", " This is the direct caller of the function. As explained in the next notebook, smart contracts can import functions from other smart contracts. If you submit a transaction to a smart contract which calls upon another smart contract, the call stack is modified. The `caller` becomes the calling smart contract on a function. This should be used for access control.\n", " \n", "--- \n", " \n", "* `ctx.this`\n", "\n", " This is the name of the smart contract. This is how you will reference a smart contract for ascribing ownership to them." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def call_me():\n", " @export\n", " def caller():\n", " return ctx.caller\n", " \n", " @export\n", " def this():\n", " return ctx.this\n", "\n", "def ctx_example():\n", " import call_me\n", " \n", " @export\n", " def ctx_now():\n", " return ctx.signer, ctx.caller, ctx.this\n", " \n", " @export\n", " def ctx_after_call():\n", " c = call_me.caller()\n", " t = call_me.this()\n", " \n", " return ctx.signer, c, t" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "client.submit(call_me)\n", "client.submit(ctx_example)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "ctx_contract = client.get_contract('ctx_example')" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('stu', 'stu', 'ctx_example')" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ctx_contract.ctx_now()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('stu', 'ctx_example', 'call_me')" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ctx_contract.ctx_after_call()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice above how `ctx_after_call` returns different information. This is because `ctx` is modified after each function call. Because `ctx_example` called `call_me`, the `ctx.caller` returned was `ctx_example`. If we call that function directly, we will get `stu` back." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "call_me_contract = client.get_contract('call_me')" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'stu'" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "call_me_contract.caller()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Why does `ctx` being dynamic matter?\n", "Having `ctx` lets you create smart contracts that act as operators for users and other smart contracts. Because they are given their own identity, they are essentially the signers of their own function calls. This allows you to give them their own accounts, balances, ownerships, etc. to create structures that behave complexly and securely.\n", "\n", "Assume you have a bank smart contract. You want to keep everyone's balance inside of the main bank vault, but keep sub-accounts in their name. You don't want the bank to be able to spend someone else's money on their behalf, so you would check the `ctx.caller` to make sure it is the user, and not the bank itself.\n", "\n", "Let's make a token contract to demonstrate this idea." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "def coin():\n", " balances = Hash()\n", " token_name = 'Stubucks'\n", " token_symbol = 'SBX'\n", " \n", " @construct\n", " def seed():\n", " # Whoever creates this smart contract is minted 1,000,000 tokens\n", " balances[ctx.caller] = 1000000\n", " \n", " @export\n", " def transfer(amount, to):\n", " # Make sure that the person calling this function has the amount they are trying to transfer\n", " assert balances[ctx.caller] >= amount, \"You don't have enough to spend!\"\n", " \n", " # If so, deduct from their account and send to who they want to send to\n", " balances[ctx.caller] -= amount\n", " balances[to] += amount\n", " \n", " @export\n", " def allow(amount, spender):\n", " # This creates a 'subaccount' to allow the spender to transfer from our account a certain amount\n", " balances[ctx.caller, spender] = amount\n", " \n", " @export\n", " def spend_on_behalf(amount, owner, to):\n", " # We make sure the subaccount has enough coins to spend\n", " assert balances[owner, ctx.caller] >= amount, \"You can't spend that!\"\n", " \n", " # If so, deduct from the amount that the subaccount can spend\n", " balances[owner, ctx.caller] -= amount\n", " \n", " # And then make the transfer\n", " balances[owner] -= amount\n", " balances[to] += amount\n", " \n", "def bank():\n", " import coin\n", " \n", " balances = Hash()\n", " \n", " @export\n", " def deposit(amount):\n", " # The bank spends the coins it is allowed to on itself. It takes it from the subaccount and give it to itself\n", " # We don't need an assertion because this will fail\n", " coin.spend_on_behalf(amount=amount, owner=ctx.caller, to=ctx.this)\n", " \n", " # The account that of whoever called the deposit function is incremented accordingly\n", " balances[ctx.caller] += amount\n", " \n", " @export\n", " def withdraw(amount):\n", " # Make sure there is enough in the caller's account to withdraw\n", " assert balances[ctx.caller] >= amount, \"You don't have enough in your account!\"\n", " \n", " # Deduct from the account\n", " balances[ctx.caller] -= amount\n", " \n", " # Transfer the coins out to the caller\n", " coin.transfer(amount=amount, to=ctx.caller)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is the idea:\n", "\n", "1. You must allow a user to spend tokens on your behalf by calling the `allow` function.\n", "2. You can then deposit tokens into the bank by calling deposit after you have `allow`ed the bank to spend on your behalf.\n", "3. You can withdraw tokens from the bank if they belong to you. However, the bank is the one that has the token balance. You have a sub-balance.\n", " \n", "Let's see if it works!" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1000000" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "client.submit(coin)\n", "client.submit(bank)\n", "\n", "coin_contract = client.get_contract('coin')\n", "bank_contract = client.get_contract('bank')\n", "\n", "coin_contract.balances['stu'] # Check if things @construct'ed appropriately" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['coin.__author__',\n", " 'coin.__code__',\n", " 'coin.__compiled__',\n", " 'coin.__type__',\n", " 'coin.balances:stu']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coin_contract.keys()" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "balances = Hash(contract='coin', name='balances')\n", "token_name = 'Stubucks'\n", "token_symbol = 'SBX'\n", "\n", "\n", "def ____():\n", " balances[ctx.caller] = 1000000\n", "\n", "\n", "def transfer(amount, to):\n", " assert balances[ctx.caller] >= amount, \"You don't have enough to spend!\"\n", " balances[ctx.caller] -= amount\n", " balances[to] += amount\n", "\n", "\n", "def allow(amount, spender):\n", " balances[ctx.caller, spender] = amount\n", "\n", "\n", "def spend_on_behalf(amount, owner, to):\n", " assert balances[owner, ctx.caller] >= amount, \"You can't spend that!\"\n", " balances[owner, ctx.caller] -= amount\n", " balances[owner] -= amount\n", " balances[to] += amount\n", "\n" ] } ], "source": [ "print(coin_contract.__code__)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coin_contract.transfer(amount=10, to='hi')\n", "coin_contract.balances['hi']" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Let's verify that the bank has no coins\n", "coin_contract.balances['bank']" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "coin_contract.allow(amount=500, spender='bank')" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "500" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coin_contract.balances['stu', 'bank'] # This is how you access a 'subaccount.' A nice feature of multihashes!" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "999990" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coin_contract.balances['stu'] # Notice that it is not affecting the main account. It is just an allowance account." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "bank_contract.deposit(amount=450) # This should modify our balance and give bank 450 coins. Let's check!" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "stu balance: 999540\n", "bank balance: 450\n", "bank allowance: 50\n" ] } ], "source": [ "stu_balance = coin_contract.balances['stu']\n", "bank_balance = coin_contract.balances['bank']\n", "\n", "# This should only be 50, because 450 were spent on the bank's behalf.\n", "bank_allowance = coin_contract.balances['stu', 'bank']\n", "\n", "print('stu balance: {}\\nbank balance: {}\\nbank allowance: {}'.format(\n", " stu_balance,\n", " bank_balance,\n", " bank_allowance\n", "))" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "450" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bank_contract.balances['stu'] # Our account in the bank reflects the total" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding another participant to the equation\n", "\n", "Now, let's transfer some coins to another account and do the same thing to see how the bank's total account value goes up and is the sum of all of the subaccounts from under it. We will also withdraw our coins from the bank and see them reappear in our balance on the coin contract." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "# 'stu' will transfer some coins to 'raghu' to put in the bank.\n", "coin_contract.transfer(amount=5000, to='raghu')" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "# Remember, the default signer on this client is 'stu' so we have to set it to 'raghu' to call a function from him.\n", "coin_contract.allow(amount=4000, spender='bank', signer='raghu')" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "raghu balance: 5000\n", "bank allowance for raghu: 4000\n" ] } ], "source": [ "raghu_balance = coin_contract.balances['raghu']\n", "bank_raghu_allowance = coin_contract.balances['raghu', 'bank']\n", "\n", "print('raghu balance: {}\\nbank allowance for raghu: {}'.format(raghu_balance, bank_raghu_allowance))" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You can't spend that!\n" ] } ], "source": [ "# If raghu tries to deposit more than 4000, an error will occur\n", "try:\n", " bank_contract.deposit(amount=4001, signer='raghu')\n", "except AssertionError as e:\n", " print(e)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "# Less than 4000 or 4000 will do.\n", "bank_contract.deposit(amount=3999, signer='raghu')" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "raghu balance: 1001\n", "bank allowance for raghu: 1\n" ] } ], "source": [ "# Updated balances\n", "raghu_balance = coin_contract.balances['raghu']\n", "bank_raghu_allowance = coin_contract.balances['raghu', 'bank']\n", "\n", "print('raghu balance: {}\\nbank allowance for raghu: {}'.format(raghu_balance, bank_raghu_allowance))" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bank balance: 4449\n", "stu bank account: 450\n", "raghu bank account: 3999\n" ] } ], "source": [ "# The bank will now have 450 + 3999 coins total, with two subaccounts.\n", "bank_balance = coin_contract.balances['bank']\n", "stu_bank_account = bank_contract.balances['stu']\n", "raghu_bank_account = bank_contract.balances['raghu']\n", "\n", "print('bank balance: {}\\nstu bank account: {}\\nraghu bank account: {}'.format(\n", " bank_balance,\n", " stu_bank_account,\n", " raghu_bank_account\n", "))" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You don't have enough in your account!\n" ] } ], "source": [ "# If we try to withdraw more than our account balance from the bank, then we will have an AssertionError\n", "try:\n", " bank_contract.withdraw(amount=500)\n", "except AssertionError as e:\n", " print(e)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "stu prior balance:994540\n", "stu after balance: 994640\n" ] } ], "source": [ "stu_prior_balance = coin_contract.balances['stu']\n", "bank_contract.withdraw(amount=100)\n", "stu_after_balance = coin_contract.balances['stu']\n", "\n", "print('stu prior balance: {}\\nstu after balance: {}'.format(\n", " stu_prior_balance,\n", " stu_after_balance\n", "))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that you understand the basics of the `ctx` object, you can create extremely robust smart contracts that pass around context. In the next section, we will learn more about the import system and multihashes to add even more capabilities to your smart contracts!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: examples/05 Imports and Advanced Data Storage.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Imports and Advanced Data Storage\n", "To add even more functionality to smart contracts, you can import the exported functions of other smart contracts. You can also store complex data in the form of Python objects, which is useful for storing things like lists, tuples, and dates. Here's how." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def import_this():\n", " @export\n", " def dumb_func():\n", " return 4\n", " \n", "def to_import():\n", " import import_this\n", " @export\n", " def dumber_func():\n", " return import_this.dumb_func()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "from contracting.client import ContractingClient\n", "client = ContractingClient(signer='stu')\n", "client.flush()\n", "client.submit(import_this)\n", "client.submit(to_import)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ti_contract = client.get_contract('to_import')\n", "ti_contract.dumber_func()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Easy! Notice that you can only import entire contracts. You cannot use `from a import b` notation to import just a singular function from a smart contract. Furthermore, dynamic imports will be added at a later time to allow importing a contract by name that can be used as an argument." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Advanced Data\n", "Imagine a game where you can buy or sell pixels. Each pixel has an X and a Y coordinate. At each pixel, we want to store the owner, the price, and the color. While this might sound complicated, it is actually extremely straight forward to do.\n", "\n", "All `Hash` objects have the ability to store up to 16 dimensions of information in a key that a max of 1024 bytes in size. The stored object is just JSON using the standard Python JSON encoder and decoder, so you can store things such as lists, sets, etc. At this point in time, there is no limit on how large the value for a key can be, but it will be capped in the future. The current floating figure is 256 bytes to 1024 bytes, so design your contracts accordingly.\n", "\n", "Unlike normal Python dictionaries, which `Hash` objects are similar to, you can add different dimensions to your `Hash` object without an issue. For example:\n", "\n", "```\n", "h = Hash()\n", "h['one'] = 15\n", "h['one', 'two'] = 20\n", "h['two'] = 25\n", "h['a', 'b', 'c', 'd', 'e', 'f', 'g'] = 6\n", "```\n", "\n", "Let's try to build that pixel application." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def coin():\n", " balances = Hash(default_value=0)\n", " token_name = 'Stubucks'\n", " token_symbol = 'SBX'\n", " \n", " @construct\n", " def seed():\n", " # Whoever creates this smart contract is minted 1,000,000 tokens\n", " balances[ctx.caller] = 1000000\n", " \n", " @export\n", " def transfer(amount, to):\n", " # Make sure that the person calling this function has the amount they are trying to transfer\n", " assert balances[ctx.caller] >= amount, \"You don't have enough to spend!\"\n", " \n", " # If so, deduct from their account and send to who they want to send to\n", " balances[ctx.caller] -= amount\n", " balances[to] += amount\n", " \n", " @export\n", " def allow(amount, spender):\n", " # This creates a 'subaccount' to allow the spender to transfer from our account a certain amount\n", " balances[ctx.caller, spender] = amount\n", " \n", " @export\n", " def spend_on_behalf(amount, owner, to):\n", " # We make sure the subaccount has enough coins to spend\n", " assert balances[owner, ctx.caller] >= amount, \"You can't spend that!\"\n", " \n", " # If so, deduct from the amount that the subaccount can spend\n", " balances[owner, ctx.caller] -= amount\n", " \n", " # And then make the transfer\n", " balances[owner] -= amount\n", " balances[to] += amount\n", "\n", "def pixel_game():\n", " import coin\n", " \n", " # We set the default value of dictionary values to None for testing if they exist\n", " pixels = Hash(default_value=None)\n", " \n", " # These constants can never be changed. If you want mutable variables, use Variable objects and provide getters\n", " # and setters.\n", " max_x = 256\n", " max_y = 256\n", " \n", " # Just a palette to simplify this\n", " color_min = 0\n", " color_max = 16\n", " \n", " @export\n", " def buy_pixel(x, y, color, amount):\n", " assert x < max_x and x >= 0, 'X out of bounds!'\n", " assert y < max_y and y >= 0, 'Y out of bounds!'\n", " \n", " assert color < color_max and color >= color_min, 'Color is out of bounds!'\n", " \n", " # If we make it to here, we can access the pixel.\n", " pixel = pixels[x, y]\n", " \n", " # If it is None, it's never been bought before, so we can buy it outright\n", " if pixel is None:\n", " # Take the coins and store it in the pixel game's account\n", " overwrite_pixel(x, y, color, amount, ctx.caller)\n", " \n", " else:\n", " # Otherwise, the pixel is a dictionary, so we can access it like such\n", " assert amount > pixel['amount'], 'You must pay at least {} to purchase.'.format(pixel['amount'])\n", " overwrite_pixel(x, y, color, amount, ctx.caller)\n", " \n", " def overwrite_pixel(x, y, color, amount, owner):\n", " coin.spend_on_behalf(amount=amount, owner=ctx.caller, to=ctx.this)\n", " \n", " pixels.set[x, y] = {\n", " 'owner': owner,\n", " 'amount': amount,\n", " 'color': color\n", " }\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To keep things simple, let's just assume that we can buy pixels. If you see `overwrite_pixel`, you'll notice that we are setting the X and Y coordinates to a Python dictionary rather than just a primitive type. When we access the database again to pull it out, it is decoded into a Python dictionary again, so you can access it like how you are used to.\n", "\n", "Let's try to buy a pixel!" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "client.submit(coin)\n", "client.submit(pixel_game)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "coin_contract = client.get_contract('coin')\n", "pixel_contract = client.get_contract('pixel_game')" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1000000" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coin_contract.balances['stu'] # Let's make sure we have the coins to make a pixel purchase." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "coin_contract.allow(amount=1, spender='pixel_game')\n", "pixel_contract.buy_pixel(x=10, y=10, color=5, amount=1)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "999999" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coin_contract.balances['stu'] # The balance has deducted properly" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'owner': 'stu', 'amount': 1, 'color': 5}" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pixel_contract.pixels[10, 10] # Now we can access the information and recieve the dictionary back" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(pixel_contract.pixels[10, 10]) # Proof the dictionary is in fact a dictionary!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: examples/Rock Paper Scissors Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Rock Paper Scissors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this tutorial you will learn how to build a Lamden smart contract for the classic game Rock, Paper, Scissors. Through this contract, two players can play Rock, Paper, Scissors over the Lamden Blockchain. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is an advanced example. Please make sure you have gone over the previous examples before this one so you have a better understanding of contracting as a whole. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Important Disclaimer:\n", "This is an example smart contract. It is not production ready. It needs more tests. It also does not address certain timing edge cases. For example a user can just not reveal their choice and stall a game forever. The solution is left as an exercise for you. It is important when building a smart contract that you think of all edge cases and test heavily before deploying it to the blockchain. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First let's import some things we will need later." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from contracting.stdlib.bridge.hashing import sha3\n", "from contracting.client import ContractingClient\n", "import secrets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below is the function `rps_contract` containing the entire Rock, Paper, Scissors contract. It is never run as a Python function. Instead contracting extracts constructor, storage variables, public and private functions from it when it is submitted to the blockchain. We recomend reading on below the contract and then investigating the contract functions when they are being called." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def rps_contract():\n", " # This tells the blockchain to make space for data. Variable and Hash are not Python builtins. \n", " # They are globals (globals are something you can access without importing it) made available by contracting. \n", " # Calling Variable() creates a new piece of memory within the blockchain, that can hold a single value. \n", " # If we say \"foo = Variable()\" then we can interact with that piece of memory through \"foo\". \n", " # We can set the piece of memory to 1, for example by calling \"foo.set(1)\".\n", " # Setting the value will change the value for the next call to the smart contract.\n", " # We can get the piece of memory by calling \"foo.get()\".\n", " next_game_id = Variable()\n", " \n", " game_id_to_password_hash = Hash()\n", " \n", " game_id_to_player1_choice_hash = Hash()\n", " game_id_to_player2_choice_hash = Hash()\n", " \n", " game_id_to_player1_choice = Hash()\n", " game_id_to_player2_choice = Hash()\n", " \n", " # @construct means that this is the function that will be called ONCE when the smart contract is created\n", " @construct\n", " def constructor():\n", " # Game id starts at 0.\n", " next_game_id.set(0)\n", " \n", " # @export makes this function public, so it can be called by anyone on a deployed contract.\n", " # By calling start_game player1 creates a game and also submits their hashed and salted choice.\n", " @export\n", " def start_game(password_hash, player1_choice_hash):\n", " # This retrieves unique game Id from the blockchain. \n", " unique_game_id = next_game_id.get()\n", " \n", " assert get_game_state(unique_game_id) == \"game_doesnt_exist\", \"this is a bug in the contract. new game id already exists\"\n", " \n", " # This increments the number by 1, so the next game has a unique Id.\n", " next_game_id.set(next_game_id.get() + 1) \n", " \n", " # Hash throws error on integer keys. That's why we convert to a string\n", " # Remember player1 choice hash, and password hash for this game. \n", " game_id_to_password_hash[str(unique_game_id)] = password_hash\n", "\n", " game_id_to_player1_choice_hash[str(unique_game_id)] = player1_choice_hash\n", " \n", " # assert get_game_state(unique_game_id) == \"only_player1_submitted\", \"this is a bug in the contract. after starting a new game only player 1 must have submitted\"\n", " \n", " return unique_game_id\n", " \n", " # By calling submit_choice player2 submits their hashed and salted choice.\n", " @export\n", " def submit_choice(game_id, game_password, player2_choice_hash):\n", " assert get_game_state(game_id) == \"only_player1_submitted\", \"submit_choice can only be called if only player 1 has submitted their choice\"\n", "\n", " # Check that this is the right password for the game.\n", " assert hashlib.sha3(game_password) == game_id_to_password_hash[str(game_id)], 'Wrong password!'\n", " \n", " # Remember player2's choice.\n", " game_id_to_player2_choice_hash[str(game_id)] = player2_choice_hash\n", " \n", " assert get_game_state(game_id) == \"both_players_submitted\", \"this is a bug in the contract. after submitting player2 choice both players must have submitted\"\n", " \n", " return\n", " \n", " # Returns 'player1_wins' if player1 is the winner.\n", " # Returns 'player2_wins' if player2 is the winner.\n", " # Returns 'tie' if both players made the same choice.\n", " # Returns 'game_doesnt_exist' if the game doesn't exist\n", " # Returns 'player1_has_submitted' if the game has been started and only player1 has submitted their choice\n", " # Returns 'both_players_have_submitted' if both players have submitted but none has revealed their choice\n", " # Returns 'only_player1_revealed' after player1 but not player2 has revealed their choice\n", " # Returns 'only_player2_revealed' after player2 but not player1 has revealed their choice\n", " \n", " @export\n", " def get_game_state(game_id):\n", " if next_game_id.get() <= game_id:\n", " return \"game_doesnt_exist\"\n", " \n", " player1_hashed_choice = game_id_to_player1_choice_hash[str(game_id)]\n", " player2_hashed_choice = game_id_to_player2_choice_hash[str(game_id)]\n", "\n", " if player1_hashed_choice is not None and player2_hashed_choice is None:\n", " return \"only_player1_submitted\"\n", " \n", " player1_choice = game_id_to_player1_choice[str(game_id)]\n", " player2_choice = game_id_to_player2_choice[str(game_id)]\n", " \n", " if player1_hashed_choice is not None and player2_hashed_choice is not None and player1_choice is None and player2_choice is None:\n", " return \"both_players_submitted\"\n", " \n", " assert player1_hashed_choice is not None and player2_hashed_choice is not None, \"this is a bug in the contract. error code 1\"\n", " # For the rest of the function we know that both players have submitted their choices \n", " # and one player has revealed their choice\n", " \n", " if player1_choice is not None and player2_choice is None:\n", " return \"only_player1_revealed\"\n", " \n", " if player1_choice is None and player2_choice is not None:\n", " return \"only_player2_revealed\"\n", " \n", " # Make sure that both players have submitted their choices\n", " assert player1_choice is not None and player2_choice is not None, \"this is a bug in the contract. error code 2\"\n", " # For the rest of the function we know that both players have submitted their choices\n", " \n", " # Now that we have both choices we can resolve the game\n", " \n", " if player1_choice == player2_choice:\n", " return \"tie\"\n", "\n", " if beats(player1_choice, player2_choice):\n", " return \"player1_wins\"\n", " \n", " if beats(player2_choice, player1_choice):\n", " return \"player2_wins\"\n", " \n", " @export\n", " def is_valid_choice(choice):\n", " return choice in [\"rock\", \"paper\", \"scissors\"]\n", " \n", " # Returns whether choice1 beats choice2\n", " @export\n", " def beats(choice1, choice2):\n", " assert is_valid_choice(choice1), \"choice1 must be a valid choice\"\n", " assert is_valid_choice(choice2), \"choice2 must be a valid choice\"\n", "\n", " if choice1 == \"rock\" and choice2 == \"scissors\":\n", " return True\n", " \n", " if choice1 == \"paper\" and choice2 == \"rock\":\n", " return True\n", " \n", " if choice1 == \"scissors\" and choice2 == \"paper\":\n", " return True\n", " \n", " return False\n", " \n", " # By calling reveal a player can reveal their unhashed choice.\n", " # Player1 has to call the function with is_player1=true.\n", " # Player2 has to call the function with is_player1=false.\n", " @export\n", " def reveal(game_id, choice, choice_salt, is_player1):\n", " if is_player1:\n", " assert get_game_state(game_id) in [\"both_players_submitted\", \"only_player2_revealed\"], \"reveal can only be called by player1 if no player or only player2 has revealed\"\n", " else:\n", " assert get_game_state(game_id) in [\"both_players_submitted\", \"only_player1_revealed\"], \"reveal can only be called by player2 if no player or only player1 has revealed\"\n", " \n", " # Make sure players can only reveal valid choices\n", " assert is_valid_choice(choice), \"choice must be rock, paper or scissors\"\n", " \n", " # Now we need to check that the reveal hashes to what was submitted earlier\n", " \n", " salted_choice = choice + choice_salt\n", " hashed_choice = hashlib.sha3(salted_choice)\n", " \n", " if is_player1:\n", " assert game_id_to_player1_choice_hash[str(game_id)] == hashed_choice, \"Player 1 has revealed a choice different from what they submitted\"\n", " else:\n", " assert game_id_to_player2_choice_hash[str(game_id)] == hashed_choice, \"Player 2 has revealed a choice different from what they submitted\"\n", " \n", " # Now we're sure that the player has revealed the choice they have previously submitted the hashed salted version of\n", " \n", " # Remember the choice\n", " if is_player1:\n", " game_id_to_player1_choice[str(game_id)] = choice\n", " else:\n", " game_id_to_player2_choice[str(game_id)] = choice\n", " \n", " return" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Welcome back! Lets get this contract into the blockchain. To interact with the blockchain we need a client. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "client = ContractingClient(signer='ren')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Get rid of all state of the blockchain so we have a blank slate. Otherwise running this script twice causes problems. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "client.flush()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we submit the contract to the blockchain. `client.submit` doesn't run the function `rps_contract` but examines it, and extracts its public functions, state variables, ..." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "client.submit(rps_contract)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Get a handle for the contract that we can interact with." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "contract = client.get_contract('rps_contract')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the contract in the blockchain we can now play Rock, Paper, Scissors. Our players for this example are Alice and Bob." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alice (player 1) chooses single use password. Only the person that has the password can join the game and play with Alice. Everything that starts with alice_ is only visible to Alice. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "alice_game_password = \"trollbridge\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alice hashes the password so she can submit it to the blockchain without sharing the actual password. She does this because everything on the blockchain is public, and she wants only the person she chooses to play the game with to have the password. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'47a5bcfb0d81053f5025ab57e6b94f43751f91bdb16fc0d63595223dc78ec1b4'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alice_game_password_hash = sha3(alice_game_password)\n", "alice_game_password_hash" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alice chooses rock." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "alice_choice = \"rock\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can't submit the choice to the blockchain as plain text, because then Bob (player 2) can see it and win by choosing paper. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'bd996e2dc82a7c3e2c2da41291648e852b62b01fb09bcb6ed77975c273f08404'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alice_choice_hash = sha3(alice_choice)\n", "alice_choice_hash" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The problem with submitting Alices choice like this is the 3 choices will be hashed the same every time. Bob (player 2) can know what each of the hashes for the 3 choices and pick paper to win. To fix this Alice needs to pick a random salt to hash with her choice so that Bob can't guess her choice by looking at the hash." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'88ceef7a2c748432d5a150fcff5df717c8a67298365ae2e1969be4ee856ce39e'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alice_choice_salt = secrets.token_hex(32)\n", "alice_choice_salt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can combine alice_choice and alice_choice_salt and hash them together to create something that Bob can't guess Alices choice from. But Alice can later submit her choice and the salt to prove her choice." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'rock88ceef7a2c748432d5a150fcff5df717c8a67298365ae2e1969be4ee856ce39e'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alice_salted_choice = alice_choice + alice_choice_salt\n", "alice_salted_choice" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'dcbd95890db4648405b1e04541b6dcabacc1c3e958172171262dc11d6dacebf7'" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alice_salted_choice_hash = sha3(alice_salted_choice)\n", "alice_salted_choice_hash" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before a game is started it is in state `\"game_doesnt_exist\"`" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "assert contract.get_game_state(game_id=0) == \"game_doesnt_exist\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now Alice starts a game so she can invite Bob to play." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alice_game_id = contract.start_game(password_hash=alice_game_password_hash, player1_choice_hash=alice_salted_choice_hash) \n", "alice_game_id" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alice gets back a game Id." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The game is now in state \"only_player1_submitted\"" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "assert contract.get_game_state(game_id=alice_game_id) == \"only_player1_submitted\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now Alice has to tell Bob the password and the game Id. This could be done over a messenger or built into the frontend of an application. \n", "\n", "Everything that starts with bob_ is only visible to Bob." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "bob_game_password = alice_game_password\n", "bob_game_id = alice_game_id" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now it is Bobs turn.\n", "Bob chooses scissors." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "bob_choice = \"scissors\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now Bob has to salt his choice." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'13729fb6834f46b30046d82ba9e624821a479ed1f7714152c8a4da81b42d1213'" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bob_choice_salt = secrets.token_hex(32)\n", "bob_choice_salt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now we combine bob_choice and bob_choice_salt together and hash them." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'scissors13729fb6834f46b30046d82ba9e624821a479ed1f7714152c8a4da81b42d1213'" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bob_salted_choice = bob_choice + bob_choice_salt\n", "bob_salted_choice" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'9af37ec1317afab75c13f89f1d4a8b4c39f77ae52e7ad05116a5f8b2f9995125'" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bob_salted_choice_hash = sha3(bob_salted_choice)\n", "bob_salted_choice_hash" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Bob now needs to submit his choice to the blockchain. Only Bob has the game password so only Bob can join Alices game." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "contract.submit_choice(game_password=bob_game_password, game_id=bob_game_id, player2_choice_hash=bob_salted_choice_hash)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The game is now in state \"both_players_submitted\"" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "assert contract.get_game_state(game_id=bob_game_id) == \"both_players_submitted\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that both players have submitted their hashed and salted choices, both players can reveal their choices.\n", "The order doesn't matter. Alice goes first in this example." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "contract.reveal(game_id=alice_game_id, choice=alice_choice, choice_salt=alice_choice_salt, is_player1=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The game is now in state \"only_player1_revealed\"" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "assert contract.get_game_state(game_id=alice_game_id) == \"only_player1_revealed\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Bob goes second" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "contract.reveal(game_id=bob_game_id, choice=bob_choice, choice_salt=bob_choice_salt, is_player1=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can see who won the game" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'player1_wins'" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "contract.get_game_state(game_id=alice_game_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As expected Alice, who is player1, wins!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## tests" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Test function is_valid_choice" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "assert contract.is_valid_choice(choice=\"rock\")\n", "assert contract.is_valid_choice(choice=\"paper\")\n", "assert contract.is_valid_choice(choice=\"scissors\")\n", "assert not contract.is_valid_choice(choice=\"airplane\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Test function beats" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "assert contract.beats(choice1=\"rock\", choice2=\"scissors\")\n", "assert not contract.beats(choice1=\"rock\", choice2=\"rock\")\n", "assert not contract.beats(choice1=\"rock\", choice2=\"paper\")\n", "\n", "assert contract.beats(choice1=\"paper\", choice2=\"rock\")\n", "assert not contract.beats(choice1=\"paper\", choice2=\"paper\")\n", "assert not contract.beats(choice1=\"paper\", choice2=\"scissors\")\n", "\n", "assert contract.beats(choice1=\"scissors\", choice2=\"paper\")\n", "assert not contract.beats(choice1=\"scissors\", choice2=\"scissors\")\n", "assert not contract.beats(choice1=\"scissors\", choice2=\"rock\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: pyproject.toml ================================================ [tool.poetry] name = "xian-contracting" version = "1.0.1" description = "Xian Network Python Contracting Engine" authors = ["Xian Network "] readme = "README.md" packages = [{include = "contracting", from = "src"}] license = "GPL-3.0-only" repository = "https://github.com/xian-network/xian-contracting" keywords = ["blockchain", "xian", "contracting", "python"] classifiers = [ "Programming Language :: Python :: 3.11", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Development Status :: 5 - Production/Stable", ] [tool.poetry.dependencies] python = "~=3.11.0" astor = "0.8.1" pycodestyle = "2.10.0" autopep8 = "1.5.7" iso8601 = "*" h5py = "*" cachetools = "*" loguru = "*" pynacl = "*" psutil = "*" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" ================================================ FILE: release.sh ================================================ #!/bin/bash set -e # Exit on any error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Function to print with color print_status() { echo -e "${GREEN}==>${NC} $1" } print_warning() { echo -e "${YELLOW}WARNING:${NC} $1" } print_error() { echo -e "${RED}ERROR:${NC} $1" } # Check if a version bump type was provided if [ -z "$1" ]; then print_error "Please provide a version bump type: patch, minor, or major" echo "Usage: ./release.sh [patch|minor|major]" exit 1 fi # Validate version bump type if [ "$1" != "patch" ] && [ "$1" != "minor" ] && [ "$1" != "major" ]; then print_error "Invalid version bump type. Please use: patch, minor, or major" exit 1 fi # Make sure we're on the master branch BRANCH=$(git branch --show-current) if [ "$BRANCH" != "master" ]; then print_error "Please switch to the master branch before creating a release" exit 1 fi # Make sure the working directory is clean if [ -n "$(git status --porcelain)" ]; then print_error "Working directory is not clean. Please commit or stash changes first." exit 1 fi # Check if poetry is installed if ! command -v poetry &> /dev/null; then print_error "Poetry could not be found. Please install it first." exit 1 fi # Check if pytest is installed if ! poetry run python -c "import pytest" 2>/dev/null; then print_warning "pytest is not installed. Skipping tests." RUN_TESTS=false else RUN_TESTS=true fi # Pull latest changes print_status "Pulling latest changes from master..." git pull origin master # Show what the new version will be and ask for confirmation CURRENT_VERSION=$(poetry version -s) NEW_VERSION=$(poetry version $1 --dry-run) print_status "Current version: $CURRENT_VERSION" print_status "New version will be: $NEW_VERSION" # Generate changelog LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "none") if [ "$LAST_TAG" != "none" ]; then print_status "Generating changelog since $LAST_TAG..." CHANGELOG=$(git log "$LAST_TAG"..HEAD --oneline --pretty=format:"- %s") else CHANGELOG=$(git log --oneline --pretty=format:"- %s") fi echo -e "\nChangelog:" echo "$CHANGELOG" echo # Check dependencies print_status "Checking for outdated dependencies..." poetry show --outdated || true # Run tests if available if [ "$RUN_TESTS" = true ]; then print_status "Running tests..." poetry run pytest || { print_error "Tests failed!" exit 1 } fi # Final confirmation echo print_status "Ready to release version $NEW_VERSION" read -p "Continue? (y/n) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then print_status "Release cancelled." exit 1 fi # Update version using Poetry print_status "Bumping version ($1)..." poetry version $1 # Create release notes file RELEASE_NOTES="release_notes.md" echo "# Release Notes for v$NEW_VERSION" > $RELEASE_NOTES echo "" >> $RELEASE_NOTES echo "## Changes" >> $RELEASE_NOTES echo "$CHANGELOG" >> $RELEASE_NOTES # Stage and commit version bump print_status "Committing version bump..." git add pyproject.toml $RELEASE_NOTES git commit -m "Bump version to $NEW_VERSION Release Notes: $CHANGELOG" # Create and push tag print_status "Creating and pushing tag v$NEW_VERSION..." git tag -a "v$NEW_VERSION" -m "Version $NEW_VERSION $CHANGELOG" git push && git push --tags # Cleanup rm $RELEASE_NOTES print_status "Release process initiated!" print_status "Version $NEW_VERSION will be published to PyPI and GitHub releases automatically." print_status "You can monitor the progress at: https://github.com/xian-network/xian-contracting/actions" ================================================ FILE: src/contracting/__init__.py ================================================ ================================================ FILE: src/contracting/client.py ================================================ from contracting.execution.executor import Executor from contracting.storage.driver import Driver from contracting.compilation.compiler import ContractingCompiler from contracting.stdlib.bridge.time import Datetime from datetime import datetime from functools import partial from types import FunctionType import ast import inspect import astor import autopep8 import os from . import constants from .storage.orm import Variable from .storage.orm import Hash class AbstractContract: def __init__(self, name, signer, environment, executor: Executor, funcs, return_full_output=False): self.name = name self.signer = signer self.environment = environment self.executor = executor self.functions = funcs # set up virtual functions for f in funcs: # unpack tuple packed in SenecaClient func, kwargs = f # set the kwargs to None. these will fail if they are not provided default_kwargs = {} for kwarg in kwargs: default_kwargs[kwarg] = None # each function is a partial that allows kwarg overloading and overriding setattr(self, func, partial(self._abstract_function_call, signer=self.signer, contract_name=self.name, executor=self.executor, func=func, return_full_output=return_full_output, # environment=self.environment, **default_kwargs)) def keys(self): # Scope strictly to this contract's namespace return self.executor.driver.keys(f"{self.name}.") # a variable contains a DOT, but no __, and no : # a hash contains a DOT, no __, and a : # a constant contains __, a DOT, and : def quick_read(self, variable, key=None, args=None): a = [] if key is not None: a.append(key) if args is not None and isinstance(args, list): for arg in args: a.append(arg) k = self.executor.driver.make_key(contract=self.name, variable=variable, args=a) return self.executor.driver.get(k) def quick_write(self, variable, key=None, value=None, args=None): if key is not None: a = [key] else: a = [] if args is not None and isinstance(args, list): for arg in args: a.append(arg) k = self.executor.driver.make_key(contract=self.name, variable=variable, args=a) self.executor.driver.set(k, value) self.executor.driver.commit() def run_private_function(self, f, signer=None, environment=None, **kwargs): # Override kwargs if provided signer = signer or self.signer environment = environment or self.environment # Let executor access private functions self.executor.bypass_privates = True # Append private method prefix to function name if it isn't there already if not f.startswith(constants.PRIVATE_METHOD_PREFIX): f = '{}{}'.format(constants.PRIVATE_METHOD_PREFIX, f) # Execute result = self._abstract_function_call( signer=signer, executor=self.executor, contract_name=self.name, environment=environment, func=f, metering=None, now=None, **kwargs ) # Set executor back to restricted mode self.executor.bypass_privates = False return result def __getattr__(self, item): try: # return the attribute if it exists on the instance return self.__getattribute__(item) except AttributeError as e: # otherwise, attempt to resolve it. full name is contract.item fullname = '{}.{}'.format(self.name, item) # if the raw name exists, it is a __protected__ or a variable, so prepare for those if fullname in self.keys(): variable = Variable(contract=self.name, name=item, driver=self.executor.driver) # return just the value if it is __protected__ to prevent sets if item.startswith('__'): return variable.get() # otherwise, return the variable object with allows sets return variable # otherwise, see if contract.items: has more than one entry if len(self.executor.driver.values(prefix=self.name + '.' + item + ':')) > 0: # if so, it is a hash. return the hash object return Hash(contract=self.name, name=item, driver=self.executor.driver) # otherwise, the attribut does not exist, so throw the error. raise e def now(self): d = datetime.today() return Datetime(d.year, d.month, d.day, hour=d.hour, minute=d.minute) def _abstract_function_call( self, signer, executor, contract_name, func, environment=None, stamps=constants.DEFAULT_STAMPS, metering=None, now=None, return_full_output=False, **kwargs ): # for k, v in kwargs.items(): # assert v is not None, 'Keyword "{}" not provided. Must not be None.'.format(k) environment = environment or self.environment if now is None: now = self.now() if environment.get('now') is None: environment.update({'now': now}) output = executor.execute( sender=signer, contract_name=contract_name, function_name=func, kwargs=kwargs, stamps=stamps, environment=environment, metering=metering ) if executor.production: executor.sandbox.terminate() if output['status_code'] == 1: raise output['result'] if not return_full_output else output return output['result'] if not return_full_output else output class ContractingClient: def __init__( self, signer='sys', submission_filename=os.path.join(os.path.dirname(__file__), 'contracts/submission.s.py'), storage_home=constants.STORAGE_HOME, driver:Driver=None, metering=False, compiler=ContractingCompiler(), environment={}, ): driver = driver if driver is not None else Driver(storage_home=storage_home) self.executor = Executor(metering=metering, driver=driver) self.raw_driver = driver self.signer = signer self.compiler = compiler self.submission_filename = submission_filename self.environment = environment # Get submission contract from file if submission_filename is not None: # Seed the genesis contracts into the instance with open(self.submission_filename) as f: contract = f.read() self.raw_driver.set_contract(name='submission', code=contract) self.raw_driver.commit() # Get submission contract from state self.submission_contract = self.get_contract('submission') def set_submission_contract(self, filename=None, commit=True): state_contract = self.get_contract('submission') if filename is None: filename = self.submission_filename if filename is None and state_contract is None: raise AssertionError("No submission contract provided or found in state.") if filename is not None: with open(filename) as f: contract = f.read() self.raw_driver.delete_contract(name='submission') self.raw_driver.set_contract(name='submission', code=contract) if commit: self.raw_driver.commit() self.submission_contract = self.get_contract('submission') def flush(self): # flushes storage and resubmits genesis contracts self.raw_driver.flush_full() self.raw_driver.flush_cache() if self.submission_filename is not None: self.set_submission_contract() # Returns abstract contract which has partial methods mapped to each exported function. def get_contract(self, name): contract = self.raw_driver.get_contract(name) if contract is None: return None tree = ast.parse(contract) function_defs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)] funcs = [] for definition in function_defs: func_name = definition.name kwargs = [arg.arg for arg in definition.args.args] funcs.append((func_name, kwargs)) return AbstractContract( name=name, signer=self.signer, environment=self.environment, executor=self.executor, funcs=funcs, ) def closure_to_code_string(self, f): closure_code = inspect.getsource(f) closure_code = autopep8.fix_code(closure_code) closure_tree = ast.parse(closure_code) # Remove the enclosing function by swapping out the function def node with its children assert len(closure_tree.body) == 1, 'Module has multiple body nodes.' assert isinstance(closure_tree.body[0], ast.FunctionDef), 'Function definition not found at root.' func_def_body = closure_tree.body[0] closure_tree.body = func_def_body.body contract_code = astor.to_source(closure_tree) name = func_def_body.name return contract_code, name def lint(self, f, raise_errors=False): if isinstance(f, FunctionType): f, _ = self.closure_to_code_string(f) tree = ast.parse(f) violations = self.compiler.linter.check(tree) if violations is None: return None else: if raise_errors: for v in violations: raise Exception(v) else: return violations def compile(self, f): if isinstance(f, FunctionType): f, _ = self.closure_to_code_string(f) code = self.compiler.parse_to_code(f) return code def submit(self, f, name=None, metering=None, owner=None, constructor_args={}, signer=None): assert self.submission_contract is not None, "No submission contract set. Try set_submission_contract first." if isinstance(f, FunctionType): f, n = self.closure_to_code_string(f) if name is None: name = n assert name is not None, 'No name provided.' if signer is None: signer = self.signer self.submission_contract.submit_contract( name=name, code=f, owner=owner, constructor_args=constructor_args, metering=metering, signer=signer ) def get_contracts(self): contracts = [] for key in self.raw_driver.keys(): if key.endswith('.__code__'): contracts.append(key.replace('.__code__', '')) return contracts def get_var(self, contract, variable, arguments=[], mark=False): return self.raw_driver.get_var(contract, variable, arguments, mark) def set_var(self, contract, variable, arguments=[], value=None, mark=False): self.raw_driver.set_var(contract, variable, arguments, value, mark) ================================================ FILE: src/contracting/compilation/__init__.py ================================================ ================================================ FILE: src/contracting/compilation/compiler.py ================================================ import ast import astor from contracting import constants from contracting.compilation.linter import Linter class ContractingCompiler(ast.NodeTransformer): def __init__(self, module_name='__main__', linter=Linter()): self.module_name = module_name self.linter = linter self.lint_alerts = None self.constructor_visited = False self.private_names = set() self.orm_names = set() self.visited_names = set() # store the method visits def parse(self, source: str, lint=True): self.constructor_visited = False tree = ast.parse(source) if lint: self.lint_alerts = self.linter.check(tree) # compilation.fix_missing_locations(tree) tree = self.visit(tree) if self.lint_alerts is not None: raise Exception(self.lint_alerts) # check all visited nodes and see if they are actually private # An Expr node can have a value func of compilation.Name, or compilation. # Attribute which you much access the value of. # TODO: This code branching is not ideal and should be investigated for simplicity. for node in self.visited_names: if node.id in self.private_names or node.id in self.orm_names: node.id = self.privatize(node.id) ast.fix_missing_locations(tree) # reset state self.private_names = set() self.orm_names = set() self.visited_names = set() return tree @staticmethod def privatize(s): return '{}{}'.format(constants.PRIVATE_METHOD_PREFIX, s) def compile(self, source: str, lint=True): tree = self.parse(source, lint=lint) compiled_code = compile(tree, '', 'exec') return compiled_code def parse_to_code(self, source, lint=True): tree = self.parse(source, lint=lint) code = astor.to_source(tree) return code def visit_FunctionDef(self, node): # Presumes all decorators are valid, as caught by linter. if node.decorator_list: # Presumes that a single decorator is passed. This is caught by the linter. decorator = node.decorator_list.pop() # change the name of the init function to '____' so it is uncallable except once if decorator.id == constants.INIT_DECORATOR_STRING: node.name = '____' elif decorator.id == constants.EXPORT_DECORATOR_STRING: # Transform @export decorators to @__export(contract_name) decorators decorator.id = '{}{}'.format('__', constants.EXPORT_DECORATOR_STRING) new_node = ast.Call( func=decorator, args=[ast.Str(s=self.module_name)], keywords=[] ) node.decorator_list.append(new_node) else: self.private_names.add(node.name) node.name = self.privatize(node.name) self.generic_visit(node) return node def visit_Assign(self, node): if (isinstance(node.value, ast.Call) and not isinstance(node.value.func, ast.Attribute) and node.value.func.id in constants.ORM_CLASS_NAMES): node.value.keywords.append(ast.keyword('contract', ast.Str(self.module_name))) node.value.keywords.append(ast.keyword('name', ast.Str(node.targets[0].id))) self.orm_names.add(node.targets[0].id) self.generic_visit(node) return node def visit_Name(self, node): self.visited_names.add(node) return node def visit_Expr(self, node): self.generic_visit(node) return node def visit_Num(self, node): if isinstance(node.n, float): return ast.Call(func=ast.Name(id='decimal', ctx=ast.Load()), args=[ast.Str(str(node.n))], keywords=[]) return node ================================================ FILE: src/contracting/compilation/linter.py ================================================ import ast import sys from .. import constants from ..compilation.whitelists import ( ALLOWED_AST_TYPES, ALLOWED_ANNOTATION_TYPES, VIOLATION_TRIGGERS, ILLEGAL_BUILTINS, ILLEGAL_AST_TYPES ) class Linter(ast.NodeVisitor): def __init__(self): self._violations = [] self._functions = [] self._is_one_export = False self._is_success = True self._constructor_visited = False self.orm_names = set() self.visited_args = set() self.return_annotation = set() self.arg_types = set() self.builtins = list(set(list(sys.stdlib_module_names) + list(sys.builtin_module_names))) def ast_types(self, t, lnum): if type(t) not in ALLOWED_AST_TYPES: str = "Line {}".format(lnum) + " : " + VIOLATION_TRIGGERS[0] + " : {}" .format(type(t).__name__) self._violations.append(str) self._is_success = False def not_system_variable(self, v, lnum): if v.startswith('_') or v.endswith('_'): str = "Line {} : ".format(lnum) + VIOLATION_TRIGGERS[1] + " : {}" .format(v) self._violations.append(str) self._is_success = False def no_nested_imports(self, node): for item in node.body: if type(item) in [ast.ImportFrom, ast.Import]: str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[2] self._violations.append(str) self._is_success = False def visit_Name(self, node): self.not_system_variable(node.id, node.lineno) if node.id == 'rt':# or node.id == 'Hash' or node.id == 'Variable': self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[13] self._violations.append(str) if node.id in ILLEGAL_BUILTINS and node.id != 'float': self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[13] self._violations.append(str) self.generic_visit(node) return node def visit_Attribute(self, node): self.not_system_variable(node.attr, node.lineno) if node.attr == 'rt': self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[13] self._violations.append(str) self.generic_visit(node) return node def visit_Import(self, node): for n in node.names: if n.name in self.builtins: self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[13] self._violations.append(str) return node def visit_ImportFrom(self, node): str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[3] self._violations.append(str) self._is_success = False # TODO: Why are we even doing any logic instead of just failing on visiting these? def visit_ClassDef(self, node): str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[5] self._violations.append(str) self._is_success = False self.generic_visit(node) return node def visit_AsyncFunctionDef(self, node): str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[6] self._violations.append(str) self._is_success = False self.generic_visit(node) return node def visit_Assign(self, node): # resource_names, func_name = Assert.valid_assign(node, Parser.parser_scope) if isinstance(node.value, ast.Name): if node.value.id == 'Hash' or node.value.id == 'Variable' or node.value.id == 'LogEvent': self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[13] self._violations.append(str) if (isinstance(node.value, ast.Call) and not isinstance(node.value.func, ast.Attribute) and node.value.func.id in constants.ORM_CLASS_NAMES): if node.value.func.id in ['Variable', 'Hash', 'LogEvent']: kwargs = [k.arg for k in node.value.keywords] if 'contract' in kwargs or 'name' in kwargs: self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[10] self._violations.append(str) if ast.Tuple in [type(t) for t in node.targets] or isinstance(node.value, ast.Tuple): self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[11] self._violations.append(str) try: self.orm_names.add(node.targets[0].id) except AttributeError: pass self.generic_visit(node) return node def visit_AugAssign(self, node): # TODO: Checks here? self.generic_visit(node) return node def visit_Call(self, node: ast.Call): # Prevent calling of illegal builtins if isinstance(node.func, ast.Name): if node.func.id in ILLEGAL_BUILTINS: self._is_success = False str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[13] self._violations.append(str) self.generic_visit(node) return node def generic_visit(self, node): # Prevent calling of illegal builtins if type(node) in ILLEGAL_AST_TYPES: self._is_success = False s = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[0] self._violations.append(s) return super().generic_visit(node) def visit_Num(self, node): # NOTE: Integers are important for indexing and slicing so we cannot replace them. # They also will not suffer from rounding issues. # TODO: are any types we don't allow right now? self.generic_visit(node) return node def visit_FunctionDef(self, node): self.no_nested_imports(node) # Make sure there are no closures try: for n in node.body: if isinstance(n, ast.FunctionDef): str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[18] self._violations.append(str) self._is_success = False except: pass # Only allow 1 decorator per function definition. if len(node.decorator_list) > 1: str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[9] + \ ": Detected: {} MAX limit: 1".format(len(node.decorator_list)) self._violations.append(str) self._is_success = False export_decorator = False for d in node.decorator_list: # Only allow decorators from the allowed set. if d.id not in constants.VALID_DECORATORS: str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[7] + \ ": valid list: {}".format(d.id, constants.VALID_DECORATORS) self._violations.append(str) self._is_success = False if d.id == constants.EXPORT_DECORATOR_STRING: self._is_one_export = True export_decorator = True if d.id == constants.INIT_DECORATOR_STRING: if self._constructor_visited: str = "Line {}: ".format(node.lineno) + VIOLATION_TRIGGERS[8] self._violations.append(str) self._is_success = False self._constructor_visited = True # Add argument names to set to make sure that no ORM variable names are being reused in function def args arguments = node.args for a in arguments.args: self.visited_args.add((a.arg, node.lineno)) if export_decorator: if a.annotation is not None: try: self.arg_types.add((a.annotation.id, node.lineno)) except AttributeError: arg = a.annotation.value.id + '.' + a.annotation.attr self.arg_types.add((arg, node.lineno)) else: self.arg_types.add((None, node.lineno)) if export_decorator: if node.returns is not None: try: self.arg_types.add((a.annotation.id, node.lineno)) except AttributeError: arg = a.annotation.value.id + '.' + a.annotation.attr self.arg_types.add((arg, node.lineno)) else: self.return_annotation.add((None, node.lineno)) self.generic_visit(node) return node def annotation_types(self, t, lnum): if t is None: str = "Line {}".format(lnum) + " : " + VIOLATION_TRIGGERS[16] self._violations.append(str) self._is_success = False elif t not in ALLOWED_ANNOTATION_TYPES: str = "Line {}".format(lnum) + " : " + VIOLATION_TRIGGERS[15] + " : {}" .format(t) self._violations.append(str) self._is_success = False def check_return_types(self, t, lnum): if t is not None: str = "Line {}".format(lnum) + " : " + VIOLATION_TRIGGERS[17] + " : {}" .format(t) self._violations.append(str) self._is_success = False def _reset(self): self._violations = [] self._functions = [] self._is_one_export = False self._is_success = True self._constructor_visited = False self.orm_names = set() self.visited_args = set() self.return_annotation = set() self.arg_types = set() def _final_checks(self): for name, lineno in self.visited_args: if name in self.orm_names: str = "Line {}: ".format(lineno) + VIOLATION_TRIGGERS[14] self._violations.append(str) self._is_success = False if not self._is_one_export: str = "Line 0: " + VIOLATION_TRIGGERS[12] self._violations.append(str) self._is_success = False for t, lineno in self.arg_types: self.annotation_types(t,lineno) for t, lineno in self.return_annotation: self.check_return_types(t,lineno) def _collect_function_defs(self, root): for node in ast.walk(root): if isinstance(node, ast.FunctionDef): self._functions.append(node.name) elif isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom): for n in node.names: if n.asname: self._functions.append(n.asname) else: self._functions.append(n.name.split('.')[-1]) def check(self, ast_tree): self._reset() # pass 1 - collect function def and imports self._collect_function_defs(ast_tree) self.visit(ast_tree) self._final_checks() if self._is_success is False: return sorted(self._violations, key=lambda x: int(x.split(':')[0].split()[1])) else: return None def dump_violations(self): import pprint pp = pprint.PrettyPrinter(indent=4) pp.pprint(self._violations) ================================================ FILE: src/contracting/compilation/parser.py ================================================ import ast def methods_for_contract(contract_code: str): tree = ast.parse(contract_code) function_defs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)] funcs = [] for definition in function_defs: func_name = definition.name if func_name.startswith('__'): continue kwargs = [] for arg in definition.args.args: try: a = arg.annotation.id except AttributeError: a = arg.annotation.value.id + '.' + arg.annotation.attr kwargs.append({ 'name': arg.arg, 'type': a }) funcs.append({'name': func_name, 'arguments': kwargs}) return funcs def variables_for_contract(contract_code: str): tree = ast.parse(contract_code) assigns = [] for node in ast.walk(tree): if isinstance(node, ast.Assign): assigns.append(node) if isinstance(node, ast.FunctionDef): break variables = [] hashes = [] for assign in assigns: if type(assign.value) == ast.Call: if assign.value.func.id == 'Variable': variables.append(assign.targets[0].id.lstrip('__')) elif assign.value.func.id == 'Hash': hashes.append(assign.targets[0].id.lstrip('__')) return { 'variables': variables, 'hashes': hashes } ================================================ FILE: src/contracting/compilation/whitelists.py ================================================ import ast, builtins ALLOWED_BUILTINS = {'Exception', 'False', 'None', 'True', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'chr', 'dict', 'divmod', 'filter', 'format', 'frozenset', 'hex', 'int', 'isinstance', 'issubclass', 'import', 'len', 'list', 'map', 'max', 'min', 'oct', 'ord', 'pow', 'range', 'reversed', 'round', 'set', 'sorted', 'str', 'sum', 'tuple', 'zip'} ILLEGAL_BUILTINS = set(dir(builtins)) - ALLOWED_BUILTINS ALLOWED_AST_TYPES = {ast.Module, ast.Eq, ast.Call, ast.Dict, ast.Attribute, ast.Pow, ast.Index, ast.Not, ast.alias, ast.If, ast.FunctionDef, ast.Global, ast.GtE, ast.LtE, ast.Load, ast.arg, ast.Add, ast.Import, ast.ImportFrom, ast.Name, ast.Num, ast.BinOp, ast.Store, ast.Assert, ast.Assign, ast.AugAssign, ast.Subscript, ast.Compare, ast.Return, ast.NameConstant, ast.Expr, ast.keyword, ast.Sub, ast.arguments, ast.List, ast.Set, ast.Str, ast.UnaryOp, ast.Pass, ast.Tuple, ast.Div, ast.In, ast.NotIn, ast.Gt, ast.Lt, ast.Starred, ast.Mod, ast.NotEq, ast.For, ast.While, ast.ListComp, ast.comprehension, ast.Slice, ast.USub, ast.BoolOp, ast.And, ast.Or, ast.Mult, ast.IsNot, ast.Is, ast.Constant} ILLEGAL_AST_TYPES = { ast.AsyncFor, ast.AsyncFunctionDef, ast.AsyncWith, ast.Await, ast.ClassDef, ast.Ellipsis, ast.GeneratorExp, ast.Global, ast.ImportFrom, ast.Interactive, ast.Lambda, ast.MatMult, ast.Nonlocal, ast.Suite, ast.Try, ast.With, ast.Yield, ast.YieldFrom, } ALLOWED_ANNOTATION_TYPES = {'dict', 'list', 'str', 'int', 'float', 'bool', 'datetime.timedelta', 'datetime.datetime', 'Any'} VIOLATION_TRIGGERS = [ "S1- Illegal contracting syntax type used", "S2- Illicit use of '_' before variable", "S3- Illicit use of Nested imports", "S4- ImportFrom compilation nodes not yet supported", "S5- Contract not found in lib", "S6- Illicit use of classes", "S7- Illicit use of Async functions", "S8- Invalid decorator used", "S9- Multiple use of constructors detected", "S10- Illicit use of multiple decorators", "S11- Illicit keyword overloading for ORM assignments", "S12- Multiple targets to ORM definition detected", "S13- No valid contracting decorator found", "S14- Illegal use of a builtin", "S15- Reuse of ORM name definition in a function definition argument name", "S16- Illegal argument annotation used", "S17- No valid argument annotation found", "S18- Illegal use of return annotation", "S19- Illegal use of a nested function definition." ] ================================================ FILE: src/contracting/constants.py ================================================ from pathlib import Path RECURSION_LIMIT = 1024 DELIMITER = ':' INDEX_SEPARATOR = '.' HDF5_GROUP_SEPARATOR = '/' SUBMISSION_CONTRACT_NAME = 'submission' PRIVATE_METHOD_PREFIX = '__' EXPORT_DECORATOR_STRING = 'export' INIT_DECORATOR_STRING = 'construct' INIT_FUNC_NAME = '__{}'.format(PRIVATE_METHOD_PREFIX) VALID_DECORATORS = {EXPORT_DECORATOR_STRING, INIT_DECORATOR_STRING} ORM_CLASS_NAMES = {'Variable', 'Hash', 'ForeignVariable', 'ForeignHash', 'LogEvent'} MAX_HASH_DIMENSIONS = 16 MAX_KEY_SIZE = 1024 READ_COST_PER_BYTE = 1 WRITE_COST_PER_BYTE = 25 STAMPS_PER_TAU = 20 BLOCK_NUM_DEFAULT = -1 FILENAME_LEN_MAX = 255 DEFAULT_STAMPS = 1000000 STORAGE_HOME = Path().home().joinpath(".cometbft/xian") ================================================ FILE: src/contracting/contracts/__init__.py ================================================ ================================================ FILE: src/contracting/contracts/proxythis.py ================================================ @export def proxythis(con: str): return importlib.import_module(con).getthis() @export def nestedproxythis(con: str): return importlib.import_module(con).nested_exported() @export def noproxy(): return ctx.this, ctx.caller ================================================ FILE: src/contracting/contracts/submission.s.py ================================================ @__export('submission') def submit_contract(name: str, code: str, owner: Any=None, constructor_args: dict={}): if ctx.caller != 'sys': assert name.startswith('con_'), 'Contract must start with con_!' assert ctx.caller == ctx.signer, 'Contract cannot be called from another contract!' assert len(name) <= 64, 'Contract name length exceeds 64 characters!' assert name.islower(), 'Contract name must be lowercase!' __Contract().submit( name=name, code=code, owner=owner, constructor_args=constructor_args, developer=ctx.caller ) @__export('submission') def change_developer(contract: str, new_developer: str): d = __Contract()._driver.get_var(contract=contract, variable='__developer__') assert ctx.caller == d, 'Sender is not current developer!' __Contract()._driver.set_var( contract=contract, variable='__developer__', value=new_developer ) ================================================ FILE: src/contracting/contracts/thistest2.py ================================================ @export def exported(): return ctx.this, ctx.caller @export def getthis(): return ctx.this, ctx.caller @export def nested_exported(): return exported() ================================================ FILE: src/contracting/execution/__init__.py ================================================ ================================================ FILE: src/contracting/execution/executor.py ================================================ from contracting.execution import runtime from contracting.storage.driver import Driver from contracting.execution.module import install_database_loader, uninstall_builtins, enable_restricted_imports, disable_restricted_imports from contracting.stdlib.bridge.decimal import ContractingDecimal, CONTEXT from contracting.stdlib.bridge.random import Seeded from contracting import constants from copy import deepcopy import importlib import decimal class Executor: def __init__(self, production=False, driver=None, metering=True, currency_contract='currency', balances_hash='balances', bypass_privates=False, bypass_balance_amount=False, bypass_cache=False): self.metering = metering self.driver = driver if not self.driver: self.driver = Driver(bypass_cache=bypass_cache) self.production = production self.currency_contract = currency_contract self.balances_hash = balances_hash self.bypass_privates = bypass_privates self.bypass_balance_amount = bypass_balance_amount # For Stamp Estimation runtime.rt.env.update({'__Driver': self.driver}) def wipe_modules(self): uninstall_builtins() install_database_loader() def execute(self, sender, contract_name, function_name, kwargs, environment={}, auto_commit=False, driver=None, stamps=constants.DEFAULT_STAMPS, stamp_cost=constants.STAMPS_PER_TAU, metering=None) -> dict: current_driver_pending_writes = deepcopy(self.driver.pending_writes) self.driver.clear_transaction_writes() self.driver.clear_events() if not self.bypass_privates: assert not function_name.startswith(constants.PRIVATE_METHOD_PREFIX), 'Private method not callable.' if metering is None: metering = self.metering runtime.rt.env.update({'__Driver': self.driver}) if driver: runtime.rt.env.update({'__Driver': driver}) else: driver = runtime.rt.env.get('__Driver') install_database_loader(driver=driver) balances_key = None try: if metering: balances_key = (f'{self.currency_contract}' f'{constants.INDEX_SEPARATOR}' f'{self.balances_hash}' f'{constants.DELIMITER}' f'{sender}') if self.bypass_balance_amount: balance = 9999999 else: balance = driver.get(balances_key) if type(balance) == dict: balance = ContractingDecimal(balance.get('__fixed__')) if balance is None: balance = 0 assert balance * stamp_cost >= stamps, (f'Sender does not have enough stamps for the transaction. ' f'Balance at key {balances_key} is {balance}') runtime.rt.env.update(environment) status_code = 0 # Multiply stamps by 1000 because we divide by it later # runtime.rt.set_up(stmps=stamps * 1000, meter=metering) runtime.rt.context._base_state = { 'signer': sender, 'caller': sender, 'this': contract_name, 'entry': (contract_name, function_name), 'owner': driver.get_owner(contract_name), 'submission_name': None } if runtime.rt.context.owner is not None and runtime.rt.context.owner != runtime.rt.context.caller: raise Exception(f'Caller {runtime.rt.context.caller} is not the owner {runtime.rt.context.owner}!') decimal.setcontext(CONTEXT) module = importlib.import_module(contract_name) func = getattr(module, function_name) # Add the contract name to the context on a submission call if contract_name == constants.SUBMISSION_CONTRACT_NAME: runtime.rt.context._base_state['submission_name'] = kwargs.get('name') for k, v in kwargs.items(): if type(v) == float: kwargs[k] = ContractingDecimal(str(v)) enable_restricted_imports() runtime.rt.set_up(stmps=stamps * 1000, meter=metering) result = func(**kwargs) transaction_writes = deepcopy(driver.transaction_writes) events = deepcopy(driver.log_events) runtime.rt.tracer.stop() disable_restricted_imports() if auto_commit: driver.commit() except Exception as e: result = e status_code = 1 # Revert the writes if the transaction fails driver.pending_writes = current_driver_pending_writes transaction_writes = {} events = [] if auto_commit: driver.flush_cache() finally: driver.clear_events() driver.clear_transaction_writes() runtime.rt.tracer.stop() #runtime.rt.tracer.stop() # Deduct the stamps if that is enabled stamps_used = runtime.rt.tracer.get_stamp_used() stamps_used = stamps_used // 1000 stamps_used += 5 if stamps_used > stamps: stamps_used = stamps if metering: assert balances_key is not None, 'Balance key was not set properly. Cannot deduct stamps.' to_deduct = stamps_used to_deduct /= stamp_cost to_deduct = ContractingDecimal(to_deduct) balance = driver.get(balances_key) if balance is None: balance = 0 balance = max(balance - to_deduct, 0) driver.set(balances_key, balance) transaction_writes[balances_key] = balance if auto_commit: driver.commit() Seeded.s = False runtime.rt.clean_up() runtime.rt.env.update({'__Driver': driver}) output = { 'status_code': status_code, 'result': result, 'stamps_used': stamps_used, 'writes': transaction_writes, 'reads': driver.pending_reads, 'events': events } disable_restricted_imports() return output ================================================ FILE: src/contracting/execution/module.py ================================================ from importlib.abc import Loader from importlib import invalidate_caches, __import__ from importlib.machinery import ModuleSpec from contracting.storage.driver import Driver from contracting.stdlib import env from contracting.execution.runtime import rt import marshal import builtins import sys import importlib.util # This function overrides the __import__ function, which is the builtin function that is called whenever Python runs # an 'import' statement. If the globals dictionary contains {'__contract__': True}, then this function will make sure # that the module being imported comes from the database and not from builtins or site packages. # # For all exec statements, we add the {'__contract__': True} _key to the globals to protect against unwanted imports. # # Note: anything installed with pip or in site-packages will also not work, so contract package names *must* be unique. def is_valid_import(name): spec = importlib.util.find_spec(name) if not isinstance(spec.loader, DatabaseLoader): raise ImportError("module {} cannot be imported in a smart contract.".format(name)) def restricted_import(name, globals=None, locals=None, fromlist=(), level=0): if globals is not None and globals.get('__contract__') is True: spec = importlib.util.find_spec(name) if spec is None or not isinstance(spec.loader, DatabaseLoader): raise ImportError("module {} cannot be imported in a smart contract.".format(name)) return __import__(name, globals, locals, fromlist, level) def enable_restricted_imports(): builtins.__import__ = restricted_import # builtins.float = ContractingDecimal def disable_restricted_imports(): builtins.__import__ = __import__ def uninstall_builtins(): sys.meta_path.clear() sys.path_hooks.clear() sys.path.clear() sys.path_importer_cache.clear() invalidate_caches() def install_database_loader(driver=Driver()): DatabaseFinder.driver = driver if DatabaseFinder not in sys.meta_path: sys.meta_path.insert(0, DatabaseFinder) def uninstall_database_loader(): sys.meta_path = list(set(sys.meta_path)) if DatabaseFinder in sys.meta_path: sys.meta_path.remove(DatabaseFinder) def install_system_contracts(directory=''): pass ''' Is this where interaction with the database occurs with the interface of code strings, etc? IE: pushing a contract does sanity checks here? ''' class DatabaseFinder: driver = Driver() def find_spec(self, fullname, path=None, target=None): if DatabaseFinder.driver.get_contract(self) is None: return None return ModuleSpec(self, DatabaseLoader(DatabaseFinder.driver)) MODULE_CACHE = {} class DatabaseLoader(Loader): def __init__(self, d=Driver()): self.d = d def create_module(self, spec): return None def exec_module(self, module): # fetch the individual contract code = self.d.get_compiled(module.__name__) if code is None: raise ImportError("Module {} not found".format(module.__name__)) if type(code) != bytes: code = bytes.fromhex(code) code = marshal.loads(code) if code is None: raise ImportError("Module {} not found".format(module.__name__)) scope = env.gather() scope.update(rt.env) scope.update({'__contract__': True}) # execute the module with the std env and update the module to pass forward exec(code, scope) # Update the module's attributes with the new scope vars(module).update(scope) del vars(module)['__builtins__'] rt.loaded_modules.append(module.__name__) def module_repr(self, module): return ''.format(module.__name__) ================================================ FILE: src/contracting/execution/runtime.py ================================================ from contracting import constants from contracting.execution.tracer import Tracer import contracting import sys import os import math class Context: def __init__(self, base_state, maxlen=constants.RECURSION_LIMIT): self._state = [] self._depth = [] self._base_state = base_state self._maxlen = maxlen def _context_changed(self, contract): if self._get_state()['this'] == contract: return False return True def _get_state(self): if len(self._state) == 0: return self._base_state return self._state[-1] def _add_state(self, state: dict): if self._context_changed(state['this']) and len(self._state) < self._maxlen: self._state.append(state) self._depth.append(1) def _ins_state(self): if len(self._depth) > 0: self._depth[-1] += 1 def _pop_state(self): if len(self._state) > 0: #len(self._state) should equal len(self._depth) self._depth[-1] -= 1 if self._depth[-1] == 0: self._state.pop(-1) self._depth.pop(-1) def _reset(self): self._state = [] self._depth = [] @property def this(self): return self._get_state()['this'] @property def caller(self): return self._get_state()['caller'] @property def signer(self): return self._get_state()['signer'] @property def owner(self): return self._get_state()['owner'] @property def entry(self): return self._get_state()['entry'] @property def submission_name(self): return self._get_state()['submission_name'] _context = Context({ 'this': None, 'caller': None, 'owner': None, 'signer': None, 'entry': None, 'submission_name': None }) WRITE_MAX = 1024 * 128 class Runtime: cu_path = contracting.__path__[0] cu_path = os.path.join(cu_path, 'execution', 'metering', 'cu_costs.const') os.environ['CU_COST_FNAME'] = cu_path loaded_modules = [] env = {} stamps = 0 writes = 0 tracer = Tracer() signer = None context = _context @classmethod def set_up(cls, stmps, meter): if meter: cls.stamps = stmps cls.tracer.set_stamp(stmps) cls.tracer.start() cls.context._reset() @classmethod def clean_up(cls): cls.tracer.stop() cls.tracer.reset() cls.stamps = 0 cls.writes = 0 cls.signer = None for mod in cls.loaded_modules: if sys.modules.get(mod) is not None: del sys.modules[mod] cls.loaded_modules = [] cls.env = {} @classmethod def deduct_read(cls, key, value): if cls.tracer.is_started(): cost = len(key) + len(value) cost *= constants.READ_COST_PER_BYTE cls.tracer.add_cost(cost) @classmethod def deduct_write(cls, key, value): if key is not None and cls.tracer.is_started(): cost = len(key) + len(value) cls.writes += cost assert cls.writes < WRITE_MAX, 'You have exceeded the maximum write capacity per transaction!' stamp_cost = cost * constants.WRITE_COST_PER_BYTE cls.tracer.add_cost(stamp_cost) rt = Runtime() ================================================ FILE: src/contracting/execution/tracer.py ================================================ import sys import dis import threading import psutil import os # Define the opcode costs cu_costs = { 0: 2, 1: 2, 2: 4, 3: 4, 4: 4, 5: 4, 6: 4, 7: 4, 8: 4, 9: 2, 10: 2, 11: 4, 12: 2, 13: 4, 14: 4, 15: 4, 16: 4, 17: 4, 18: 4, 19: 4, 20: 2, 21: 4, 22: 8, 23: 6, 24: 6, 25: 4, 26: 4, 27: 4, 28: 4, 29: 4, 30: 4, 31: 6, 32: 6, 33: 6, 34: 2, 35: 6, 36: 6, 37: 6, 38: 2, 39: 4, 40: 4, 41: 4, 42: 4, 43: 4, 44: 2, 45: 2, 46: 2, 47: 4, 48: 2, 49: 6, 50: 6, 51: 6, 52: 6, 53: 4, 54: 6, 55: 4, 56: 4, 57: 4, 58: 4, 59: 4, 60: 4, 61: 4, 62: 4, 63: 4, 64: 6, 65: 6, 66: 8, 67: 8, 68: 8, 69: 12, 70: 2, 71: 1610, 72: 8, 73: 6, 74: 4, 75: 6, 76: 6, 77: 4, 78: 4, 79: 4, 80: 6, 81: 6, 82: 4, 83: 2, 84: 126, 85: 1000, 86: 4, 87: 8, 88: 6, 89: 4, 90: 2, 91: 2, 92: 2, 93: 512, 94: 8, 95: 6, 96: 6, 97: 4, 98: 4, 99: 2, 100: 2, 101: 2, 102: 2, 103: 6, 104: 8, 105: 8, 106: 4, 107: 4, 108: 38, 109: 126, 110: 4, 111: 4, 112: 4, 113: 6, 114: 4, 115: 4, 116: 4, 117: 4, 118: 6, 119: 6, 120: 4, 121: 4, 122: 4, 123: 6, 124: 32, 125: 2, 126: 2, 127: 4, 128: 4, 129: 4, 130: 6, 131: 10, 132: 8, 133: 12, 134: 4, 135: 4, 136: 8, 137: 2, 138: 2, 139: 2, 140: 4, 141: 6, 142: 12, 143: 6, 144: 2, 145: 8, 146: 8, 147: 6, 148: 2, 149: 6, 150: 6, 151: 6, 152: 6, 153: 4, 154: 4, 155: 4, 156: 6, 157: 4, 158: 4, 159: 4, 160: 4, 161: 2, 162: 4, 163: 6, 164: 6, 165: 6, 166: 6, 167: 2, 168: 4, 169: 4, 170: 2, 171: 8, 172: 2, 173: 4, 174: 4, 175: 4, 176: 4, 177: 4, 178: 4, 179: 4, 180: 4, 255: 8 } # Define maximum stamps MAX_STAMPS = 6500000 class Tracer: def __init__(self): self.cost = 0 self.stamp_supplied = 0 self.last_frame_mem_usage = 0 self.total_mem_usage = 0 self.started = False self.call_count = 0 self.max_call_count = 800000 self.instruction_cache = {} self.lock = threading.Lock() def start(self): sys.settrace(self.trace_func) self.cost = 0 self.call_count = 0 self.started = True def stop(self): if self.started: sys.settrace(None) self.started = False def reset(self): self.stop() self.cost = 0 self.stamp_supplied = 0 self.last_frame_mem_usage = 0 self.total_mem_usage = 0 self.call_count = 0 def set_stamp(self, stamp): self.stamp_supplied = stamp def add_cost(self, new_cost): self.cost += new_cost if self.cost > self.stamp_supplied or self.cost > MAX_STAMPS: self.stop() raise AssertionError("The cost has exceeded the stamp supplied!") def get_stamp_used(self): return self.cost def get_last_frame_mem_usage(self): return self.last_frame_mem_usage def get_total_mem_usage(self): return self.total_mem_usage def is_started(self): return self.started def get_memory_usage(self): process = psutil.Process(os.getpid()) mem_info = process.memory_info() # Return the RSS (Resident Set Size) return mem_info.rss def trace_func(self, frame, event, arg): if event == 'line': self.call_count += 1 if self.call_count > self.max_call_count: self.stop() raise AssertionError("Call count exceeded threshold! Infinite Loop?") # Check if the function matches the target module and function names code = frame.f_code current_function_name = code.co_name globals_dict = frame.f_globals module_name = globals_dict.get('__name__', '') # Only trace code within contracts (if '__contract__' in globals) if '__contract__' not in globals_dict: return # Get the opcode at the current instruction lasti = frame.f_lasti opcode = self.get_opcode(code, lasti) # Update memory usage if self.last_frame_mem_usage == 0: self.last_frame_mem_usage = self.get_memory_usage() new_memory_usage = self.get_memory_usage() if new_memory_usage > self.last_frame_mem_usage: self.total_mem_usage += (new_memory_usage - self.last_frame_mem_usage) self.last_frame_mem_usage = new_memory_usage # Check for memory usage limit (set an arbitrary limit, e.g., 500MB) if self.total_mem_usage > 500 * 1024 * 1024: self.stop() raise AssertionError(f"Transaction exceeded memory usage! Total usage: {self.total_mem_usage} bytes") # Add cost based on opcode opcode_cost = cu_costs.get(opcode, 1) # Default cost if opcode not found self.cost += opcode_cost if self.cost > self.stamp_supplied or self.cost > MAX_STAMPS: self.stop() raise AssertionError("The cost has exceeded the stamp supplied!") return self.trace_func def get_opcode(self, code, offset): # Cache the instruction map per code object with self.lock: instruction_map = self.instruction_cache.get(code) if instruction_map is None: instruction_map = {} for instr in dis.get_instructions(code): instruction_map[instr.offset] = instr.opcode self.instruction_cache[code] = instruction_map opcode = instruction_map.get(offset, None) if opcode is None: # Instruction not found; default to 0 opcode = 0 return opcode ================================================ FILE: src/contracting/stdlib/__init__.py ================================================ ================================================ FILE: src/contracting/stdlib/bridge/__init__.py ================================================ ================================================ FILE: src/contracting/stdlib/bridge/access.py ================================================ from contracting.execution.runtime import rt from contextlib import ContextDecorator from contracting.storage.driver import Driver from typing import Any class __export(ContextDecorator): def __init__(self, contract): self.contract = contract def __enter__(self, *args, **kwargs): driver = rt.env.get('__Driver') or Driver() if rt.context._context_changed(self.contract): current_state = rt.context._get_state() state = { 'owner': driver.get_owner(self.contract), 'caller': current_state['this'], 'signer': current_state['signer'], 'this': self.contract, 'entry': current_state['entry'], 'submission_name': current_state['submission_name'] } rt.context._add_state(state) if state['owner'] is not None and state['owner'] != state['caller']: raise Exception('Caller is not the owner!') else: rt.context._ins_state() def __exit__(self, *args, **kwargs): rt.context._pop_state() exports = { '__export': __export, 'ctx': rt.context, 'rt': rt, 'Any': Any } ================================================ FILE: src/contracting/stdlib/bridge/crypto.py ================================================ from types import ModuleType import nacl def verify(vk: str, msg: str, signature: str): vk = bytes.fromhex(vk) msg = msg.encode() signature = bytes.fromhex(signature) vk = nacl.signing.VerifyKey(vk) try: vk.verify(msg, signature) except: return False return True def key_is_valid(key: str): """ Check if the given address is valid. Can be used with public and private keys """ if not len(key) == 64: return False try: int(key, 16) except: return False return True crypto_module = ModuleType('crypto') crypto_module.verify = verify crypto_module.key_is_valid = key_is_valid exports = { 'crypto': crypto_module } ================================================ FILE: src/contracting/stdlib/bridge/decimal.py ================================================ from decimal import Decimal, Context, ROUND_FLOOR import decimal # Define precision constants MAX_UPPER_PRECISION = 30 MAX_LOWER_PRECISION = 30 # Set the decimal context for precision and rounding CONTEXT = Context( prec=MAX_UPPER_PRECISION + MAX_LOWER_PRECISION, rounding=ROUND_FLOOR, Emin=-100, Emax=100 ) decimal.setcontext(CONTEXT) # Create min and max decimal strings for precision boundaries def make_min_decimal_str(prec): return '0.' + '0' * (prec - 1) + '1' def make_max_decimal_str(prec): return '1' + '0' * (prec - 1) # Convert scientific notation to non-exponential format if needed def neg_sci_not(s: str): try: base, exp = s.split('e-') if float(base) > 9: return s base = base.replace('.', '') numbers = ('0' * (int(exp) - 1)) + base if int(exp) > 0: numbers = '0.' + numbers return numbers except ValueError: return s # Define maximum and minimum decimal constants MAX_DECIMAL = Decimal(make_max_decimal_str(MAX_UPPER_PRECISION)) MIN_DECIMAL = Decimal(make_min_decimal_str(MAX_LOWER_PRECISION)) # Ensure the value is within bounds and quantized def fix_precision(x: Decimal): if x > MAX_DECIMAL: return MAX_DECIMAL return x.quantize(MIN_DECIMAL, rounding=ROUND_FLOOR).normalize() # Main ContractingDecimal class class ContractingDecimal: def _get_other(self, other): if isinstance(other, ContractingDecimal): return other._d elif isinstance(other, (float, int)): return fix_precision(Decimal(neg_sci_not(str(other)))) return other def __init__(self, a): if isinstance(a, (float, int)): self._d = Decimal(neg_sci_not(str(a))) elif isinstance(a, str): self._d = Decimal(neg_sci_not(a)) elif isinstance(a, Decimal): self._d = a else: self._d = Decimal(a) # Clamp and quantize during initialization self._d = fix_precision(self._d) def __bool__(self): return self._d > 0 def __eq__(self, other): return self._d == self._get_other(other) def __lt__(self, other): return self._d < self._get_other(other) def __le__(self, other): return self._d <= self._get_other(other) def __gt__(self, other): return self._d > self._get_other(other) def __ge__(self, other): return self._d >= self._get_other(other) def __str__(self): return self._d.to_eng_string() def __repr__(self): return self._d.to_eng_string() def __neg__(self): return ContractingDecimal(-self._d) def __pos__(self): return self def __abs__(self): return ContractingDecimal(abs(self._d)) def __add__(self, other): return ContractingDecimal(fix_precision(self._d + self._get_other(other))) def __radd__(self, other): return ContractingDecimal(fix_precision(self._get_other(other) + self._d)) def __sub__(self, other): return ContractingDecimal(fix_precision(self._d - self._get_other(other))) def __rsub__(self, other): return ContractingDecimal(fix_precision(self._get_other(other) - self._d)) def __mul__(self, other): return ContractingDecimal(fix_precision(self._d * self._get_other(other))) def __rmul__(self, other): return ContractingDecimal(fix_precision(self._get_other(other) * self._d)) def __truediv__(self, other): return ContractingDecimal(fix_precision(self._d / self._get_other(other))) def __rtruediv__(self, other): return ContractingDecimal(fix_precision(self._get_other(other) / self._d)) def __mod__(self, other): return ContractingDecimal(fix_precision(self._d % self._get_other(other))) def __rmod__(self, other): return ContractingDecimal(fix_precision(self._get_other(other) % self._d)) def __floordiv__(self, other): return ContractingDecimal(fix_precision(self._d // self._get_other(other))) def __rfloordiv__(self, other): return ContractingDecimal(fix_precision(self._get_other(other) // self._d)) def __pow__(self, other): return ContractingDecimal(fix_precision(self._d ** self._get_other(other))) def __rpow__(self, other): return ContractingDecimal(fix_precision(self._get_other(other) ** self._d)) def __int__(self): return int(self._d) def __float__(self): return float(self._d) def __round__(self, n=None): return round(self._d, n) # Export ContractingDecimal for external use exports = { 'decimal': ContractingDecimal } ================================================ FILE: src/contracting/stdlib/bridge/hashing.py ================================================ import hashlib from types import ModuleType ''' Bytes can't be stored in JSON so we use hex-strings converted into bytes and back. ''' def sha3(hex_str: str): try: byte_str = bytes.fromhex(hex_str) except ValueError: byte_str = hex_str.encode() hasher = hashlib.sha3_256() hasher.update(byte_str) hashed_bytes = hasher.digest() return hashed_bytes.hex() def sha256(hex_str: str): try: byte_str = bytes.fromhex(hex_str) except ValueError: byte_str = hex_str.encode() hasher = hashlib.sha256() hasher.update(byte_str) hashed_bytes = hasher.digest() return hashed_bytes.hex() hashlib_module = ModuleType('hashlib') hashlib_module.sha3 = sha3 hashlib_module.sha256 = sha256 exports = { 'hashlib': hashlib_module, } ================================================ FILE: src/contracting/stdlib/bridge/imports.py ================================================ from types import FunctionType, ModuleType from contracting.constants import PRIVATE_METHOD_PREFIX from contracting.storage.orm import Datum from contracting.storage.driver import Driver, OWNER_KEY from contracting.execution.runtime import rt import importlib import sys def extract_closure(fn): closure = fn.__closure__[0] return closure.cell_contents class Func: def __init__(self, name, args=(), private=False): self.name = name if private: self.name = PRIVATE_METHOD_PREFIX + self.name self.args = args def is_of(self, f: FunctionType): if f.__closure__ is not None: f = extract_closure(f) num_args = f.__code__.co_argcount if f.__code__.co_name == self.name and f.__code__.co_varnames[:num_args] == self.args: return True return False class Var: def __init__(self, name, t): self.name = PRIVATE_METHOD_PREFIX + name assert issubclass(t, Datum), 'Cannot enforce a variable that is not a Variable, Hash, or Foreign type!' self.type = t def is_of(self, v): if isinstance(v, self.type): return True return False def import_module(name): assert not name.isdigit() and all(c.isalnum() or c == '_' for c in name), 'Invalid contract name!' assert name.islower(), 'Name must be lowercase!' _driver = rt.env.get('__Driver') or Driver() if name in set(list(sys.stdlib_module_names) + list(sys.builtin_module_names)): raise ImportError if name.startswith('_'): raise ImportError if _driver.get_contract(name) is None: raise ImportError return importlib.import_module(name, package=None) def enforce_interface(m: ModuleType, interface: list): implemented = vars(m) for i in interface: attribute = implemented.get(i.name) if attribute is None: return False # Branch for data types if isinstance(attribute, Datum): if not i.is_of(attribute): return False if isinstance(attribute, FunctionType): if not i.is_of(attribute): return False return True def owner_of(m: ModuleType): _driver = rt.env.get('__Driver') or Driver() owner = _driver.get_var(m.__name__, OWNER_KEY) return owner imports_module = ModuleType('importlib') imports_module.import_module = import_module imports_module.enforce_interface = enforce_interface imports_module.Func = Func imports_module.Var = Var imports_module.owner_of = owner_of exports = { 'importlib': imports_module, } ================================================ FILE: src/contracting/stdlib/bridge/orm.py ================================================ from contracting.storage.orm import Variable, Hash, ForeignVariable, ForeignHash, LogEvent from contracting.storage.contract import Contract from contracting.execution.runtime import rt class V(Variable): def __init__(self, *args, **kwargs): if rt.env.get('__Driver') is not None: kwargs['driver'] = rt.env.get('__Driver') super().__init__(*args, **kwargs) class H(Hash): def __init__(self, *args, **kwargs): if rt.env.get('__Driver') is not None: kwargs['driver'] = rt.env.get('__Driver') super().__init__(*args, **kwargs) class FV(ForeignVariable): def __init__(self, *args, **kwargs): if rt.env.get('__Driver') is not None: kwargs['driver'] = rt.env.get('__Driver') super().__init__(*args, **kwargs) class FH(ForeignHash): def __init__(self, *args, **kwargs): if rt.env.get('__Driver') is not None: kwargs['driver'] = rt.env.get('__Driver') super().__init__(*args, **kwargs) class C(Contract): def __init__(self, *args, **kwargs): if rt.env.get('__Driver') is not None: kwargs['driver'] = rt.env.get('__Driver') super().__init__(*args, **kwargs) class LE(LogEvent): def __init__(self, *args, **kwargs): if rt.env.get('__Driver') is not None: kwargs['driver'] = rt.env.get('__Driver') super().__init__(*args, **kwargs) # Define the locals that will be available for smart contracts at runtime exports = { 'Variable': V, 'Hash': H, 'ForeignVariable': FV, 'ForeignHash': FH, 'LogEvent': LE, '__Contract': C } ================================================ FILE: src/contracting/stdlib/bridge/random.py ================================================ """ This module wraps and exposes the Python stdlib random functions that can be made deterministic with a random seed and return fixed point precision where possible. This allows some psuedorandom behavior when it is nice to have, but with the caveat that it's based on environmental constants such as the last block hash and public information such as the sender's address to seed the random state so it's not *really* random. It's most likely 'random enough' for most cases, but people can always theoretically reproduce the seed and try to front-run a smart contract by testing the seeded randoms for a preferable outcome and submitting a transaction before the next block is minted. While this is extremely unlikely and hard to pull off, it's a valid hole in the security and needs to be accepted as a flaw when using random numbers on a reproducible transaction log such as a blockchain. """ import random from types import ModuleType from contracting.execution.runtime import rt class Seeded: s = False def seed(aux_salt=None): block_height = '0' if rt.env.get('block_num') is not None: block_height = str(rt.env.get('block_num')) block_hash = rt.env.get('block_hash') or '0' __input_hash = rt.env.get('__input_hash') or '0' # Auxiliary salt is used to create completely unique random seeds based on some other properties (optional) auxiliary_salt = '' if aux_salt is not None and rt.env.get(aux_salt): auxiliary_salt = str(rt.env.get(aux_salt)) else: if rt.env.get("AUXILIARY_SALT"): auxiliary_salt = str(rt.env.get("AUXILIARY_SALT")) s = block_height + block_hash + __input_hash + auxiliary_salt random.seed(s) Seeded.s = True def getrandbits(k): assert Seeded.s, 'Random state not seeded. Call seed().' b_str = '' for i in range(k): if random.random() > 0.5: b_str += '1' else: b_str += '0' return int(b_str, 2) def shuffle(l): assert Seeded.s, 'Random state not seeded. Call seed().' random.shuffle(l) def randrange(k): assert Seeded.s, 'Random state not seeded. Call seed().' return random.randrange(k) def randint(a, b): assert Seeded.s, 'Random state not seeded. Call seed().' return random.randint(a, b) def choice(l): assert Seeded.s, 'Random state not seeded. Call seed().' return random.choice(l) def choices(l, k): assert Seeded.s, 'Random state not seeded. Call seed().' return random.choices(l, k=k) # Construct module for exposure in the contract runtime random_module = ModuleType('random') random_module.seed = seed random_module.shuffle = shuffle random_module.getrandbits = getrandbits random_module.randrange = randrange random_module.randint = randint random_module.choice = choice random_module.choices = choices # Add it to the export object and it's good to go exports = { 'random': random_module } ================================================ FILE: src/contracting/stdlib/bridge/time.py ================================================ from datetime import datetime as dt from datetime import timedelta as td from types import ModuleType # Redefine a controlled datetime object that feels like a regular Python datetime object but is restricted so that we # can regulate the user interaction with it to prevent security attack vectors. It may seem redundant, but it guarantees # security. SECONDS_IN_MINUTE = 60 SECONDS_IN_HOUR = 3600 SECONDS_IN_DAY = 86400 SECONDS_IN_WEEK = 604800 def get_raw_seconds(weeks, days, hours, minutes, seconds): m_sec = minutes * SECONDS_IN_MINUTE h_sec = hours * SECONDS_IN_HOUR d_sec = days * SECONDS_IN_DAY w_sec = weeks * SECONDS_IN_WEEK raw_seconds = seconds + m_sec + h_sec + d_sec + w_sec return raw_seconds class Datetime: def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0): self._datetime = dt( year=year, month=month, day=day, hour=hour, minute=minute, second=second, microsecond=microsecond ) self.year = self._datetime.year self.month = self._datetime.month self.day = self._datetime.day self.hour = self._datetime.hour self.minute = self._datetime.minute self.second = self._datetime.second self.microsecond = self._datetime.microsecond def __lt__(self, other): if type(other) != Datetime: raise TypeError(f'{type(other)} is not a Datetime!') return self._datetime < other._datetime def __le__(self, other): if type(other) != Datetime: raise TypeError(f'{type(other)} is not a Datetime!') return self._datetime <= other._datetime def __eq__(self, other): if type(other) != Datetime: raise TypeError(f'{type(other)} is not a Datetime!') return self._datetime == other._datetime def __ge__(self, other): if type(other) != Datetime: raise TypeError(f'{type(other)} is not a Datetime!') return self._datetime >= other._datetime def __gt__(self, other): if type(other) != Datetime: raise TypeError(f'{type(other)} is not a Datetime!') return self._datetime > other._datetime def __ne__(self, other): if type(other) != Datetime: raise TypeError(f'{type(other)} is not a Datetime!') return self._datetime != other._datetime def __sub__(self, other): if isinstance(other, Datetime): delta = self._datetime - other._datetime return Timedelta(days=delta.days, seconds=delta.seconds) return NotImplemented def __add__(self, other): if isinstance(other, Timedelta): return Datetime._from_datetime(self._datetime + other._timedelta) return NotImplemented def __str__(self): return str(self._datetime) def __repr__(self): return self.__str__() @classmethod def _from_datetime(cls, d: dt): return cls(year=d.year, month=d.month, day=d.day, hour=d.hour, minute=d.minute, second=d.second, microsecond=d.microsecond) @classmethod def strptime(cls, date_string, format): d = dt.strptime(date_string, format) return cls._from_datetime(d) class Timedelta: def __init__(self, weeks=0, days=0, hours=0, minutes=0, seconds=0): self._timedelta = td( weeks=int(weeks), days=int(days), hours=int(hours), minutes=int(minutes), seconds=int(seconds) ) # For fast access to how many hours are in a timedelta. self.__raw_seconds = get_raw_seconds( weeks=int(weeks), days=int(days), hours=int(hours), minutes=int(minutes), seconds=int(seconds) ) def __lt__(self, other): if type(other) != Timedelta: raise TypeError(f'{type(other)} is not a Timedelta!') return self._timedelta < other._timedelta def __le__(self, other): if type(other) != Timedelta: raise TypeError(f'{type(other)} is not a Timedelta!') return self._timedelta <= other._timedelta def __eq__(self, other): if type(other) != Timedelta: raise TypeError(f'{type(other)} is not a Timedelta!') return self._timedelta == other._timedelta def __ge__(self, other): if type(other) != Timedelta: raise TypeError(f'{type(other)} is not a Timedelta!') return self._timedelta >= other._timedelta def __gt__(self, other): if type(other) != Timedelta: raise TypeError(f'{type(other)} is not a Timedelta!') return self._timedelta > other._timedelta def __ne__(self, other): if type(other) != Timedelta: raise TypeError(f'{type(other)} is not a Timedelta!') return self._timedelta != other._timedelta # Operator implementations inspired by CPython implementations def __add__(self, other): if isinstance(other, Timedelta): return Timedelta(days=self._timedelta.days + other._timedelta.days, seconds=self._timedelta.seconds + other._timedelta.seconds) if isinstance(other, Datetime): d = other._datetime + self._timedelta return Datetime._from_datetime(d) return NotImplemented def __sub__(self, other): if isinstance(other, Timedelta): return Timedelta(days=self._timedelta.days - other._timedelta.days, seconds=self._timedelta.seconds - other._timedelta.seconds,) if isinstance(other, Datetime): d = other._datetime - self._timedelta return Datetime._from_datetime(d) return NotImplemented def __mul__(self, other): if isinstance(other, Timedelta): return Timedelta(days=self._timedelta.days * other._timedelta.days, seconds=self._timedelta.seconds * other._timedelta.seconds) elif isinstance(other, int): return Timedelta(days=self._timedelta.days * other, seconds=self._timedelta.seconds * other) return NotImplemented def __str__(self): return str(self._timedelta) def __repr__(self): return self.__str__() # Accesses raw seconds and does a simple modulo to get the number of the component in the total seconds @property def seconds(self): return self.__raw_seconds @property def minutes(self): return self.__raw_seconds // SECONDS_IN_MINUTE @property def hours(self): return self.__raw_seconds // SECONDS_IN_HOUR @property def days(self): return self.__raw_seconds // SECONDS_IN_DAY @property def weeks(self): return self.__raw_seconds // SECONDS_IN_WEEK WEEKS = Timedelta(weeks=1) DAYS = Timedelta(days=1) HOURS = Timedelta(hours=1) MINUTES = Timedelta(minutes=1) SECONDS = Timedelta(seconds=1) datetime_module = ModuleType('datetime') datetime_module.datetime = Datetime datetime_module.timedelta = Timedelta datetime_module.WEEKS = WEEKS datetime_module.DAYS = DAYS datetime_module.HOURS = HOURS datetime_module.MINUTES = MINUTES datetime_module.SECONDS = SECONDS exports = { 'datetime': datetime_module } ================================================ FILE: src/contracting/stdlib/env.py ================================================ from contracting.stdlib.bridge.orm import exports as orm_exports from contracting.stdlib.bridge.hashing import exports as hash_exports from contracting.stdlib.bridge.time import exports as time_exports from contracting.stdlib.bridge.random import exports as random_exports from contracting.stdlib.bridge.imports import exports as imports_exports from contracting.stdlib.bridge.access import exports as access_exports from contracting.stdlib.bridge.decimal import exports as decimal_exports from contracting.stdlib.bridge.crypto import exports as crypto_exports # TODO create a module instead and return it inside of a dictionary like: # { # 'stdlib': module # } # # Then stdlib.datetime becomes available, etc def gather(): env = {} env.update(orm_exports) env.update(hash_exports) env.update(time_exports) env.update(random_exports) env.update(imports_exports) env.update(access_exports) env.update(decimal_exports) env.update(crypto_exports) return env ================================================ FILE: src/contracting/storage/__init__.py ================================================ ================================================ FILE: src/contracting/storage/contract.py ================================================ from contracting.compilation.compiler import ContractingCompiler from contracting.storage.driver import Driver from contracting.execution.runtime import rt from contracting.stdlib import env from contracting import constants _driver = rt.env.get('__Driver') or Driver() class Contract: def __init__(self, driver: Driver = _driver): self._driver = driver def submit(self, name, code, owner=None, constructor_args={}, developer=None): if self._driver.get_contract(name) is not None: raise Exception('Contract already exists.') c = ContractingCompiler(module_name=name) code_obj = c.parse_to_code(code, lint=True) scope = env.gather() scope.update({'__contract__': True}) scope.update(rt.env) exec(code_obj, scope) if scope.get(constants.INIT_FUNC_NAME) is not None: if constructor_args is None: constructor_args = {} scope[constants.INIT_FUNC_NAME](**constructor_args) now = scope.get('now') if now is not None: self._driver.set_contract( name=name, code=code_obj, owner=owner, overwrite=False, timestamp=now, developer=developer ) else: self._driver.set_contract( name=name, code=code_obj, owner=owner, overwrite=False, developer=developer ) ================================================ FILE: src/contracting/storage/driver.py ================================================ from contracting.storage.encoder import encode_kv from contracting.execution.runtime import rt from contracting.stdlib.bridge.time import Datetime from contracting.stdlib.bridge.decimal import ContractingDecimal from datetime import datetime from pathlib import Path from cachetools import TTLCache from contracting import constants from contracting.storage import hdf5 import marshal import decimal import os import shutil FILE_EXT = ".d" HASH_EXT = ".x" DELIMITER = "." HASH_DEPTH_DELIMITER = ":" CODE_KEY = "__code__" TYPE_KEY = "__type__" AUTHOR_KEY = "__author__" OWNER_KEY = "__owner__" TIME_KEY = "__submitted__" COMPILED_KEY = "__compiled__" DEVELOPER_KEY = "__developer__" class Driver: def __init__(self, bypass_cache=False, storage_home=constants.STORAGE_HOME): self.pending_deltas = {} self.pending_writes = {} self.pending_reads = {} self.transaction_writes = {} self.log_events = [] self.cache = TTLCache(maxsize=1000, ttl=6*3600) self.bypass_cache = bypass_cache self.contract_state = storage_home.joinpath("contract_state") self.run_state = storage_home.joinpath("run_state") self.__build_directories() def __build_directories(self): self.contract_state.mkdir(exist_ok=True, parents=True) self.run_state.mkdir(exist_ok=True, parents=True) def __parse_key(self, key): # Split the key into parts (filename, group, etc.) parts = key.split(constants.INDEX_SEPARATOR, 1) # Ensure key contains the INDEX_SEPARATOR # The first part should be the filename (e.g., "currency") filename = parts[0].split(constants.DELIMITER, 1)[0] # Get only 'currency' from 'currency.balances' # The rest (after the first '.') becomes the group and attribute inside the HDF5 file if len(parts) > 1: variable = parts[1].replace(constants.DELIMITER, constants.HDF5_GROUP_SEPARATOR) else: variable = parts[0].replace(constants.DELIMITER, constants.HDF5_GROUP_SEPARATOR) return filename, variable def __filename_to_path(self, filename): if filename.startswith("__"): return str(self.run_state.joinpath(filename)) else: return str(self.contract_state.joinpath(filename)) def __get_files(self): return sorted(os.listdir(self.contract_state) + os.listdir(self.run_state)) def is_file(self, filename): file_path = Path(self.__filename_to_path(filename)) return file_path.is_file() def get(self, key: str, save: bool = True): """ Get a value from the cache, pending reads, or disk. If save is True, the value will be saved to pending_reads. """ # Parse the key to get the filename and group value = self.find(key) if save and self.pending_reads.get(key) is None: self.pending_reads[key] = value # if value is not None: # rt.deduct_read(*encode_kv(key, value)) return value def set(self, key, value, is_txn_write=False): rt.deduct_write(*encode_kv(key, value)) if self.pending_reads.get(key) is None: self.get(key) if type(value) in [decimal.Decimal, float]: value = ContractingDecimal(str(value)) self.pending_writes[key] = value if is_txn_write: self.transaction_writes[key] = value def find(self, key: str): """ Find the value for a given key. If not found in cache or pending writes, it will look it up from the disk. """ if self.bypass_cache: # Parse the key to get the filename and group filename, variable = self.__parse_key(key) value = hdf5.get_value_from_disk(self.__filename_to_path(filename), variable) return value value = self.pending_writes.get(key) if value is None: value = self.cache.get(key) if value is None: # Parse the key to get the filename and group for disk lookup filename, variable = self.__parse_key(key) value = hdf5.get_value_from_disk(self.__filename_to_path(filename), variable) return value def __get_keys_from_file(self, filename): return hdf5.get_groups(self.__filename_to_path(filename)) def keys_from_disk(self, prefix=None, length=0): """ Get all keys from disk with a given prefix """ keys = set() try: for filename in self.__get_files(): for key in self.__get_keys_from_file(filename): if prefix and key.startswith(prefix): keys.add(key) elif not prefix: keys.add(key) if 0 < length <= len(keys): raise AssertionError("Length threshold has been hit. Continuing.") except AssertionError: pass keys = list(keys) keys.sort() return keys def iter_from_disk(self, prefix="", length=0): try: filename, _ = self.__parse_key(prefix) except Exception: return self.keys(prefix=prefix) if not self.is_file(filename=filename): return [] keys_from_file = self.__get_keys_from_file(filename) keys = [key for key in keys_from_file if key.startswith(prefix)] keys.sort() return keys if length == 0 else keys[:length] def value_from_disk(self, key): """ Retrieve a value from the disk based on the parsed key. """ # Parse the key to get the filename and group filename, variable = self.__parse_key(key) return hdf5.get_value_from_disk(self.__filename_to_path(filename), variable) def items(self, prefix=""): """ Get all existing items with a given prefix. """ _items = {} keys = set() # Collect pending writes with matching prefix for k, v in self.pending_writes.items(): if k.startswith(prefix): # Always mark the key as seen to suppress disk rehydration keys.add(k) if v is not None: _items[k] = v # Collect cache items with matching prefix for k, v in self.cache.items(): if k.startswith(prefix): keys.add(k) if v is not None: _items[k] = v # Collect keys from the disk db_keys = set(self.iter_from_disk(prefix=prefix)) # Subtract already collected keys and add missing ones from disk for k in db_keys - keys: _items[k] = self.get(k) # Cache get will add the keys to the cache return _items def keys(self, prefix=""): return list(self.items(prefix).keys()) def values(self, prefix=""): return list(self.items(prefix).values()) def make_key(self, contract, variable, args=[]): contract_variable = DELIMITER.join((contract, variable)) if args: return HASH_DEPTH_DELIMITER.join((contract_variable, *[str(arg) for arg in args])) return contract_variable def set_var(self, contract, variable, arguments=[], value=None, mark=True): """ Set a variable in a contract. """ # Construct the key and set the value key = self.make_key(contract, variable, arguments) self.set(key, value) def get_var(self, contract, variable, arguments=[], mark=True): """ Get a variable from a contract. """ # Construct the key and get the value key = self.make_key(contract, variable, arguments) return self.get(key) def get_owner(self, name): owner = self.get_var(name, OWNER_KEY) if owner == "": owner = None return owner def get_time_submitted(self, name): return self.get_var(name, TIME_KEY) def get_compiled(self, name): return self.get_var(name, COMPILED_KEY) def get_contract(self, name): return self.get_var(name, CODE_KEY) def set_contract( self, name, code, owner=None, overwrite=False, timestamp=Datetime._from_datetime(datetime.now()), developer=None, ): if self.get_contract(name) is None: code_obj = compile(code, "", "exec") code_blob = marshal.dumps(code_obj) self.set_var(name, CODE_KEY, value=code) self.set_var(name, COMPILED_KEY, value=code_blob) self.set_var(name, OWNER_KEY, value=owner) self.set_var(name, TIME_KEY, value=timestamp) self.set_var(name, DEVELOPER_KEY, value=developer) def delete_contract(self, name): """ Fully delete a contract from the caches and disk """ for key in self.keys(name): if self.cache.get(key) is not None: del self.cache[key] if self.pending_writes.get(key) is not None: del self.pending_writes[key] self.delete_key_from_disk(key) def get_contract_files(self): """ Get all contract files as a list of strings """ return sorted(os.listdir(self.contract_state)) def delete_key_from_disk(self, key): """ Delete a key from the disk by parsing the filename and group from the key. """ # Parse the key to get the filename and group filename, variable = self.__parse_key(key) if len(filename) < constants.FILENAME_LEN_MAX: hdf5.delete_key_from_disk(self.__filename_to_path(filename), variable) def flush_cache(self): self.pending_writes.clear() self.pending_reads.clear() self.pending_deltas.clear() self.transaction_writes.clear() self.log_events.clear() self.cache.clear() def flush_disk(self): shutil.rmtree(self.run_state, ignore_errors=True) shutil.rmtree(self.contract_state, ignore_errors=True) self.__build_directories() def flush_file(self, filename): file_path = self.__filename_to_path(filename) if os.path.isfile(file_path): os.unlink(file_path) def set_event(self, event): self.log_events.append(event) def flush_full(self): """ Flush all caches and disk storage. """ self.flush_disk() self.flush_cache() def delete(self, key): """ Delete a key fully from the caches and queue it for deletion from disk on commit. """ self.set(key, None) # Setting the value to None will mark it for deletion def rollback(self, nanos=None): """ Rollback to a given Nanoseconds in L2 cache or if no Nanoseconds is given, rollback to the latest state on disk. """ if nanos is None: # Resets to the latest state on disk self.cache.clear() self.pending_reads.clear() self.pending_writes.clear() self.pending_deltas.clear() else: to_delete = [] for _nanos, _deltas in sorted(self.pending_deltas.items(), reverse=True): if _nanos < nanos: break to_delete.append(_nanos) for key, delta in _deltas['writes'].items(): self.cache[key] = delta[0] # Restoring the value before the write for _nanos in to_delete: self.pending_deltas.pop(_nanos, None) def commit(self): """ Save the current state to disk and clear the L1 and L2 caches. """ for k, v in self.pending_writes.items(): # Parse the key before applying to HDF5 filename, variable = self.__parse_key(k) if v is None: hdf5.delete_key_from_disk(self.__filename_to_path(filename), variable) else: hdf5.set_value_to_disk(self.__filename_to_path(filename), variable, v, None) self.cache.clear() self.pending_writes.clear() self.pending_reads.clear() def hard_apply(self, nanos): """ Save the current state to disk and L1 cache and clear the L2 cache. """ deltas = {} for k, v in self.pending_writes.items(): current = self.pending_reads.get(k) deltas[k] = (current, v) self.cache[k] = v self.pending_deltas[nanos] = {"writes": deltas, "reads": self.pending_reads} # Clear the top cache self.pending_reads = {} self.pending_writes.clear() # Run through the sorted HCLs from oldest to newest applying each one to_delete = [] for _nanos, _deltas in sorted(self.pending_deltas.items()): # Run through all state changes, taking the second value, which is the post delta for key, delta in _deltas["writes"].items(): # Parse the key before applying to HDF5 filename, variable = self.__parse_key(key) hdf5.set_value_to_disk(self.__filename_to_path(filename), variable, delta[1], nanos) to_delete.append(_nanos) if _nanos == nanos: break # Remove the deltas from the set [self.pending_deltas.pop(key) for key in to_delete] def get_all_contract_state(self): """ Queries the disk storage and returns a dictionary with all the state from the contract storage directory. """ all_contract_state = {} for file_path in self.contract_state.iterdir(): filename = file_path.name keys = hdf5.get_all_keys_from_file(self.__filename_to_path(filename)) for key in keys: full_key = f"{filename}{DELIMITER}{key}" value = self.get(full_key) all_contract_state[full_key] = value return all_contract_state def get_run_state(self): """ Retrieves the latest state information from the run state directory. """ run_state = {} for file_path in self.run_state.iterdir(): filename = file_path.name keys = self.__get_keys_from_file(self.__filename_to_path(filename)) for key in keys: full_key = f"{filename}{DELIMITER}{key}" value = hdf5.get_value_from_disk(self.__filename_to_path(filename), key) run_state[full_key] = value return run_state def clear_transaction_writes(self): """ Clear the transaction-specific writes. """ self.transaction_writes.clear() def clear_events(self): self.log_events.clear() ================================================ FILE: src/contracting/storage/encoder.py ================================================ import json import decimal from contracting.stdlib.bridge.time import Datetime, Timedelta from contracting.stdlib.bridge.decimal import ContractingDecimal, MAX_LOWER_PRECISION, fix_precision from contracting.constants import INDEX_SEPARATOR, DELIMITER MONGO_MIN_INT = -(2 ** 63) MONGO_MAX_INT = 2 ** 63 - 1 ## # ENCODER CLASS # Add to this to encode Python types for storage. # Right now, this is only for datetime types. They are passed into the system as ISO strings, cast into Datetime objs # and stored as dicts. Is there a better way? I don't know, maybe. ## def safe_repr(obj, max_len=1024): try: r = obj.__repr__() rr = r.split(' at 0x') if len(rr) > 1: return rr[0] + '>' return rr[0][:max_len] except: return None class Encoder(json.JSONEncoder): def default(self, o, *args): if isinstance(o, Datetime) or o.__class__.__name__ == Datetime.__name__: return { '__time__': [o.year, o.month, o.day, o.hour, o.minute, o.second, o.microsecond] } elif isinstance(o, Timedelta) or o.__class__.__name__ == Timedelta.__name__: return { '__delta__': [o._timedelta.days, o._timedelta.seconds] } elif isinstance(o, bytes): return { '__bytes__': o.hex() } elif isinstance(o, decimal.Decimal) or o.__class__.__name__ == decimal.Decimal.__name__: #return format(o, f'.{MAX_LOWER_PRECISION}f') return { '__fixed__': str(fix_precision(o)) } elif isinstance(o, ContractingDecimal) or o.__class__.__name__ == ContractingDecimal.__name__: #return format(o._d, f'.{MAX_LOWER_PRECISION}f') return { '__fixed__': str(fix_precision(o._d)) } #else: # return safe_repr(o) return super().default(o) def encode_int(value: int): if MONGO_MIN_INT < value < MONGO_MAX_INT: return value return { '__big_int__': str(value) } def encode_ints_in_dict(data: dict): d = dict() for k, v in data.items(): if isinstance(v, int): d[k] = encode_int(v) elif isinstance(v, dict): d[k] = encode_ints_in_dict(v) elif isinstance(v, list): d[k] = [] for i in v: if isinstance(i, dict): d[k].append(encode_ints_in_dict(i)) elif isinstance(i, int): d[k].append(encode_int(i)) else: d[k].append(i) else: d[k] = v return d # JSON library from Python 3 doesn't let you instantiate your custom Encoder. You have to pass it as an obj to json def encode(data: str): """ NOTE: Normally encoding behavior is overriden in 'default' method inside a class derived from json.JSONEncoder. Unfortunately this can be done only for custom types. Due to MongoDB integer limitation (8 bytes), we need to preprocess 'big' integers. """ if isinstance(data, int): data = encode_int(data) elif isinstance(data, dict): data = encode_ints_in_dict(data) return json.dumps(data, cls=Encoder, separators=(',', ':')) def as_object(d): if '__time__' in d: return Datetime(*d['__time__']) elif '__delta__' in d: return Timedelta(days=d['__delta__'][0], seconds=d['__delta__'][1]) elif '__bytes__' in d: return bytes.fromhex(d['__bytes__']) elif '__fixed__' in d: return ContractingDecimal(d['__fixed__']) elif '__big_int__' in d: return int(d['__big_int__']) return dict(d) # Decode has a hook for JSON objects, which are just Python dictionaries. You have to specify the logic in this hook. # This is not uniform, but this is how Python made it. def decode(data): if data is None: return None if isinstance(data, bytes): data = data.decode() try: return json.loads(data, object_hook=as_object) except json.decoder.JSONDecodeError as e: return None def make_key(contract, variable, args=[]): contract_variable = INDEX_SEPARATOR.join((contract, variable)) if args: return DELIMITER.join((contract_variable, *args)) return contract_variable def encode_kv(key, value): # if key is None: # key = '' # # if value is None: # value = '' k = key.encode() v = encode(value).encode() return k, v def decode_kv(key, value): k = key.decode() v = decode(value) # if v == '': # v = None return k, v TYPES = {'__fixed__', '__delta__', '__bytes__', '__time__', '__big_int__'} def convert(k, v): if k == '__fixed__': return ContractingDecimal(v) elif k == '__delta__': return Timedelta(days=v[0], seconds=v[1]) elif k == '__bytes__': return bytes.fromhex(v) elif k == '__time__': return Datetime(*v) elif k == '__big_int__': return int(v) return v def convert_dict(d): if not isinstance(d, dict): return d d2 = dict() for k, v in d.items(): if k in TYPES: return convert(k, v) elif isinstance(v, dict): d2[k] = convert_dict(v) elif isinstance(v, list): d2[k] = [] for i in v: d2[k].append(convert_dict(i)) else: d2[k] = v return d2 ================================================ FILE: src/contracting/storage/hdf5.py ================================================ import h5py from threading import Lock from collections import defaultdict from contracting.storage.encoder import encode, decode from contracting import constants # A dictionary to maintain file-specific locks file_locks = defaultdict(Lock) # Constants ATTR_LEN_MAX = 64000 ATTR_VALUE = "value" ATTR_BLOCK = "block" def get_file_lock(file_path): """Retrieve a lock for a specific file path.""" return file_locks[file_path] def get_value(file_path, group_name): return get_attr(file_path, group_name, ATTR_VALUE) def get_block(file_path, group_name): return get_attr(file_path, group_name, ATTR_BLOCK) def get_attr(file_path, group_name, attr_name): try: with h5py.File(file_path, 'r') as f: try: value = f[group_name].attrs[attr_name] return value.decode() if isinstance(value, bytes) else value except KeyError: return None except OSError: # File doesn't exist return None def get_groups(file_path): try: with h5py.File(file_path, 'r') as f: return list(f.keys()) except OSError: # File doesn't exist return [] def set(file_path, group_name, value, blocknum, timeout=20): """ Set the value and blocknum attributes in the HDF5 file for the given group. """ # Acquire a file lock to prevent concurrent writes lock = get_file_lock(file_path if isinstance(file_path, str) else file_path.filename) if lock.acquire(timeout=timeout): try: with h5py.File(file_path, 'a') as f: # Write value and blocknum to the group attributes write_attr(f, group_name, ATTR_VALUE, value, timeout) write_attr(f, group_name, ATTR_BLOCK, blocknum, timeout) finally: # Always release the lock after operation lock.release() else: raise TimeoutError("Lock acquisition timed out") def write_attr(file_or_path, group_name, attr_name, value, timeout=20): """ Write an attribute to a group inside an HDF5 file. """ # Open the file and ensure group exists, then write the attribute if isinstance(file_or_path, str): with h5py.File(file_or_path, 'a') as f: _write_attr_to_file(f, group_name, attr_name, value, timeout) else: _write_attr_to_file(file_or_path, group_name, attr_name, value, timeout) def _write_attr_to_file(file, group_name, attr_name, value, timeout): """ Internal method to write the attribute to the group. """ # Ensure the group exists, or create it if necessary grp = file.require_group(group_name) # Write or update the attribute in the group if attr_name in grp.attrs: del grp.attrs[attr_name] if value is not None: grp.attrs[attr_name] = value def delete(file_path, group_name, timeout=20): lock = get_file_lock(file_path if isinstance(file_path, str) else file_path.filename) if lock.acquire(timeout=timeout): try: with h5py.File(file_path, 'a') as f: try: del f[group_name].attrs[ATTR_VALUE] del f[group_name].attrs[ATTR_BLOCK] except KeyError: pass finally: lock.release() else: raise TimeoutError("Lock acquisition timed out") def set_value_to_disk(file_path, group_name, value, block_num=None, timeout=20): """ Save value to disk with optional block number. """ encoded_value = encode(value) if value is not None else None set(file_path, group_name, encoded_value, block_num if block_num is not None else -1, timeout) def delete_key_from_disk(file_path, group_name, timeout=20): delete(file_path, group_name, timeout) def get_value_from_disk(file_path, group_name): return decode(get_value(file_path, group_name)) def get_all_keys_from_file(file_path): """ Retrieve all keys (datasets and groups) from an HDF5 file and replace '/' with a specified character. :param file_path: Path to the HDF5 file. :param replace_char: Character to replace '/' with in the keys. :return: List of all keys in the HDF5 file with '/' replaced by replace_char. """ keys = [] def visit_func(name, node): keys.append(name.replace(constants.HDF5_GROUP_SEPARATOR, constants.DELIMITER)) with h5py.File(file_path, 'r') as f: f.visititems(visit_func) return keys ================================================ FILE: src/contracting/storage/orm.py ================================================ from contracting.storage.driver import Driver from contracting.execution.runtime import rt from contracting import constants from contracting.stdlib.bridge.decimal import ContractingDecimal from contracting.storage.encoder import encode_kv from copy import deepcopy driver = rt.env.get("__Driver") or Driver() class Datum: def __init__(self, contract, name, driver: Driver): self._driver = driver self._key = self._driver.make_key(contract, name) class Variable(Datum): def __init__(self, contract, name, driver: Driver = driver, t=None, default_value=None): self._type = None if isinstance(t, type): self._type = t self._default_value = default_value super().__init__(contract, name, driver=driver) def set(self, value): if self._type is not None and value is not None: assert isinstance(value, self._type), ( f'Wrong type passed to variable! ' f'Expected {self._type}, got {type(value)}.' ) self._driver.set(self._key, value, True) def get(self): value = self._driver.get(self._key) if value is None: dv = self._default_value if isinstance(dv, (list, dict)): return deepcopy(dv) return dv return value class Hash(Datum): def __init__(self, contract, name, driver: Driver = driver, default_value=None): super().__init__(contract, name, driver=driver) self._delimiter = constants.DELIMITER self._default_value = default_value def _set(self, key, value): self._driver.set(f"{self._key}{self._delimiter}{key}", value, True) def _get(self, item): value = self._driver.get(f"{self._key}{self._delimiter}{item}") # Add Python defaultdict behavior for easier smart contracting if value is None: value = self._default_value if type(value) == float or type(value) == ContractingDecimal: return ContractingDecimal(str(value)) # Return a defensive copy for mutable structures to prevent in-place # mutations from affecting cached objects in the driver. if isinstance(value, (list, dict)): return deepcopy(value) return value def _validate_key(self, key): if isinstance(key, tuple): assert len(key) <= constants.MAX_HASH_DIMENSIONS, ( f"Too many dimensions ({len(key)}) for hash. " f"Max is {constants.MAX_HASH_DIMENSIONS}" ) new_key_str = "" for k in key: assert not isinstance(k, slice), "Slices prohibited in hashes." k = str(k) assert constants.DELIMITER not in k, "Illegal delimiter in key." assert constants.INDEX_SEPARATOR not in k, "Illegal separator in key." new_key_str += f"{k}{self._delimiter}" key = new_key_str[: -len(self._delimiter)] else: key = str(key) assert constants.DELIMITER not in key, "Illegal delimiter in key." assert constants.INDEX_SEPARATOR not in key, "Illegal separator in key." assert ( len(key) <= constants.MAX_KEY_SIZE ), f"Key is too long ({len(key)}). Max is {constants.MAX_KEY_SIZE}." return key def _prefix_for_args(self, args): multi = self._validate_key(args) prefix = f"{self._key}{self._delimiter}" if multi != "": prefix += f"{multi}{self._delimiter}" return prefix def all(self, *args): prefix = self._prefix_for_args(args) return self._driver.values(prefix=prefix) def _items(self, *args): prefix = self._prefix_for_args(args) return self._driver.items(prefix=prefix) def clear(self, *args): kvs = self._items(*args) for k in kvs.keys(): self._driver.delete(k) def __setitem__(self, key, value): # handle multiple hashes differently key = self._validate_key(key) self._set(key, value) def __getitem__(self, key): key = self._validate_key(key) return self._get(key) def __contains__(self, key): raise Exception('Cannot use "in" with a Hash.') class ForeignVariable(Variable): def __init__( self, contract, name, foreign_contract, foreign_name, driver: Driver = driver ): super().__init__(contract, name, driver=driver) self._key = self._driver.make_key(foreign_contract, foreign_name) def set(self, value): raise ReferenceError class ForeignHash(Hash): def __init__( self, contract, name, foreign_contract, foreign_name, driver: Driver = driver ): super().__init__(contract, name, driver=driver) self._key = self._driver.make_key(foreign_contract, foreign_name) def _set(self, key, value): raise ReferenceError def __setitem__(self, key, value): raise ReferenceError def __getitem__(self, item): return super().__getitem__(item) def clear(self, *args): raise Exception("Cannot write with a ForeignHash.") class LogEvent(Datum): """ TODO - Break validation into smaller functions - Add checks for use of illegal types and argument names (See Hash checks.) """ def __init__(self, contract, name, event, params, driver: Driver = driver): self._driver = driver self._params = params self._event = event self._signer = rt.context.signer assert isinstance(params, dict), "Args must be a dictionary." assert len(params) > 0, "Args must have at least one argument." # Check for indexed arguments with a maximum of three indexed_args_count = sum(1 for arg in params.values() if arg.get("idx", False)) assert ( indexed_args_count <= 3 ), "Args must have at most three indexed arguments." for param in params.values(): if not isinstance(param["type"], tuple): param["type"] = (param["type"],) assert all( issubclass(t, (str, int, float, bool, ContractingDecimal)) for t in param["type"] ), "Each type in args must be str, int, float, decimal or bool." def write_event(self, event_data): contract = rt.context.this caller = rt.context.caller assert len(event_data) == len( self._params ), "Event Data must have the same number of arguments as specified in the event." # Check for unexpected arguments for arg in event_data: assert ( arg in self._params ), f"Unexpected argument {arg} in the data dictionary." # Check for missing and type-mismatched arguments for arg in self._params: assert ( arg in event_data ), f"Argument {arg} is missing from the data dictionary." # Check the type of the argument assert isinstance(event_data[arg], self._params[arg]["type"]), ( f"Argument {arg} is the wrong type! " f"Expected {self._params[arg]['type']}, got {type(event_data[arg])}." ) # Check the size of the argument value_size = len(str(event_data[arg]).encode("utf-8")) assert ( value_size <= 1024 ), f"Argument {arg} is too large ({value_size} bytes). Max is 1024 bytes." event = { "contract": contract, "event": self._event, "signer": self._signer, "caller": caller, "data_indexed": { arg: event_data[arg] for arg in self._params if self._params[arg].get("idx", False) }, "data": { arg: event_data[arg] for arg in self._params if not self._params[arg].get("idx", False) }, } for arg, value in event["data_indexed"].items(): assert isinstance( value, self._params[arg]["type"] ), f"Indexed argument {arg} is the wrong type! Expected {self._params[arg]['type']}, got {type(value)}." encoded = encode_kv(arg, value) rt.deduct_write(*encoded) for arg, value in event["data"].items(): assert isinstance( value, self._params[arg]["type"] ), f"Non-indexed argument {arg} is the wrong type! Expected {self._params[arg]['type']}, got {type(value)}." encoded = encode_kv(arg, value) rt.deduct_write(*encoded) self._driver.set_event(event) def __call__(self, data): self.write_event(data) ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/integration/__init__.py ================================================ ================================================ FILE: tests/integration/test_atomic_swap.py ================================================ from unittest import TestCase from contracting.storage.driver import Driver from contracting.execution.executor import Executor from contracting.stdlib.bridge.time import Datetime import contracting import os def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': f"con_{contract_name}", 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } class TestAtomicSwapContract(TestCase): def setUp(self): self.d = Driver() self.d.flush_full() with open(contracting.__path__[0] + '/contracts/submission.s.py') as f: contract = f.read() self.d.set_contract(name='submission', code=contract) self.d.commit() self.e = Executor(currency_contract='con_erc20_clone', metering=False) environment = {'now': Datetime(2019, 1, 1)} self.script_dir = os.path.dirname(os.path.abspath(__file__)) token_path = os.path.join(self.script_dir, "test_contracts", "erc20_clone.s.py") atomic_swaps_path = os.path.join(self.script_dir, "test_contracts", "atomic_swaps.s.py") self.e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(token_path), environment=environment) self.e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(atomic_swaps_path)) def tearDown(self): self.e.bypass_privates = False self.d.flush_full() def test_initiate_not_enough_approved(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) output = self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5000000 }) self.assertEqual(output['status_code'], 1) self.assertIn("You cannot initiate an atomic swap without allowing 'con_atomic_swaps' at least 5000000 coins. You have only allowed 1000000 coins", str(output['result'])) def test_initiate_transfers_coins_correctly(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) atomic_swaps = self.e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account':'con_atomic_swaps'}) stu = self.e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'stu'}) stu_as = self.e.execute('stu', 'con_erc20_clone', 'allowance', kwargs={'owner': 'stu', 'spender': 'con_atomic_swaps'}) self.assertEqual(atomic_swaps['result'], 5) self.assertEqual(stu['result'], 999995) self.assertEqual(stu_as['result'], 999995) def test_initiate_writes_to_correct_key_and_properly(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}, auto_commit=True) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }, auto_commit=True) key = 'con_atomic_swaps.swaps:raghu:eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514' expiration, amount = self.d.get(key) self.assertEqual(expiration, Datetime(2020, 1, 1)) self.assertEqual(amount, 5) def test_redeem_on_wrong_secret_fails(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) output = self.e.execute('raghu', 'con_atomic_swaps', 'redeem', kwargs={'secret': '00'}) self.assertEqual(output['status_code'], 1) self.assertIn('Incorrect sender or secret passed.', str(output['result'])) def test_redeem_on_wrong_sender_fails(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) output = self.e.execute('stu', 'con_atomic_swaps', 'redeem', kwargs={'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}) # status_code, result, stamps_used self.assertEqual(output['status_code'], 1) self.assertIn('Incorrect sender or secret passed.', str(output['result'])) def test_past_expiration_fails(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2021, 1, 1)} output = self.e.execute('raghu', 'con_atomic_swaps', 'redeem', kwargs={'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}, environment=environment) self.assertEqual(output['status_code'], 1) self.assertIn('Swap has expired.', str(output['result'])) def test_successful_redeem_transfers_coins_correctly(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2019, 1, 1)} self.e.execute('raghu', 'con_atomic_swaps', 'redeem', kwargs={'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}, environment=environment) atomic_swaps = self.e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'con_atomic_swaps'}) raghu = self.e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'raghu'}) self.assertEqual(raghu['result'], 5) self.assertEqual(atomic_swaps['result'], 0) def test_successful_redeem_deletes_entry(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2019, 1, 1)} self.e.execute('raghu', 'con_atomic_swaps', 'redeem', kwargs={'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}, environment=environment) key = 'atomic_swaps.swaps:raghu:eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514' v = self.d.get(key) self.assertEqual(v, None) def test_refund_works(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2021, 1, 1)} self.e.execute('stu', 'con_atomic_swaps', 'refund', kwargs={'participant': 'raghu', 'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}, environment=environment) atomic_swaps = self.e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'con_atomic_swaps'}) stu = self.e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'stu'}) self.assertEqual(stu['result'], 1000000) self.assertEqual(atomic_swaps['result'], 0) def test_refund_too_early_fails(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2019, 1, 1)} res = self.e.execute('stu', 'con_atomic_swaps', 'refund', kwargs={'participant': 'raghu', 'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}, environment=environment) self.assertIn('Swap has not expired.', str(res['result'])) def test_refund_participant_is_signer_fails(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2021, 1, 1)} res = self.e.execute('raghu', 'con_atomic_swaps', 'refund', kwargs={'participant': 'raghu', 'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}, environment=environment) self.assertIn('Caller and signer cannot issue a refund.', str(res['result'])) def test_refund_fails_with_wrong_secret(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2019, 1, 1)} res = self.e.execute('stu', 'con_atomic_swaps', 'refund', kwargs={'participant': 'raghu', 'secret': '00'}, environment=environment) self.assertIn('No swap to refund found.', str(res['result'])) def test_refund_resets_swaps(self): self.e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1000000, 'to': 'con_atomic_swaps'}) self.e.execute('stu', 'con_atomic_swaps', 'initiate', kwargs={ 'participant': 'raghu', 'expiration': Datetime(2020, 1, 1), 'hashlock': 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', 'amount': 5 }) environment = {'now': Datetime(2021, 1, 1)} self.e.execute('stu', 'con_atomic_swaps', 'refund', kwargs={'participant': 'raghu', 'secret': '842b65a7d48e3a3c3f0e9d37eaced0b2'}, environment=environment) key = 'atomic_swaps.swaps:raghu:eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514' v = self.d.get(key) self.assertEqual(v, None) def test_trying_to_call_private_function_fails(self): with self.assertRaises(AssertionError): self.e.execute('stu', 'con_atomic_swaps', '__test', kwargs={}) self.e.bypass_privates = True self.e.execute('stu', 'con_atomic_swaps', '__test', kwargs={}) ================================================ FILE: tests/integration/test_builtins_locked_off.py ================================================ from unittest import TestCase from contracting.client import ContractingClient import os class TestBuiltinsLockedOff(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') def tearDown(self): self.c.raw_driver.flush_full() def test_if_builtin_can_be_submitted(self): builtin_path = os.path.join(os.path.dirname(__file__), "test_contracts", "builtin_lib.s.py") with open(builtin_path) as f: contract = f.read() with self.assertRaises(Exception): self.c.submit(contract, name='con_builtin') def test_if_non_builtin_can_be_submitted(self): pass class TestMathBuiltinsLockedOff(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') def tearDown(self): self.c.raw_driver.flush_full() def test_if_builtin_can_be_submitted(self): mathtime_path = os.path.join(os.path.dirname(__file__), "test_contracts", "mathtime.s.py") with open(mathtime_path) as f: contract = f.read() with self.assertRaises(Exception): self.c.submit(contract, name='con_mathtime') class TestDatabaseLoaderLoadsFirst(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') def tearDown(self): self.c.raw_driver.flush_full() def test_if_builtin_can_be_submitted(self): contracting_path = os.path.join(os.path.dirname(__file__), "test_contracts", "contracting.s.py") with open(contracting_path) as f: contract = f.read() self.c.submit(contract, name='con_contracting') import_test_path = os.path.join(os.path.dirname(__file__), "test_contracts", "import_test.s.py") with open(import_test_path) as f: contract = f.read() with self.assertRaises(ImportError): self.c.submit(contract, name='con_import_test') class TestDynamicImport(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') def tearDown(self): self.c.raw_driver.flush_full() def test_if_builtin_can_be_submitted(self): dynamic_import_path = os.path.join(os.path.dirname(__file__), "test_contracts", "dynamic_import.s.py") with open(dynamic_import_path) as f: contract = f.read() self.c.submit(contract, name='con_dynamic_import') dynamic_import = self.c.get_contract('con_dynamic_import') with self.assertRaises(ImportError): dynamic_import.import_thing(name='con_math') class TestFloatIssue(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') def tearDown(self): self.c.raw_driver.flush_full() def test_if_builtin_can_be_submitted(self): float_issue_path = os.path.join(os.path.dirname(__file__), "test_contracts", "float_issue.s.py") with open(float_issue_path) as f: contract = f.read() self.c.submit(contract, name='con_float_issue') float_issue = self.c.get_contract('con_float_issue') float_issue.get(x=0.1, y=0.1) ================================================ FILE: tests/integration/test_complex_contracts.py ================================================ from unittest import TestCase from contracting.storage.driver import Driver from contracting.execution.executor import Executor from datetime import datetime from contracting.stdlib.env import gather from hashlib import sha256, sha3_256 import os def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': f'con_{contract_name}', 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } class TestComplexContracts(TestCase): def setUp(self): self.d = Driver() self.d.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: submission_contract = f.read() self.d.set_contract(name='submission', code=submission_contract) self.d.commit() def tearDown(self): self.d.flush_full() def test_token_construction_works(self): e = Executor(metering=False) currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(currency_path)) res = e.execute('stu', 'con_currency', 'balance', kwargs={'account': 'colin'}) self.assertEqual(res['result'], 100) res = e.execute('stu', 'con_currency', 'balance', kwargs={'account': 'stu'}) self.assertEqual(res['result'], 1000000) res = e.execute('stu', 'con_currency', 'balance', kwargs={'account': 'raghu'}) self.assertEqual(res['result'], None) def test_token_transfer_works(self): e = Executor(metering=False) currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(currency_path)) e.execute('stu', 'con_currency', 'transfer', kwargs={'amount': 1000, 'to': 'colin'}) stu_balance = e.execute('stu', 'con_currency', 'balance', kwargs={'account': 'stu'}) colin_balance = e.execute('stu', 'con_currency', 'balance', kwargs={'account': 'colin'}) self.assertEqual(stu_balance['result'], 1000000 - 1000) self.assertEqual(colin_balance['result'], 100 + 1000) def test_token_transfer_failure_not_enough_to_send(self): e = Executor(metering=False) currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(currency_path)) status = e.execute('stu', 'currency', 'transfer', kwargs={'amount': 1000001, 'to': 'colin'}) self.assertEqual(status['status_code'], 1) def test_token_transfer_to_new_account(self): e = Executor(metering=False) currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(currency_path)) e.execute('stu', 'con_currency', 'transfer', kwargs={'amount': 1000, 'to': 'raghu'}) raghu_balance = e.execute('stu', 'con_currency', 'balance', kwargs={'account': 'raghu'}) self.assertEqual(raghu_balance['result'], 1000) def test_erc20_clone_construction_works(self): e = Executor(metering=False) erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path)) stu = e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'stu'}) colin = e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'colin'}) supply = e.execute('stu', 'con_erc20_clone', 'total_supply', kwargs={}) self.assertEqual(stu['result'], 1000000) self.assertEqual(colin['result'], 100) self.assertEqual(supply['result'], 1000100) def test_erc20_clone_transfer_works(self): e = Executor(metering=False) erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path)) e.execute('stu', 'con_erc20_clone', 'transfer', kwargs={'amount': 1000000, 'to': 'raghu'}) raghu = e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'raghu'}) stu = e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'stu'}) self.assertEqual(raghu['result'], 1000000) self.assertEqual(stu['result'], 0) def test_erc20_clone_transfer_fails(self): e = Executor(metering=False) erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path)) output = e.execute('stu', 'con_erc20_clone', 'transfer', kwargs={'amount': 10000000, 'to': 'raghu'}) self.assertEqual(output['status_code'], 1) # breakpoint() self.assertEqual(str(output['result']), 'Not enough coins to send!') def test_allowance_of_blank(self): e = Executor(metering=False) erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path)) output = e.execute('stu', 'con_erc20_clone', 'allowance', kwargs={'owner': 'stu', 'spender': 'raghu'}) self.assertEqual(output['result'], 0) def test_approve_works_and_allowance_shows(self): e = Executor(metering=False) erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path)) e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1234, 'to': 'raghu'}) output = e.execute('stu', 'con_erc20_clone', 'allowance', kwargs={'owner': 'stu', 'spender': 'raghu'}) self.assertEqual(output['result'], 1234) def test_approve_and_transfer_from(self): e = Executor(metering=False) erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path)) e.execute('stu', 'con_erc20_clone', 'approve', kwargs={'amount': 1234, 'to': 'raghu'}) e.execute('raghu', 'con_erc20_clone', 'transfer_from', kwargs={'amount': 123, 'to': 'tejas', 'main_account': 'stu'}) raghu = e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'raghu'}) stu = e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'stu'}) tejas = e.execute('stu', 'con_erc20_clone', 'balance_of', kwargs={'account': 'tejas'}) self.assertEqual(raghu['result'], 0) self.assertEqual(stu['result'], (1000000 - 123)) self.assertEqual(tejas['result'], 123) def test_failure_after_data_writes_doesnt_commit(self): e = Executor(metering=False) leaky_path = os.path.join(os.path.dirname(__file__), "test_contracts", "leaky.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(leaky_path), auto_commit=True) e.execute('colin', 'con_leaky', 'transfer', kwargs={'amount': 1234, 'to': 'raghu'}, auto_commit=True) raghu = e.execute('stu', 'con_leaky', 'balance_of', kwargs={'account': 'raghu'}, auto_commit=True) colin = e.execute('stu', 'con_leaky', 'balance_of', kwargs={'account': 'colin'}, auto_commit=True) self.assertEqual(raghu['result'], 0) self.assertEqual(colin['result'], 100) def test_leaky_contract_commits_on_success(self): e = Executor(metering=False) leaky_path = os.path.join(os.path.dirname(__file__), "test_contracts", "leaky.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(leaky_path)) e.execute('colin', 'con_leaky', 'transfer', kwargs={'amount': 1, 'to': 'raghu'}) raghu = e.execute('stu', 'con_leaky', 'balance_of', kwargs={'account': 'raghu'}) colin = e.execute('stu', 'con_leaky', 'balance_of', kwargs={'account': 'colin'}) self.assertEqual(raghu['result'], 1) self.assertEqual(colin['result'], 99) def test_time_stdlib_works(self): e = Executor(metering=False) now = datetime.now() environment = gather() date = environment['datetime'].datetime(now.year, now.month, now.day) environment.update({'now': date}) time_path = os.path.join(os.path.dirname(__file__), "test_contracts", "time.s.py") res = e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(time_path), environment=environment) gt = e.execute('colin', 'con_time', 'gt', kwargs={}, environment=environment) self.assertTrue(gt['result']) lt = e.execute('colin', 'con_time', 'lt', kwargs={}, environment=environment) self.assertFalse(lt['result']) eq = e.execute('colin', 'con_time', 'eq', kwargs={}, environment=environment) self.assertFalse(eq['result']) def test_bad_time_contract_not_submittable(self): e = Executor(metering=False) now = datetime.now() environment = gather() date = environment['datetime'].datetime(now.year, now.month, now.day) environment.update({'now': date}) bad_time_path = os.path.join(os.path.dirname(__file__), "test_contracts", "bad_time.s.py") output = e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(bad_time_path), environment=environment) self.assertEqual(output['status_code'], 1) def test_json_lists_work(self): e = Executor(metering=False) json_tests_path = os.path.join(os.path.dirname(__file__), "test_contracts", "json_tests.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(json_tests_path)) res = e.execute('colin', 'con_json_tests', 'get_some', kwargs={}) self.assertListEqual([1, 2, 3, 4], res['result']) def test_time_storage_works(self): e = Executor(metering=False) environment = gather() time_storage_path = os.path.join(os.path.dirname(__file__), "test_contracts", "time_storage.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(time_storage_path)) v = e.execute('colin', 'con_time_storage', 'get', kwargs={}) date = environment['datetime'].datetime(2019, 1, 1) self.assertEqual(v['result'], date) def test_hash_sha3_works(self): e = Executor(metering=False) hashing_works_path = os.path.join(os.path.dirname(__file__), "test_contracts", "hashing_works.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(hashing_works_path)) secret = 'c0d1cc254c2aca8716c6ef170630550d' s3 = e.execute('colin', 'con_hashing_works', 't_sha3', kwargs={'s': secret}) h = sha3_256() h.update(bytes.fromhex(secret)) self.assertEqual(h.hexdigest(), s3['result']) def test_hash_sha256_works(self): e = Executor(metering=False) test_hashing_works_path = os.path.join(os.path.dirname(__file__), "test_contracts", "hashing_works.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_hashing_works_path)) secret = 'c0d1cc254c2aca8716c6ef170630550d' s3 = e.execute('colin', 'con_hashing_works', 't_sha256', kwargs={'s': secret}) h = sha256() h.update(bytes.fromhex(secret)) self.assertEqual(h.hexdigest(), s3['result']) ================================================ FILE: tests/integration/test_complex_object_setting.py ================================================ from contracting.client import ContractingClient from unittest import TestCase import os def contract(): storage = Hash() @export def create(x: int, y: int, color: str): storage[x, y] = { 'color': color, 'owner': ctx.caller } @export def update(x: int, y: int, color: str): s = storage[x, y] s['color'] = color storage[x, y] = s class TestComplexStorage(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.flush() self.c.submit(contract, name="con_contract") self.contract = self.c.get_contract('con_contract') def tearDown(self): self.c.flush() def test_storage(self): self.contract.create(x=1, y=2, color='howdy') self.assertEqual(self.contract.storage[1, 2]['color'], 'howdy') def test_modify(self): self.contract.create(x=1, y=2, color='howdy') self.contract.update(x=1, y=2, color='yoyoyo') ================================================ FILE: tests/integration/test_constructor_args.py ================================================ from unittest import TestCase from contracting.stdlib.bridge.time import Datetime from contracting.client import ContractingClient import os class TestSenecaClientReplacesExecutor(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract) self.c.raw_driver.commit() # submit erc20 clone constructor_args_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "constructor_args_contract.s.py") with open(constructor_args_contract_path) as f: self.code = f.read() def test_custom_args_works(self): self.c.submit(self.code, name='con_constructor_args_contract', constructor_args={'a': 123, 'b': 321}) contract = self.c.get_contract('con_constructor_args_contract') a, b = contract.get() self.assertEqual(a, 123) self.assertEqual(b, 321) def test_custom_args_overloading(self): with self.assertRaises(TypeError): self.c.submit(self.code, name='con_constructor_args_contract', constructor_args={'a': 123, 'x': 321}) def test_custom_args_not_enough_args(self): with self.assertRaises(TypeError): self.c.submit(self.code, name='con_constructor_args_contract', constructor_args={'a': 123}) ================================================ FILE: tests/integration/test_contracts/__init__.py ================================================ ================================================ FILE: tests/integration/test_contracts/atomic_swaps.s.py ================================================ import con_erc20_clone swaps = Hash() @export def initiate(participant: str, expiration: datetime.datetime, hashlock: str, amount: float): allowance = con_erc20_clone.allowance(ctx.caller, ctx.this) assert allowance >= amount, \ "You cannot initiate an atomic swap without allowing '{}' " \ "at least {} coins. You have only allowed {} coins".format(ctx.this, amount, allowance) swaps[participant, hashlock] = [expiration, amount] con_erc20_clone.transfer_from(amount, ctx.this, ctx.caller) @export def redeem(secret: str): hashlock = hashlib.sha256(secret) result = swaps[ctx.caller, hashlock] assert result is not None, 'Incorrect sender or secret passed.' expiration, amount = result assert expiration >= now, 'Swap has expired.' con_erc20_clone.transfer(amount, ctx.caller) swaps[ctx.caller, hashlock] = None # change this to respond to the del keyword? @export def refund(participant: str, secret: str): assert participant != ctx.caller and participant != ctx.signer, \ 'Caller and signer cannot issue a refund.' hashlock = hashlib.sha256(secret) result = swaps[participant, hashlock] assert result is not None, 'No swap to refund found.' expiration, amount = result assert expiration < now, 'Swap has not expired.' con_erc20_clone.transfer(amount, ctx.caller) swaps[participant, hashlock] = None # Should fail if called def test(): return 123 ================================================ FILE: tests/integration/test_contracts/bad_time.s.py ================================================ old_time = time.datetime(2019, 1, 1) @export def ha(): old_time._datetime = None return old_time ================================================ FILE: tests/integration/test_contracts/bastardcoin.s.py ================================================ balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 999 balances['colin'] = 555 @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount @export def balance_of(account: str): return balances[account] ================================================ FILE: tests/integration/test_contracts/builtin_lib.s.py ================================================ import token @export def hahaha(): print('I work, fool!') @export def return_token(): return vars(token) ================================================ FILE: tests/integration/test_contracts/child_test.s.py ================================================ @export def get_value(): return 'good' ================================================ FILE: tests/integration/test_contracts/client.py ================================================ ================================================ FILE: tests/integration/test_contracts/con_pass_hash.s.py ================================================ import con_pass_hash my_hash = Hash() @export def store(k: Any, v: Any): con_pass_hash.store_on_behalf(my_hash, k, v) @export def get(k: Any): return my_hash[k] ================================================ FILE: tests/integration/test_contracts/construct_function_works.s.py ================================================ v = Variable() @export def get(): return v.get() @construct def seed(): v.set(42) ================================================ FILE: tests/integration/test_contracts/constructor_args_contract.s.py ================================================ var1 = Variable() var2 = Variable() @construct def seed(a, b): var1.set(a) var2.set(b) @export def get(): a = var1.get() b = var2.get() return a, b ================================================ FILE: tests/integration/test_contracts/contracting.s.py ================================================ @export def hello(): return 'hello' ================================================ FILE: tests/integration/test_contracts/currency.s.py ================================================ balances = Hash() @construct def seed(): balances['stu'] = 1000000 balances['colin'] = 100 @export def transfer(amount: int, to: str): sender = ctx.signer assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount if balances[to] is None: balances[to] = amount else: balances[to] += amount @export def balance(account: str): return balances[account] ================================================ FILE: tests/integration/test_contracts/dater.py ================================================ #import datetime v = Variable() @export def replicate(d: datetime.datetime): assert d > now, 'D IS NOT LARGER THAN NOW' v.set(d) @export def subtract(d1: datetime.datetime, d2: datetime.datetime): return d1 - d2 ================================================ FILE: tests/integration/test_contracts/dynamic_import.py ================================================ @export def called_from_a_far(): m = importlib.import_module('con_all_in_one') res = m.call_me_again_again() return [res, { 'name': 'called_from_a_far', 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller, 'entry': ctx.entry, 'submission_name': ctx.submission_name }] ================================================ FILE: tests/integration/test_contracts/dynamic_import.s.py ================================================ @export def import_thing(name: str): return importlib.import_module(name) ================================================ FILE: tests/integration/test_contracts/dynamic_importing.s.py ================================================ @export def balance_for_token(tok: str, account: str): t = importlib.import_module(tok) return t.balance_of(account=account) @export def only_erc20(tok: str, account: str): t = importlib.import_module(tok) assert enforce_erc20(t), 'You cannot use a non-ERC20 standard token!!' return t.balance_of(account=account) @export def is_erc20_compatible(tok: str): interface = [ importlib.Func('transfer', args=('amount', 'to')), importlib.Func('balance_of', args=('account',)), importlib.Func('total_supply'), importlib.Func('allowance', args=('owner', 'spender')), importlib.Func('approve', args=('amount', 'to')), importlib.Func('transfer_from', args=('amount', 'to', 'main_account')), importlib.Var('supply', Variable), importlib.Var('balances', Hash) ] t = importlib.import_module(tok) return importlib.enforce_interface(t, interface) def enforce_erc20(m): interface = [ importlib.Func('transfer', args=('amount', 'to')), importlib.Func('balance_of', args=('account',)), importlib.Func('total_supply'), importlib.Func('allowance', args=('owner', 'spender')), importlib.Func('approve', args=('amount', 'to')), importlib.Func('transfer_from', args=('amount', 'to', 'main_account')), importlib.Var('supply', Variable), importlib.Var('balances', Hash) ] return importlib.enforce_interface(m, interface) ================================================ FILE: tests/integration/test_contracts/erc20_clone.s.py ================================================ supply = Variable() balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 1000000 balances['colin'] = 100 supply.set(balances['stu'] + balances['colin']) @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount @export def balance_of(account: str): return balances[account] @export def total_supply(): return supply.get() @export def allowance(owner: str, spender: str): return balances[owner, spender] @export def approve(amount: str, to: str): sender = ctx.caller balances[sender, to] += amount return balances[sender, to] @export def transfer_from(amount: int, to: str, main_account: str): sender = ctx.caller assert balances[main_account, sender] >= amount, 'Not enough coins approved to send! You have {} and are trying to spend {}'\ .format(balances[main_account, sender], amount) assert balances[main_account] >= amount, 'Not enough coins to send!' balances[main_account, sender] -= amount balances[main_account] -= amount balances[to] += amount ================================================ FILE: tests/integration/test_contracts/exception.py ================================================ balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 999 balances['colin'] = 555 @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount raise Exception('This is an exception') @export def balance_of(account: str): return balances[account] ================================================ FILE: tests/integration/test_contracts/float_issue.s.py ================================================ random.seed() gradients = Hash() def rand_vect(): theta = random.randint(0, 100) / 50 * 3.13 return {'x': 5.124 / theta, 'y': 7.124 / theta} def dot_prod_grid(x, y, vx, vy): d_vect = {'x': x - vx, 'y': y - vy} key = str(vx) + ',' + str(vy) if gradients[key]: g_vect = gradients[key] else: g_vect = rand_vect() gradients[key] = g_vect return (d_vect['y']) * (g_vect['x']) + (d_vect['y']) * (g_vect['y']) def smootherstep(x): return (6.0 * x ** 5.0 - 15.0 * x ** 4.0 + 10.0 * x ** 3.0) def interp(x, a, b): return a + (b - a) * smootherstep(x) @export def seed(): gradients['0'] = 1 # test @export def get(x: float, y: float): xf = int(x) yf = int(y) tl = dot_prod_grid(x, y, xf, yf) tr = dot_prod_grid(x, y, xf + 1, yf) bl = dot_prod_grid(x, y, xf, yf + 1) br = dot_prod_grid(x, y, xf + 1, yf + 1) xt = interp(x - xf, tl, tr) xb = interp(x - xf, bl, br) return interp(y - yf, xt, xb) ================================================ FILE: tests/integration/test_contracts/foreign_thing.s.py ================================================ thing_H = ForeignHash(foreign_contract='con_thing', foreign_name='H') thing_V = ForeignVariable(foreign_contract='con_thing', foreign_name='V') @export def read_H_hello(): return thing_H['hello'] @export def read_H_something(): return thing_H['something'] @export def read_V(): return thing_V.get() @export def set_H(k: str, v: Any): thing_H[k] = v @export def set_V(v: Any): thing_V.set(v) ================================================ FILE: tests/integration/test_contracts/hashing_works.s.py ================================================ @export def t_sha3(s: str): return hashlib.sha3(s) @export def t_sha256(s: str): return hashlib.sha256(s) ================================================ FILE: tests/integration/test_contracts/i_use_env.s.py ================================================ @export def env_var(): return this_is_a_passed_in_variable ================================================ FILE: tests/integration/test_contracts/import_test.s.py ================================================ import contracting @export def woo(): importlib.import_module('contracting') return contracting ================================================ FILE: tests/integration/test_contracts/import_this.s.py ================================================ @export def howdy(): return 12345 ================================================ FILE: tests/integration/test_contracts/importing_that.s.py ================================================ import con_import_this @export def test(): a = con_import_this.howdy() a -= 1000 return a ================================================ FILE: tests/integration/test_contracts/inf_loop.s.py ================================================ @construct def seed(): i = 0 while True: i += 1 @export def dummy(): return 0 ================================================ FILE: tests/integration/test_contracts/json_tests.s.py ================================================ v = Variable() @construct def seed(): v.set([1, 2, 3, 4, 5, 6, 7, 8]) @export def get_some(): return v.get()[0:4] ================================================ FILE: tests/integration/test_contracts/leaky.s.py ================================================ supply = Variable() balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 1000000 balances['colin'] = 100 supply.set(balances['stu'] + balances['colin']) @export def transfer(amount: int, to: str): sender = ctx.signer balances[sender] -= amount balances[to] += amount # putting the assert down here shouldn't matter to the execution and data environment assert balances[sender] >= amount, 'Not enough coins to send!' @export def balance_of(account: str): return balances[account] ================================================ FILE: tests/integration/test_contracts/mathtime.s.py ================================================ import math pi = Variable() @construct def seed(): pi.set(math.pi) @export def get_pi(): return pi.get() ================================================ FILE: tests/integration/test_contracts/modules/all_in_one.s.py ================================================ @export def call_me(): return call_me_again() @export def call_me_again(): return call_me_again_again() @export def call_me_again_again(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/dynamic_import.s.py ================================================ @export def called_from_a_far(): m = importlib.import_module('all_in_one') return m.call_me_again_again() @export def called_from_a_far_stacked(): m = importlib.import_module('all_in_one') return m.call() ================================================ FILE: tests/integration/test_contracts/modules/module1.s.py ================================================ ''' how the modules import each other. this is to test ctx.caller etc 1 | | 2 3 | | | | 4 5 6 7 | 8 ''' import module2 import module3 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/module2.s.py ================================================ import module4 import module5 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/module3.s.py ================================================ import module6 import module7 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/module4.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/module5.s.py ================================================ import module8 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/module6.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/module7.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/modules/module8.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/integration/test_contracts/orm_foreign_hash_contract.s.py ================================================ fh = ForeignHash(foreign_contract='con_orm_hash_contract', foreign_name='h') @export def set_fh(k: str, v: int): fh[k] = v @export def get_fh(k: str): return fh[k] ================================================ FILE: tests/integration/test_contracts/orm_foreign_key_contract.s.py ================================================ fv = ForeignVariable(foreign_contract='con_orm_variable_contract', foreign_name='v') @export def set_fv(i: int): fv.set(i) @export def get_fv(): return fv.get() ================================================ FILE: tests/integration/test_contracts/orm_hash_contract.s.py ================================================ h = Hash() @export def set_h(k: str, v: int): h[k] = v @export def get_h(k: str): return h[k] ================================================ FILE: tests/integration/test_contracts/orm_no_contract_access.s.py ================================================ c = __Contract() @export def set_c(): code = ''' @export def a(): print('gottem') ''' c.submit(name='baloney', code=code, author='sys') ================================================ FILE: tests/integration/test_contracts/orm_variable_contract.s.py ================================================ v = Variable() @export def set_v(i: int): v.set(i) @export def get_v(): return v.get() ================================================ FILE: tests/integration/test_contracts/owner_stuff.s.py ================================================ @export def get_owner(s: str): m = importlib.import_module(s) return importlib.owner_of(m) @export def owner_of_this(): return ctx.owner ================================================ FILE: tests/integration/test_contracts/parent_test.s.py ================================================ @export def get_val_from_child(s: str): m = importlib.import_module(s) return m.get_value() ================================================ FILE: tests/integration/test_contracts/pass_hash.s.py ================================================ @export def store_on_behalf(H: Any, k: Any, v: Any): H[k] = v ================================================ FILE: tests/integration/test_contracts/private_methods.s.py ================================================ test_hash = Hash() @export def call_private(): return private() def private(): return 'abc' @export def set(k: str, v: int): test_hash[k] = v @export def set_multi(k: str, k2: str, k3: str, v: int): test_hash[k, k2, k3] = v ================================================ FILE: tests/integration/test_contracts/stubucks.s.py ================================================ supply = Variable() balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 123 balances['colin'] = 321 supply.set(balances['stu'] + balances['colin']) @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount @export def balance_of(account: str): return balances[account] @export def total_supply(): return supply.get() @export def allowance(owner: str, spender: str): return balances[owner, spender] @export def approve(amount: int, to: str): sender = ctx.caller balances[sender, to] += amount return balances[sender, to] @export def transfer_from(amount: int, to: str, main_account: str): sender = ctx.caller assert balances[main_account, sender] >= amount, 'Not enough coins approved to send! You have {} and are trying to spend {}'\ .format(balances[main_account, sender], amount) assert balances[main_account] >= amount, 'Not enough coins to send!' balances[main_account, sender] -= amount balances[main_account] -= amount balances[to] += amount ================================================ FILE: tests/integration/test_contracts/submission.s.py ================================================ @__export('submission') def submit_contract(name: str, code: str, owner: Any=None, constructor_args: dict={}): if ctx.caller != 'sys': assert name.startswith('con_'), 'Contract must start with con_!' assert ctx.caller == ctx.signer, 'Contract cannot be called from another contract!' assert len(name) <= 64, 'Contract name length exceeds 64 characters!' assert name.islower(), 'Contract name must be lowercase!' __Contract().submit( name=name, code=code, owner=owner, constructor_args=constructor_args, developer=ctx.caller ) @__export('submission') def change_developer(contract: str, new_developer: str): d = __Contract()._driver.get_var(contract=contract, variable='__developer__') assert ctx.caller == d, 'Sender is not current developer!' __Contract()._driver.set_var( contract=contract, variable='__developer__', value=new_developer ) ================================================ FILE: tests/integration/test_contracts/tejastokens.s.py ================================================ supply = Variable() balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 321 balances['colin'] = 123 supply.set(balances['stu'] + balances['colin']) @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount @export def balance_of(account: str): return balances[account] @export def total_supply(): return supply.get() @export def allowance(owner: str, spender: str): return balances[owner, spender] @export def approve(amount: int, to: str): sender = ctx.caller balances[sender, to] += amount return balances[sender, to] @export def transfer_from(amount: int, to: str, main_account: str): sender = ctx.caller assert balances[main_account, sender] >= amount, 'Not enough coins approved to send! You have {} and are trying to spend {}'\ .format(balances[main_account, sender], amount) assert balances[main_account] >= amount, 'Not enough coins to send!' balances[main_account, sender] -= amount balances[main_account] -= amount balances[to] += amount ================================================ FILE: tests/integration/test_contracts/thing.s.py ================================================ H = Hash() V = Variable() @construct def seed(): H['hello'] = 'there' H['something'] = 'else' V.set('hi') @export def nop(): pass ================================================ FILE: tests/integration/test_contracts/time.s.py ================================================ old_time = datetime.datetime(2019, 1, 1) @export def gt(): return now > old_time @export def lt(): return now < old_time @export def eq(): return now == old_time ================================================ FILE: tests/integration/test_contracts/time_storage.s.py ================================================ time = Variable() @construct def seed(): time.set(datetime.datetime(2019, 1, 1)) @export def get(): return time.get() ================================================ FILE: tests/integration/test_datetime_contracts.py ================================================ from unittest import TestCase from contracting.client import ContractingClient from contracting.stdlib.bridge.time import Datetime import os class TestSenecaClientReplacesExecutor(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.flush() dater_path = os.path.join(os.path.dirname(__file__), "test_contracts", "dater.py") with open(dater_path) as f: self.c.submit(f=f.read(), name='con_dater') self.dater = self.c.get_contract('con_dater') def tearDown(self): self.c.flush() def test_datetime_passed_argument_and_now_are_correctly_compared(self): self.dater.replicate(d=Datetime(year=3000, month=1, day=1)) def test_datetime_passed_argument_and_now_are_correctly_compared_json(self): with self.assertRaises(TypeError): self.dater.replicate(d={'__time__':[3000, 12, 15, 12, 12, 12, 0]}) with self.assertRaises(TypeError): self.dater.replicate(d=[2025, 11, 15, 21, 47, 14, 0]) def test_datetime_subtracts(self): self.dater.subtract(d1=Datetime(year=2000, month=1, day=1), d2=Datetime(year=2001, month=1, day=1)) ================================================ FILE: tests/integration/test_dynamic_imports.py ================================================ from unittest import TestCase from contracting.stdlib.bridge.time import Datetime from contracting.client import ContractingClient import os class TestDynamicImports(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract) self.c.raw_driver.commit() # submit erc20 clone stubucks_path = os.path.join(os.path.dirname(__file__), "test_contracts", "stubucks.s.py") with open(stubucks_path) as f: code = f.read() self.c.submit(code, name='con_stubucks') tejastokens_path = os.path.join(os.path.dirname(__file__), "test_contracts", "tejastokens.s.py") with open(tejastokens_path) as f: code = f.read() self.c.submit(code, name='con_tejastokens') bastardcoin_path = os.path.join(os.path.dirname(__file__), "test_contracts", "bastardcoin.s.py") with open(bastardcoin_path) as f: code = f.read() self.c.submit(code, name='con_bastardcoin') dynamic_importing_path = os.path.join(os.path.dirname(__file__), "test_contracts", "dynamic_importing.s.py") with open(dynamic_importing_path) as f: code = f.read() self.c.submit(code, name='con_dynamic_importing') self.stubucks = self.c.get_contract('con_stubucks') self.tejastokens = self.c.get_contract('con_tejastokens') self.bastardcoin = self.c.get_contract('con_bastardcoin') self.dynamic_importing = self.c.get_contract('con_dynamic_importing') def tearDown(self): self.c.raw_driver.flush_full() def test_successful_submission(self): self.assertEqual(self.stubucks.balance_of(account='stu'), 123) self.assertEqual(self.stubucks.balance_of(account='colin'), 321) self.assertEqual(self.tejastokens.balance_of(account='stu'), 321) self.assertEqual(self.tejastokens.balance_of(account='colin'), 123) self.assertEqual(self.bastardcoin.balance_of(account='stu'), 999) self.assertEqual(self.bastardcoin.balance_of(account='colin'), 555) def test_get_stubuck_balances(self): stu = self.dynamic_importing.balance_for_token(tok='con_stubucks', account='stu') colin = self.dynamic_importing.balance_for_token(tok='con_stubucks', account='colin') self.assertEqual(stu, 123) self.assertEqual(colin, 321) def test_get_tejastokens_balances(self): stu = self.dynamic_importing.balance_for_token(tok='con_tejastokens', account='stu') colin = self.dynamic_importing.balance_for_token(tok='con_tejastokens', account='colin') self.assertEqual(stu, 321) self.assertEqual(colin, 123) def test_get_bastardcoin_balances(self): stu = self.dynamic_importing.balance_for_token(tok='con_bastardcoin', account='stu') colin = self.dynamic_importing.balance_for_token(tok='con_bastardcoin', account='colin') self.assertEqual(stu, 999) self.assertEqual(colin, 555) def test_is_erc20(self): self.assertTrue(self.dynamic_importing.is_erc20_compatible(tok='con_stubucks')) self.assertTrue(self.dynamic_importing.is_erc20_compatible(tok='con_tejastokens')) self.assertFalse(self.dynamic_importing.is_erc20_compatible(tok='con_bastardcoin')) def test_get_balances_erc20_enforced_stubucks(self): stu = self.dynamic_importing.only_erc20(tok='con_stubucks', account='stu') colin = self.dynamic_importing.only_erc20(tok='con_stubucks', account='colin') self.assertEqual(stu, 123) self.assertEqual(colin, 321) def test_get_balances_erc20_enforced_tejastokens(self): stu = self.dynamic_importing.only_erc20(tok='con_tejastokens', account='stu') colin = self.dynamic_importing.only_erc20(tok='con_tejastokens', account='colin') self.assertEqual(stu, 321) self.assertEqual(colin, 123) def test_erc20_enforced_fails_for_bastardcoin(self): with self.assertRaises(AssertionError): stu = self.dynamic_importing.only_erc20(tok='con_bastardcoin', account='stu') def test_owner_of_returns_default(self): owner_stuff_path = os.path.join(os.path.dirname(__file__), "test_contracts", "owner_stuff.s.py") with open(owner_stuff_path) as f: code = f.read() self.c.submit(code, name='con_owner_stuff', owner='poo') owner_stuff = self.c.get_contract('con_owner_stuff') self.assertIsNone(owner_stuff.get_owner(s='con_stubucks', signer='poo')) self.assertEqual(owner_stuff.get_owner(s='con_owner_stuff', signer='poo'), 'poo') def test_ctx_owner_works(self): owner_stuff_path = os.path.join(os.path.dirname(__file__), "test_contracts", "owner_stuff.s.py") with open(owner_stuff_path) as f: code = f.read() self.c.submit(code, name='con_owner_stuff', owner='poot') owner_stuff = self.c.get_contract('con_owner_stuff') self.assertEqual(owner_stuff.owner_of_this(signer='poot'), 'poot') def test_incorrect_owner_prevents_function_call(self): owner_stuff_path = os.path.join(os.path.dirname(__file__), "test_contracts", "owner_stuff.s.py") with open(owner_stuff_path) as f: code = f.read() self.c.submit(code, name='con_owner_stuff', owner='poot') owner_stuff = self.c.get_contract('owner_stuff') with self.assertRaises(Exception): owner_stuff.owner_of_this() def test_delegate_call_with_owner_works(self): parent_test_path = os.path.join(os.path.dirname(__file__), "test_contracts", "parent_test.s.py") with open(parent_test_path) as f: code = f.read() self.c.submit(code, name='con_parent_test') child_test_path = os.path.join(os.path.dirname(__file__), "test_contracts", "child_test.s.py") with open(child_test_path) as f: code = f.read() self.c.submit(code, name='con_child_test', owner='con_parent_test') parent_test = self.c.get_contract('con_parent_test') val = parent_test.get_val_from_child(s='con_child_test') self.assertEqual(val, 'good') def test_delegate_with_wrong_owner_does_not_work(self): parent_test_path = os.path.join(os.path.dirname(__file__), "test_contracts", "parent_test.s.py") with open(parent_test_path) as f: code = f.read() self.c.submit(code, name='con_parent_test') child_test_path = os.path.join(os.path.dirname(__file__), "test_contracts", "child_test.s.py") with open(child_test_path) as f: code = f.read() self.c.submit(code, name='con_child_test', owner='blorg') parent_test = self.c.get_contract('parent_test') with self.assertRaises(Exception) as e: parent_test.get_val_from_child(s='child_test') ================================================ FILE: tests/integration/test_executor_submission_process.py ================================================ from unittest import TestCase from contracting.storage.driver import Driver from contracting.execution.executor import Executor from contracting.compilation.compiler import ContractingCompiler import os def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': f'con_{contract_name}', 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } class TestExecutor(TestCase): def setUp(self): self.d = Driver() self.d.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.d.set_contract(name='submission', code=contract) self.d.commit() self.compiler = ContractingCompiler() def tearDown(self): self.d.flush_full() def test_submission(self): e = Executor(metering=False) code = '''@export def d(): a = 1 return 1 ''' kwargs = { 'name': 'con_stubucks', 'code': code } e.execute(**TEST_SUBMISSION_KWARGS, kwargs=kwargs, auto_commit=True) self.compiler.module_name = 'con_stubucks' new_code = self.compiler.parse_to_code(code) self.assertEqual(self.d.get_contract('con_stubucks'), new_code) def test_submission_then_function_call(self): e = Executor(metering=False) code = '''@export def d(): return 1 ''' kwargs = { 'name': 'con_stubuckz', 'code': code } e.execute(**TEST_SUBMISSION_KWARGS, kwargs=kwargs) output = e.execute(sender='stu', contract_name='con_stubuckz', function_name='d', kwargs={}) self.assertEqual(output['result'], 1) self.assertEqual(output['status_code'], 0) def test_kwarg_helper(self): test_orm_variable_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_variable_contract.s.py") k = submission_kwargs_for_file(test_orm_variable_contract_path) code = '''v = Variable() @export def set_v(i: int): v.set(i) @export def get_v(): return v.get() ''' self.assertEqual(k['name'], 'con_orm_variable_contract') self.assertEqual(k['code'], code) def test_orm_variable_sets_in_contract(self): e = Executor(metering=False) test_orm_variable_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_variable_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_variable_contract_path), auto_commit=True) e.execute('stu', 'con_orm_variable_contract', 'set_v', kwargs={'i': 1000}, auto_commit=True) i = self.d.get('con_orm_variable_contract.v') self.assertEqual(i, 1000) def test_orm_variable_gets_in_contract(self): e = Executor(metering=False) test_orm_variable_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_variable_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_variable_contract_path)) res = e.execute('stu', 'con_orm_variable_contract', 'get_v', kwargs={}) self.assertEqual(res['result'], None) def test_orm_variable_gets_and_sets_in_contract(self): e = Executor(metering=False) test_orm_variable_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_variable_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_variable_contract_path)) e.execute('stu', 'con_orm_variable_contract', 'set_v', kwargs={'i': 1000}) res = e.execute('stu', 'con_orm_variable_contract', 'get_v', kwargs={}) self.assertEqual(res['result'], 1000) def test_orm_hash_sets_in_contract(self): e = Executor(metering=False) test_orm_hash_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_hash_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_hash_contract_path), auto_commit=True) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'key1', 'v': 1234}, auto_commit=True) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'another_key', 'v': 9999}, auto_commit=True) key1 = self.d.get('con_orm_hash_contract.h:key1') another_key = self.d.get('con_orm_hash_contract.h:another_key') self.assertEqual(key1, 1234) self.assertEqual(another_key, 9999) def test_orm_hash_gets_in_contract(self): e = Executor(metering=False) test_orm_hash_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_hash_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_hash_contract_path)) res = e.execute('stu', 'con_orm_hash_contract', 'get_h', kwargs={'k': 'test'}) self.assertEqual(res['result'], None) def test_orm_hash_gets_and_sets_in_contract(self): e = Executor(metering=False) test_orm_hash_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_hash_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_hash_contract_path)) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'key1', 'v': 1234}) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'another_key', 'v': 9999}) key1 = e.execute('stu', 'con_orm_hash_contract', 'get_h', kwargs={'k': 'key1'}) another_key = e.execute('stu', 'con_orm_hash_contract', 'get_h', kwargs={'k': 'another_key'}) self.assertEqual(key1['result'], 1234) self.assertEqual(another_key['result'], 9999) def test_orm_foreign_variable_sets_in_contract_doesnt_work(self): e = Executor(metering=False) test_orm_variable_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_variable_contract.s.py") test_orm_foreign_key_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_foreign_key_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_variable_contract_path)) e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_foreign_key_contract_path)) e.execute('stu', 'con_orm_variable_contract', 'set_v', kwargs={'i': 1000}) # this should fail status = e.execute('stu', 'con_orm_foreign_key_contract', 'set_fv', kwargs={'i': 999}) self.assertEqual(status['status_code'], 1) i = e.execute('stu', 'con_orm_variable_contract', 'get_v', kwargs={}) self.assertEqual(i['result'], 1000) def test_orm_foreign_variable_gets_in_contract(self): e = Executor(metering=False) test_orm_variable_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_variable_contract.s.py") test_orm_foreign_key_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_foreign_key_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_variable_contract_path)) e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_foreign_key_contract_path)) e.execute('stu', 'con_orm_variable_contract', 'set_v', kwargs={'i': 424242}) # this should fail i = e.execute('stu', 'con_orm_foreign_key_contract', 'get_fv', kwargs={}) self.assertEqual(i['result'], 424242) def test_orm_foreign_hash_sets_in_contract_doesnt_work(self): e = Executor(metering=False) test_orm_hash_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_hash_contract.s.py") test_orm_foreign_hash_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_foreign_hash_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_hash_contract_path), auto_commit=True) e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_foreign_hash_contract_path), auto_commit=True) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'key1', 'v': 1234}, auto_commit=True) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'another_key', 'v': 9999}, auto_commit=True) status_1 = e.execute('stu', 'con_orm_foreign_hash_contract', 'set_fh', kwargs={'k': 'key1', 'v': 5555}, auto_commit=True) status_2 = e.execute('stu', 'con_orm_foreign_hash_contract', 'set_fh', kwargs={'k': 'another_key', 'v': 1000}, auto_commit=True) key1 = self.d.get('con_orm_hash_contract.h:key1') another_key = self.d.get('con_orm_hash_contract.h:another_key') self.assertEqual(key1, 1234) self.assertEqual(another_key, 9999) self.assertEqual(status_1['status_code'], 1) self.assertEqual(status_2['status_code'], 1) def test_orm_foreign_hash_gets_and_sets_in_contract(self): e = Executor(metering=False) test_orm_hash_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_hash_contract.s.py") test_orm_foreign_hash_contract_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_foreign_hash_contract.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_hash_contract_path)) e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_foreign_hash_contract_path)) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'key1', 'v': 1234}) e.execute('stu', 'con_orm_hash_contract', 'set_h', kwargs={'k': 'another_key', 'v': 9999}) key1 = e.execute('stu', 'con_orm_foreign_hash_contract', 'get_fh', kwargs={'k': 'key1'}) another_key = e.execute('stu', 'con_orm_foreign_hash_contract', 'get_fh', kwargs={'k': 'another_key'}) self.assertEqual(key1['result'], 1234) self.assertEqual(another_key['result'], 9999) def test_orm_contract_not_accessible(self): e = Executor(metering=False) test_orm_no_contract_access_path = os.path.join(os.path.dirname(__file__), "test_contracts", "orm_no_contract_access.s.py") output = e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_orm_no_contract_access_path)) self.assertEqual(str(output['result']) , '["Line 1 : S2- Illicit use of \'_\' before variable : __Contract"]') def test_construct_function_sets_properly(self): e = Executor(metering=False) test_construct_function_works_path = os.path.join(os.path.dirname(__file__), "test_contracts", "construct_function_works.s.py") r = e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(test_construct_function_works_path)) output = e.execute('stu', 'con_construct_function_works', 'get', kwargs={}) self.assertEqual(output['result'], 42) def test_import_exported_function_works(self): e = Executor(metering=False) import_this_path = os.path.join(os.path.dirname(__file__), "test_contracts", "import_this.s.py") importing_that_path = os.path.join(os.path.dirname(__file__), "test_contracts", "importing_that.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(import_this_path)) e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(importing_that_path)) output = e.execute('stu', 'con_importing_that', 'test', kwargs={}) self.assertEqual(output['result'], 12345 - 1000) def test_arbitrary_environment_passing_works_via_executor(self): e = Executor(metering=False) i_use_env_path = os.path.join(os.path.dirname(__file__), "test_contracts", "i_use_env.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(i_use_env_path)) this_is_a_passed_in_variable = 555 env = {'this_is_a_passed_in_variable': this_is_a_passed_in_variable} output = e.execute('stu', 'con_i_use_env', 'env_var', kwargs={}, environment=env) self.assertEqual(output['result'], this_is_a_passed_in_variable) def test_arbitrary_environment_passing_fails_if_not_passed_correctly(self): e = Executor(metering=False) i_use_env_path = os.path.join(os.path.dirname(__file__), "test_contracts", "i_use_env.s.py") e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(i_use_env_path)) this_is_a_passed_in_variable = 555 env = {'this_is_another_passed_in_variable': this_is_a_passed_in_variable} output = e.execute('stu', 'i_use_env', 'env_var', kwargs={}, environment=env) self.assertEqual(output['status_code'], 1) ================================================ FILE: tests/integration/test_executor_transaction_writes.py ================================================ import importlib from unittest import TestCase from contracting.stdlib.bridge.time import Datetime from contracting.client import ContractingClient from contracting.storage.driver import Driver import os class TestTransactionWrites(TestCase): def setUp(self): self.c = ContractingClient() self.c.flush() currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") with open(currency_path) as f: contract = f.read() self.c.submit(contract, name="currency") self.c.executor.driver.commit() def tearDown(self): self.c.raw_driver.flush_full() def test_transfers(self): self.c.set_var( contract="currency", variable="balances", arguments=["bill"], value=200 ) res3 = self.c.executor.execute( contract_name="currency", function_name="transfer", kwargs={"to": "someone", "amount": 100}, stamps=1000, sender="bill", ) self.assertEquals(res3["writes"], self.c.executor.driver.pending_writes) res2 = self.c.executor.execute( contract_name="currency", function_name="transfer", kwargs={"to": "someone", "amount": 100}, stamps=1000, sender="bill", ) self.assertEquals(res2["writes"], self.c.executor.driver.pending_writes) # This operation will raise an exception, so will not make any writes. res3 = self.c.executor.execute( contract_name="currency", function_name="transfer", kwargs={"to": "someone", "amount": 100}, stamps=1000, sender="bill", ) self.assertEquals(res3["writes"], {}) if __name__ == "__main__": import unittest unittest.main() ================================================ FILE: tests/integration/test_memory_clean_up_after_execution.py ================================================ from unittest import TestCase from contracting.storage.driver import Driver from contracting.execution.executor import Executor import os import contracting import psutil import gc def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': f'con_{contract_name}', 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } class TestMetering(TestCase): def setUp(self): # Hard load the submission contract self.d = Driver() self.d.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.d.set_contract(name='submission', code=contract) self.d.commit() currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") # Execute the currency contract with metering disabled self.e = Executor(driver=self.d) self.e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(currency_path), metering=False, auto_commit=True) def tearDown(self): self.d.flush_full() # def test_memory_clean_up_after_execution(self): # process = psutil.Process(os.getpid()) # before = process.memory_info().rss / 1024 / 1024 # for i in range(500): # output = self.e.execute('stu', 'con_currency', 'transfer', kwargs={'amount': 100, 'to': 'colin'}, auto_commit=True,metering=True) # gc.collect() # after = process.memory_info().rss / 1024 / 1024 # before_2 = process.memory_info().rss / 1024 / 1024 # for i in range(500): # output = self.e.execute('stu', 'con_currency', 'transfer', kwargs={'amount': 100, 'to': 'colin'}, auto_commit=True,metering=False) # gc.collect() # after_2 = process.memory_info().rss / 1024 / 1024 # print(f'RAM Difference with metering: {after - before} MB') # print(f'RAM Difference without metering: {after_2 - before_2} MB') if __name__ == '__main__': t = TestMetering() t.setUp() t.test_memory_clean_up_after_execution() t.tearDown() ================================================ FILE: tests/integration/test_misc_contracts.py ================================================ import importlib from unittest import TestCase from contracting.stdlib.bridge.time import Datetime from contracting.client import ContractingClient from contracting.storage.driver import Driver import os def too_many_writes(): v = Variable() @export def single(): v.set('a' * (128 * 1024 + 1)) @export def multiple(): for i in range(32 * 1024 + 1): v.set('a') @export def not_enough(): v.set('a' * (30 * 1024)) @export def run(): a = "" for i in range(1000000): a += "NAME" * 10 return a @export def run2(): a = 0 b = "" for i in range(1000000): b = b + "wow" + "baseName" * a a += 1 return b def exploit(): @construct def seed(): a = 0 b = "" for i in range(10000000): b = b + "wow" + "baseName" * a a += 1 return b @export def b(): pass class TestMiscContracts(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract,) self.c.raw_driver.commit() submission = self.c.get_contract('submission') self.c.submit(too_many_writes, name="con_too_many_writes") # submit erc20 clone thing_path = os.path.join(os.path.dirname(__file__), "test_contracts", "thing.s.py") with open(thing_path) as f: code = f.read() self.c.submit(code, name='con_thing') foreign_thing_path = os.path.join(os.path.dirname(__file__), "test_contracts", "foreign_thing.s.py") with open(foreign_thing_path) as f: code = f.read() self.c.submit(code, name='con_foreign_thing') self.thing = self.c.get_contract('con_thing') self.foreign_thing = self.c.get_contract('con_foreign_thing') def tearDown(self): self.c.raw_driver.flush_full() def test_H_values_return(self): output = self.foreign_thing.read_H_hello() self.assertEqual(output, 'there') output = self.foreign_thing.read_H_something() self.assertEqual(output, 'else') def test_cant_modify_H(self): with self.assertRaises(ReferenceError): self.foreign_thing.set_H(k='hello', v='not_there') def test_cant_add_H(self): with self.assertRaises(ReferenceError): self.foreign_thing.set_H(k='asdf', v='123') def test_cant_set_V(self): with self.assertRaises(ReferenceError): self.foreign_thing.set_V(v=123) def test_V_returns(self): output = self.foreign_thing.read_V() self.assertEqual(output, 'hi') def test_single_too_many_writes_fails(self): tmwc = self.c.get_contract('con_too_many_writes') self.c.executor.metering = True self.c.set_var(contract='currency', variable='balances', arguments=['stu'], value=1000000) self.assertEqual(self.c.executor.execute(contract_name="con_too_many_writes", function_name="single", kwargs={}, stamps=1000, sender='stu')["status_code"], 1) self.c.executor.metering = False def test_multiple_too_many_writes_fails(self): tmwc = self.c.get_contract('con_too_many_writes') self.c.executor.metering = True self.c.set_var(contract='currency', variable='balances', arguments=['stu'], value=1000000) # AssertEquals that the status code is 1 (failed tx) self.assertEqual(self.c.executor.execute(contract_name="con_too_many_writes", function_name="multiple", kwargs={}, stamps=1000, sender='stu')["status_code"], 1) self.c.executor.metering = False def test_failed_once_doesnt_affect_others(self): tmwc = self.c.get_contract('con_too_many_writes') self.c.executor.metering = True self.c.set_var(contract='currency', variable='balances', arguments=['stu'], value=1000000) # AssertEquals that the status code is 1 (failed tx) self.assertEqual(self.c.executor.execute(contract_name="con_too_many_writes", function_name="multiple", kwargs={}, stamps=1000, sender='stu')["status_code"], 1) self.c.executor.execute(contract_name="con_too_many_writes", function_name="not_enough", kwargs={}, stamps=1000, sender='stu') self.c.executor.metering = False def test_memory_overload(self): tmwc = self.c.get_contract('con_too_many_writes') self.c.executor.metering = True self.c.set_var(contract='currency', variable='balances', arguments=['stu'], value=1000000) # AssertEquals that the status code is 1 (failed tx) self.assertEqual(self.c.executor.execute(contract_name="con_too_many_writes", function_name="run", kwargs={}, stamps=1000, sender='stu')["status_code"], 1) self.c.executor.metering = False def test_memory_overload2(self): tmwc = self.c.get_contract('con_too_many_writes') self.c.executor.metering = True self.c.set_var(contract='currency', variable='balances', arguments=['stu'], value=1000000) # AssertEquals that the status code is 1 (failed tx) self.assertEqual(self.c.executor.execute(contract_name="con_too_many_writes", function_name="run2", kwargs={}, stamps=1000, sender='stu')["status_code"], 1) self.c.executor.metering = False def test_memory_exploit(self): self.c.executor.metering = True self.c.set_var(contract='currency', variable='balances', arguments=['stu'], value=1000000) # AssertEquals that the status code is 1 (failed tx) self.assertEqual(self.c.executor.execute(contract_name='submission', function_name='submit_contract', kwargs={'name': 'exploit', 'code': exploit}, stamps=1000, sender='stu')["status_code"], 1) self.c.executor.metering = False class TestPassHash(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract,) self.c.raw_driver.commit() submission = self.c.get_contract('submission') # submit erc20 clone pass_hash_path = os.path.join(os.path.dirname(__file__), "test_contracts", "pass_hash.s.py") with open(pass_hash_path) as f: code = f.read() self.c.submit(code, name='con_pass_hash') test_pass_hash_path = os.path.join(os.path.dirname(__file__), "test_contracts", "con_pass_hash.s.py") with open(test_pass_hash_path) as f: code = f.read() self.c.submit(code, name='con_test_pass_hash') self.pass_hash = self.c.get_contract('con_pass_hash') self.test_pass_hash = self.c.get_contract('con_test_pass_hash') def test_store_value(self): self.test_pass_hash.store(k='thing', v='value') output = self.test_pass_hash.get(k='thing') self.assertEqual(output, 'value') def some_test_contract(): @export def return_something(): return 1 def import_submission(): import submission @export def haha(): code = ''' @export def something(): pass ''' submission.submit_contract(name='something123', code=code) class TestDeveloperSubmission(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract,) self.c.raw_driver.commit() def test_submit_sets_developer(self): self.c.submit(some_test_contract, name="con_some_test_contract") dev = self.c.get_var('con_some_test_contract', '__developer__') self.assertEqual(dev, 'stu') def test_change_developer_if_developer_works(self): self.c.submit(some_test_contract, name="con_some_test_contract") submission = self.c.get_contract('submission') submission.change_developer(contract='con_some_test_contract', new_developer='not_stu') dev = self.c.get_var('con_some_test_contract', '__developer__') self.assertEqual(dev, 'not_stu') def test_change_developer_prevents_new_change(self): self.c.submit(some_test_contract, name="con_some_test_contract") submission = self.c.get_contract('submission') submission.change_developer(contract='con_some_test_contract', new_developer='not_stu') with self.assertRaises(AssertionError): submission.change_developer(contract='con_some_test_contract', new_developer='woohoo') def test_cannot_import_submission(self): self.c.submit(import_submission, name="con_import_submission") imp_con = self.c.get_contract('con_import_submission') with self.assertRaises(AssertionError): imp_con.haha() def con_float_thing(): @export def float_thing_fn(currency_reserve: float, token_reserve: float, currency_amount: float): k = currency_reserve * token_reserve new_currency_reserve = currency_reserve + currency_amount new_token_reserve = k / new_currency_reserve tokens_purchased = token_reserve - new_token_reserve return tokens_purchased class TestFloatThing(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract,) self.c.raw_driver.commit() def test_can_add(self): self.c.submit(con_float_thing, name="con_float_thing") ft_con = self.c.get_contract('con_float_thing') ft_con.float_thing_fn(currency_reserve=50000.125, token_reserve=52.45, currency_amount=100.25) def a(): @export def x(): return 1 def module_hack(): v = Variable() @export def hack(): hack.__module__ return 1 def class_var(): @export def hack(): v = Variable x = v(contract="currency", name="balances") def class_hash(): @export def hack(): v = Hash x = v(contract="currency", name="balances") def exec_contract(): @export def fn(): def builtins__(): pass wExec = builtins__["exec"] wExec("print('hello world')") def type_exploit(): @export def attack(to: str): # before # assert amount > 0, 'Cannot send negative balances!' def gt(a, b): print("gt", a, b) return True # assert balances[sender] >= amount, 'Not enough coins to send!' def le(a, b): print("lt", a, b) return True # balances[sender] -= amount def rsub(a, b): print("rsub", a, b) return b # balances[to] += amount def radd(a, b): print("radd", a, b) return 100 wAmount = type("wAmount", (), {"__gt__": gt, "__le__": le, "__radd__": radd, "__rsub__": rsub}) fake_amount_object = wAmount() def con_test_one(): h = Hash() @construct def seed(): h['a'] = 100 h['b'] = 999 @export def output(): return h['a'], h['b'] def con_test_two(): f = ForeignHash(foreign_contract='con_test_one', foreign_name='h') @export def clear(): f.clear() def test_closure(): def export(contract): def decorator(func): def enter(*args, **kwargs): result = func(*args, **kwargs) return result return enter return decorator @export def closure_inner(): return 1 def test_closure2(): def export(contract): a = 1 def decorator(func): b = 2 def enter(*args, **kwargs): result = func(*args, **kwargs) return result return enter return decorator @export def closure_inner(): return 1 class TestHackThing(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract,) self.c.raw_driver.commit() def test_can_add(self): self.c.submit(a, name="con_a") with self.assertRaises(Exception): self.c.submit(module_hack, name="con_module_hack") ft_con = self.c.get_contract('con_module_hack') ft_con.hack() def test_cant_submit_class_var(self): with self.assertRaises(Exception): self.c.submit(class_var) def test_cant_submit_class_hash(self): with self.assertRaises(Exception): self.c.submit(class_hash) def test_cant_submit_exec(self): with self.assertRaises(Exception): self.c.submit(exec_contract) def test_cant_submit_type(self): with self.assertRaises(Exception): self.c.submit(type_exploit) def test_cant_clear_foreign_hash(self): self.c.submit(con_test_one) self.c.submit(con_test_two) t2 = self.c.get_contract('con_test_two') with self.assertRaises(Exception): t2.clear() def test_no_closures(self): with self.assertRaises(Exception): self.c.submit(test_closure) def test_no_closures_work_around(self): with self.assertRaises(Exception): self.c.submit(test_closure2) def con_test_fixed(): v = Variable() @construct def seed(): v.set([1.234, 5.678]) @export def multiply(): a, b = v.get() return a * b class TestFixed(TestCase): def setUp(self): self.c = ContractingClient(signer='stu', driver=Driver()) self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract,) self.c.raw_driver.commit() def test_can_multiply(self): self.c.submit(con_test_fixed) self.c.raw_driver.commit() self.c.raw_driver.flush_cache() f = self.c.get_contract('con_test_fixed') z = f.multiply() self.assertEqual(z, 1.234 * 5.678) if __name__ == '__main__': import unittest unittest.main() ================================================ FILE: tests/integration/test_pixel_game.py ================================================ from contracting.client import ContractingClient from unittest import TestCase def con_coin(): supply = Variable() balances = Hash(default_value=0) owner = Variable() @construct def seed(): balances[ctx.caller] = 1000000 owner.set(ctx.caller) supply.set(1000000) @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount @export def balance_of(account: str): return balances[account] @export def total_supply(): return supply.get() @export def allowance(main: str, spender: str): return balances[main, spender] @export def approve(amount: int, to: str): sender = ctx.caller balances[sender, to] += amount return balances[sender, to] @export def transfer_from(amount: int, to: str, main_account: str): sender = ctx.caller assert balances[main_account, sender] >= amount, 'Not enough coins approved to send! You have {} and are trying to spend {}'\ .format(balances[main_account, sender], amount) assert balances[main_account] >= amount, 'Not enough coins to send!' balances[main_account, sender] -= amount balances[main_account] -= amount balances[to] += amount @export def mint(amount: int, to: str): assert ctx.caller == owner.get(), 'Only the owner can mint!' balances[to] += amount s = supply.get() supply.set(s + amount) @export def change_ownership(new_owner: str): assert ctx.caller == owner.get(), 'Only the owner can change ownership!' owner.set(new_owner) class TestCoinContract(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.flush() self.c.submit(con_coin) self.coin = self.c.get_contract('con_coin') def tearDown(self): self.c.flush() def test_coin_construction(self): self.assertEqual(self.coin.balances['stu'], 1000000) def test_transfer_not_enough(self): with self.assertRaises(AssertionError): self.coin.transfer(amount=9999999, to='raghu') def test_transfer_enough(self): self.coin.transfer(amount=123, to='raghu') self.assertEqual(self.coin.balances['raghu'], 123) def test_balance_of_works(self): self.coin.transfer(amount=123, to='raghu') self.assertEqual(self.coin.balance_of(account='raghu'), 123) def test_total_supply_pre_mint(self): self.assertEqual(self.coin.total_supply(), 1000000) self.assertEqual(self.coin.supply.get(), 1000000) def test_approve_modified_balances(self): self.coin.approve(amount=100, to='raghu') self.assertEqual(self.coin.balances['stu', 'raghu'], 100) def test_allowance_returns_approve(self): self.coin.approve(amount=100, to='raghu') self.assertEqual(self.coin.allowance(main='stu', spender='raghu'), 100) def test_transfer_from_failure_not_enough_allowance(self): self.coin.approve(amount=100, to='raghu') with self.assertRaises(AssertionError): self.coin.transfer_from(amount=101, to='colin', main_account='stu', signer='raghu') def test_transfer_from_failure_not_enough_in_main_account(self): self.coin.approve(amount=1000000000, to='raghu') with self.assertRaises(AssertionError): self.coin.transfer_from(amount=1000000000, to='colin', main_account='stu', signer='raghu') def test_transfer_from_success_modified_balance_to_and_allowance(self): self.coin.approve(amount=100, to='raghu') self.coin.transfer_from(amount=33, to='colin', main_account='stu', signer='raghu') self.assertEqual(self.coin.balances['colin'], 33) self.assertEqual(self.coin.balances['stu'], 1000000 - 33) self.assertEqual(self.coin.balances['stu', 'raghu'], 67) def test_mint_fails_if_not_owner(self): with self.assertRaises(AssertionError): self.coin.mint(amount=1000000, to='raghu', signer='raghu') def test_mint_succeeds_if_owner_and_modifies_balance_and_supply(self): self.coin.mint(amount=999, to='raghu') self.assertEqual(self.coin.balances['raghu'], 999) self.assertEqual(self.coin.supply.get(), 1000000 + 999) def test_change_ownership_modifies_owner(self): self.coin.change_ownership(new_owner='raghu') self.assertEqual(self.coin.owner.get(), 'raghu') def test_change_ownership_only_prior_owner(self): with self.assertRaises(AssertionError): self.coin.change_ownership(new_owner='colin', signer='raghu') def test_change_ownership_then_mint_succeeds(self): self.coin.change_ownership(new_owner='raghu') self.coin.mint(amount=999, to='raghu', signer='raghu') self.assertEqual(self.coin.balances['raghu'], 999) self.assertEqual(self.coin.supply.get(), 1000000 + 999) def con_pixel_game(): import con_coin plots = Hash() landlord = Variable() max_x = 256 max_y = 256 decay = 0.02 tax_period = datetime.DAYS * 1 def assert_in_bounds(x: int, y: int): assert 0 <= x < max_x, 'X coordinate out of bounds.' assert 0 <= y < max_y, 'Y coordinate out of bounds.' def assert_is_hex(color_string): assert len(color_string) == 256, 'Invalid color string passed.' assert int(color_string, 16), 'Color string is not a hex string.' @construct def seed(): landlord.set(ctx.caller) @export def buy_plot(x: int, y: int, amount: float, price: float): assert_in_bounds(x, y) plot = plots[x, y] if plot is None: plot = { 'colors': '0' * 256, 'owner': ctx.caller, 'price': price, 'purchase_time': now } plots[x. y] = plot else: assert plot['price'] <= amount assert amount <= coin.allowance(owner=ctx.caller, spender=ctx.this) coin.transfer_from(amount=plot['price'], to=plot['owner'], main_account=ctx.sender) plot.update({ 'owner': ctx.caller, 'price': price, 'purchase_time': now }) plots[x, y] = plot @export def set_plot(x: int, y: int, color_string: str): assert_is_hex(color_string) plot = plots[x, y] assert plot['owner'] == ctx.caller, 'You do not own this plot!' plot['colors'] = color_string plots[x, y] = plot class TestPixelGame(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.flush() self.c.submit(con_coin) self.c.submit(con_pixel_game) self.pixel = self.c.get_contract('con_pixel_game') def tearDown(self): self.c.flush() def test_init(self): self.assertEqual(1, 1) ================================================ FILE: tests/integration/test_rich_ctx_calling.py ================================================ from unittest import TestCase from contracting.client import ContractingClient def con_module1(): @export def get_context2(): return { 'name': 'get_context2', 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller, 'entry': ctx.entry, 'submission_name': ctx.submission_name } def con_all_in_one(): @export def call_me(): return call_me_again() @export def call_me_again(): return call_me_again_again() @export def call_me_again_again(): return({ 'name': 'call_me_again_again', 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller, 'entry': ctx.entry, 'submission_name': ctx.submission_name }) def con_dynamic_import(): @export def called_from_a_far(): m = importlib.import_module('con_all_in_one') res = m.call_me_again_again() return [res, { 'name': 'called_from_a_far', 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller, 'entry': ctx.entry, 'submission_name': ctx.submission_name }] @export def con_called_from_a_far_stacked(): m = importlib.import_module('con_all_in_one') return m.call() def con_submission_name_test(): submission_name = Variable() @construct def seed(): submission_name.set(ctx.submission_name) @export def get_submission_context(): return submission_name.get() @export def get_entry_context(): con_name, func_name = ctx.entry return { 'entry_contract': con_name, 'entry_function': func_name } class TestRandomsContract(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.flush() # Submit contracts self.c.submit(con_module1) self.c.submit(con_all_in_one) self.c.submit(con_dynamic_import) self.c.submit(con_submission_name_test) def tearDown(self): self.c.flush() def test_ctx2(self): module = self.c.get_contract('con_module1') res = module.get_context2() expected = { 'name': 'get_context2', 'entry': ('con_module1', 'get_context2'), 'owner': None, 'this': 'con_module1', 'signer': 'stu', 'caller': 'stu', 'submission_name': None } self.assertDictEqual(res, expected) def test_multi_call_doesnt_affect_parameters(self): aio = self.c.get_contract('con_all_in_one') res = aio.call_me() expected = { 'name': 'call_me_again_again', 'entry': ('con_all_in_one', 'call_me'), 'owner': None, 'this': 'con_all_in_one', 'signer': 'stu', 'caller': 'stu', 'submission_name': None } self.assertDictEqual(res, expected) # To-Do: Figure out why this test does not work when using pytest tests/integration, but works when running the test directly # def test_dynamic_call(self): # dy = self.c.get_contract('con_dynamic_import') # res1, res2 = dy.called_from_a_far() # expected1 = { # 'name': 'call_me_again_again', # 'entry': ('con_dynamic_import', 'called_from_a_far'), # 'owner': None, # 'this': 'con_all_in_one', # 'signer': 'stu', # 'caller': 'con_dynamic_import', # 'submission_name': None # } # expected2 = { # 'name': 'called_from_a_far', # 'entry': ('con_dynamic_import', 'called_from_a_far'), # 'owner': None, # 'this': 'con_dynamic_import', # 'signer': 'stu', # 'caller': 'stu', # 'submission_name': None # } # self.assertDictEqual(res1, expected1) # self.assertDictEqual(res2, expected2) def test_submission_name_in_construct_function(self): contract = self.c.get_contract('con_submission_name_test') submission_name = contract.get_submission_context() self.assertEqual("con_submission_name_test", submission_name) def test_entry_context(self): contract = self.c.get_contract('con_submission_name_test') details = contract.get_entry_context() self.assertEqual("con_submission_name_test", details.get('entry_contract')) self.assertEqual("get_entry_context", details.get('entry_function')) ================================================ FILE: tests/integration/test_run_private_function.py ================================================ from unittest import TestCase from contracting.client import ContractingClient import os class TestRunPrivateFunction(TestCase): def setUp(self): self.client = ContractingClient() private_methods_path = os.path.join(os.path.dirname(__file__), "test_contracts", "private_methods.s.py") with open(private_methods_path) as f: code = f.read() self.client.submit(code, name='private_methods') self.private_methods = self.client.get_contract('private_methods') def tearDown(self): self.client.flush() def test_can_call_public_func(self): self.assertEqual(self.private_methods.call_private(), 'abc') def test_cannot_call_private_func(self): with self.assertRaises(Exception): self.private_methods.private() def test_cannot_execute_private_func(self): with self.assertRaises(AssertionError): self.private_methods.executor.execute( sender='sys', contract_name='private_methods', function_name='__private', kwargs={} ) def test_can_call_private_func_if_run_private_function_called(self): self.assertEqual(self.private_methods.run_private_function('__private'), 'abc') def test_can_call_private_func_if_run_private_function_called_and_no_prefix(self): self.assertEqual(self.private_methods.run_private_function('private'), 'abc') def test_can_call_private_but_then_not(self): self.assertEqual(self.private_methods.run_private_function('private'), 'abc') with self.assertRaises(AssertionError): self.private_methods.executor.execute( sender='sys', contract_name='private_methods', function_name='__private', kwargs={} ) ================================================ FILE: tests/integration/test_senecaCompiler_integration.py ================================================ from unittest import TestCase from contracting.compilation.compiler import ContractingCompiler from contracting.stdlib import env from contracting import constants import re import astor import os class TestSenecaCompiler(TestCase): def test_visit_assign_variable(self): code = ''' v = Variable() ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) scope = env.gather() exec(code_str, scope) v = scope['__v'] self.assertEqual(v._key, '__main__.v') def test_visit_assign_foreign_variable(self): code = ''' fv = ForeignVariable(foreign_contract='scoob', foreign_name='kumbucha') ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) scope = env.gather() exec(code_str, scope) fv = scope['__fv'] self.assertEqual(fv._key, 'scoob.kumbucha') def test_assign_hash_variable(self): code = ''' h = Hash() ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) scope = env.gather() exec(code_str, scope) h = scope['__h'] self.assertEqual(h._key, '__main__.h') def test_assign_foreign_hash(self): code = ''' fv = ForeignHash(foreign_contract='scoob', foreign_name='kumbucha') ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) scope = env.gather() exec(code_str, scope) fv = scope['__fv'] self.assertEqual(fv._key, 'scoob.kumbucha') # self.assertEqual(fv.foreign_key, 'scoob.kumbucha') # # def test_export_decorator_pops(self): # code = ''' # @export # def funtimes(): # print('cool') # ''' # # c = ContractingCompiler() # comp = c.parse(code, lint=False) # code_str = astor.to_source(comp) # # self.assertNotIn('@export', code_str) def test_private_function_prefixes_properly(self): code = ''' def private(): print('cool') ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) self.assertIn('__private', code_str) def test_private_func_call_in_public_func_properly_renamed(self): code = ''' @export def public(): private('hello') def private(message): print(message) ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) # there should be two private occurances of the method call self.assertEqual(len([m.start() for m in re.finditer('__private', code_str)]), 2) def test_private_func_call_in_other_private_functions(self): code = ''' def a(): b() def b(): c() def c(): e() def d(): print('hello') def e(): d() ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) self.assertEqual(len([m.start() for m in re.finditer(constants.PRIVATE_METHOD_PREFIX, code_str)]), 9) def test_construct_renames_properly(self): code = ''' @construct def seed(): print('yes') @export def hello(): print('no') def goodbye(): print('idk') ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) def test_token_contract_parses_correctly(self): currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") with open(currency_path) as f: code = f.read() c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) def test_export_decorator_argument_is_added(self): code = ''' @export def test(): pass ''' c = ContractingCompiler() comp = c.parse(code, lint=False) code_str = astor.to_source(comp) print(code_str) ================================================ FILE: tests/integration/test_seneca_client_randoms.py ================================================ from unittest import TestCase from contracting.client import ContractingClient import random def con_random_contract(): random.seed() cards = [1, 2, 3, 4, 5, 6, 7, 8] cardinal_values = ['A', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] suits = ['S', 'C', 'H', 'D'] cities = ['Cleveland', 'Detroit', 'Chicago', 'New York', 'San Francisco'] @export def shuffle_cards(**kwargs: dict): random.shuffle(cards) return cards @export def random_number(k: int): return random.randrange(k) @export def random_number_2(k: int): # adjust the random state by calling another random function shuffle_cards() return random.randrange(k) @export def random_bits(k: int): shuffle_cards() shuffle_cards() shuffle_cards() return random.getrandbits(k) @export def int_in_range(a: int, b: int): shuffle_cards() shuffle_cards() return random.randint(a, b) @export def deal_card(): random.shuffle(cardinal_values) random.shuffle(cardinal_values) random.shuffle(cardinal_values) random.shuffle(suits) random.shuffle(suits) random.shuffle(suits) c = '' c += random.choice(cardinal_values) c += random.choice(suits) return c @export def pick_cities(k: int): return random.choices(cities, k) class TestRandomsContract(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.flush() self.c.submit(con_random_contract) self.random_contract = self.c.get_contract('con_random_contract') def tearDown(self): self.c.flush() def test_basic_shuffle(self): cards_1 = self.random_contract.shuffle_cards() cards_2 = self.random_contract.shuffle_cards() self.assertEqual(cards_1, cards_2) def test_basic_shuffle_different_with_different_seeds(self): cards_1 = self.random_contract.shuffle_cards(environment={'block_num': 999}) cards_2 = self.random_contract.shuffle_cards(environment={'block_num': 998}) self.assertNotEqual(cards_1, cards_2) def test_random_num_one_vs_two(self): k = self.random_contract.random_number(k=1000) k2 = self.random_contract.random_number_2(k=1000) self.assertNotEqual(k, k2) random.seed('000') self.assertEqual(k, random.randrange(1000)) random.seed('000') cards = [1, 2, 3, 4, 5, 6, 7, 8] random.shuffle(cards) self.assertEqual(k2, random.randrange(1000)) '''' TEST CASE IS IRRELEVANT as getrandbits will never sync with system random. def test_random_getrandbits(self): b = self.random_contract.random_bits(k=20) random.seed('000') cards = [1, 2, 3, 4, 5, 6, 7, 8] random.shuffle(cards) random.shuffle(cards) random.shuffle(cards) self.assertEqual(b, random.getrandbits(20)) ''' def test_random_range_int(self): a = self.random_contract.int_in_range(a=100, b=50000) random.seed('000') cards = [1, 2, 3, 4, 5, 6, 7, 8] random.shuffle(cards) random.shuffle(cards) self.assertEqual(a, random.randint(a=100, b=50000)) def test_random_choice(self): cities = self.random_contract.pick_cities(k=2) random.seed('000') c = ['Cleveland', 'Detroit', 'Chicago', 'New York', 'San Francisco'] cc = random.choices(c, k=2) self.assertListEqual(cities, cc) def test_auxiliary_salt(self): cards_1 = self.random_contract.shuffle_cards(environment={ 'AUXILIARY_SALT': 'ffd8ded9ced929a41dae83b1f22a6a31b52f79bbf4cdabe6a27d9646dd2bd725fc29c8bc122cb9e37a2904da00e34df499ee7a897505d1de3f0511f9f9c1150c'}) cards_2 = self.random_contract.shuffle_cards(environment={ 'AUXILIARY_SALT': 'ffd8ded9ced929a41dae83b1f22a6a31b52f79bbf4cdabe6a27d9646dd2bd725fc29c8bc122cb9e37a2904da00e34df499ee7a897505d1de3f0511f9f9c1150c'}) cards_3 = self.random_contract.shuffle_cards(environment={ 'AUXILIARY_SALT': 'f79bbded9ced929a41dae83b1f22a6a31b52f79bbf4cdabe6a27d9646dd2bd725fc29c8bc122cb9e37a2904da00e34df499ee7a897505d1de3f0511f9f9c1150c'}) self.assertEqual(cards_1, cards_2) self.assertNotEqual(cards_1, cards_3) ================================================ FILE: tests/integration/test_seneca_client_replaces_executor.py ================================================ from unittest import TestCase from contracting.stdlib.bridge.time import Datetime from contracting.client import ContractingClient import os class TestSenecaClientReplacesExecutor(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract,) self.c.raw_driver.commit() submission = self.c.get_contract('submission') # submit erc20 clone erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") with open(erc20_clone_path) as f: code = f.read() self.c.submit(code, name='con_erc20_clone') atomic_swaps_path = os.path.join(os.path.dirname(__file__), "test_contracts", "atomic_swaps.s.py") with open(atomic_swaps_path) as f: code = f.read() self.c.submit(code, name='con_atomic_swaps') self.erc20_clone = self.c.get_contract('con_erc20_clone') self.atomic_swaps = self.c.get_contract('con_atomic_swaps') def tearDown(self): self.c.raw_driver.flush_full() def test_initiate_not_enough_approved(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') with self.assertRaises(AssertionError): self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5000000) def test_initiate_transfers_coins_correctly(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) atomic_swaps = self.erc20_clone.balance_of(account='con_atomic_swaps') stu_bal = self.erc20_clone.balance_of(account='stu') stu_as = self.erc20_clone.allowance(owner='stu', spender='con_atomic_swaps') self.assertEqual(atomic_swaps, 5) self.assertEqual(stu_bal, 999995) self.assertEqual(stu_as, 999995) def test_initiate_writes_to_correct_key_and_properly(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) key = 'con_atomic_swaps.swaps:raghu:eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514' expiration, amount = self.c.raw_driver.get(key) self.assertEqual(expiration, Datetime(2020, 1, 1)) self.assertEqual(amount, 5) def test_redeem_on_wrong_secret_fails(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) with self.assertRaises(AssertionError): self.atomic_swaps.redeem(signer='raghu', secret='00') def test_redeem_on_wrong_sender_fails(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) with self.assertRaises(AssertionError): self.atomic_swaps.redeem(secret='842b65a7d48e3a3c3f0e9d37eaced0b2') def test_past_expiration_fails(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2021, 1, 1)} with self.assertRaises(AssertionError): self.atomic_swaps.redeem(secret='842b65a7d48e3a3c3f0e9d37eaced0b2', signer='raghu', environment=environment) def test_successful_redeem_transfers_coins_correctly(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2019, 1, 1)} self.atomic_swaps.redeem(secret='842b65a7d48e3a3c3f0e9d37eaced0b2', signer='raghu', environment=environment) atomic_swaps = self.erc20_clone.balance_of(account='con_atomic_swaps') raghu = self.erc20_clone.balance_of(account='raghu') self.assertEqual(raghu, 5) self.assertEqual(atomic_swaps, 0) def test_successful_redeem_deletes_entry(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2019, 1, 1)} self.atomic_swaps.redeem(secret='842b65a7d48e3a3c3f0e9d37eaced0b2', signer='raghu', environment=environment) key = 'atomic_swaps.swaps:raghu:eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514' v = self.c.raw_driver.get(key) self.assertEqual(v, None) def test_refund_works(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2021, 1, 1)} self.atomic_swaps.refund(participant='raghu', secret='842b65a7d48e3a3c3f0e9d37eaced0b2', environment=environment) atomic_swaps = self.erc20_clone.balance_of(account='con_atomic_swaps') stu = self.erc20_clone.balance_of(account='stu') self.assertEqual(stu, 1000000) self.assertEqual(atomic_swaps, 0) def test_refund_too_early_fails(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2019, 1, 1)} with self.assertRaises(AssertionError): self.atomic_swaps.refund(participant='raghu', secret='842b65a7d48e3a3c3f0e9d37eaced0b2', environment=environment) def test_refund_participant_is_signer_fails(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2021, 1, 1)} with self.assertRaises(AssertionError): self.atomic_swaps.refund(participant='raghu', secret='842b65a7d48e3a3c3f0e9d37eaced0b2', environment=environment, signer='raghu') def test_refund_fails_with_wrong_secret(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2021, 1, 1)} with self.assertRaises(AssertionError): self.atomic_swaps.refund(participant='raghu', secret='00', environment=environment, ) def test_refund_resets_swaps(self): self.erc20_clone.approve(amount=1000000, to='con_atomic_swaps') self.atomic_swaps.initiate(participant='raghu', expiration=Datetime(2020, 1, 1), hashlock='eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514', amount=5) environment = {'now': Datetime(2021, 1, 1)} self.atomic_swaps.refund(participant='raghu', secret='842b65a7d48e3a3c3f0e9d37eaced0b2', environment=environment) key = 'atomic_swaps.swaps:raghu:eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514' v = self.c.raw_driver.get(key) self.assertEqual(v, None) ================================================ FILE: tests/integration/test_stamp_deduction.py ================================================ from unittest import TestCase from contracting.storage.driver import Driver from contracting.execution.executor import Executor from contracting.constants import STAMPS_PER_TAU import contracting import os def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': f'con_{contract_name}', 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } class TestMetering(TestCase): def setUp(self): # Hard load the submission contract self.d = Driver() self.d.flush_full() submission_path = os.path.join(os.path.dirname(__file__), "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.d.set_contract(name='submission', code=contract) self.d.commit() # Execute the currency contract with metering disabled self.e = Executor(driver=self.d, currency_contract='con_currency') currency_path = os.path.join(os.path.dirname(__file__), "test_contracts", "currency.s.py") self.e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(currency_path), metering=False, auto_commit=True) def tearDown(self): self.d.flush_full() def test_simple_execution_deducts_stamps(self): prior_balance = self.d.get('con_currency.balances:stu') output = self.e.execute('stu', 'con_currency', 'transfer', kwargs={'amount': 100, 'to': 'colin'}, auto_commit=True) new_balance = self.d.get('con_currency.balances:stu') self.assertEqual(float(prior_balance - new_balance - 100), output['stamps_used'] / STAMPS_PER_TAU) def test_too_few_stamps_fails_and_deducts_properly(self): prior_balance = self.d.get('con_currency.balances:stu') print(prior_balance) output = self.e.execute('stu', 'con_currency', 'transfer', kwargs={'amount': 100, 'to': 'colin'}, stamps=1, auto_commit=True) print(output) new_balance = self.d.get('con_currency.balances:stu') self.assertEqual(float(prior_balance - new_balance), output['stamps_used'] / STAMPS_PER_TAU) def test_adding_too_many_stamps_throws_error(self): prior_balance = self.d.get('con_currency.balances:stu') too_many_stamps = (prior_balance + 1000) * STAMPS_PER_TAU output = self.e.execute('stu', 'con_currency', 'transfer', kwargs={'amount': 100, 'to': 'colin'}, stamps=too_many_stamps, auto_commit=True) self.assertEqual(output['status_code'], 1) def test_adding_all_stamps_with_infinate_loop_eats_all_balance(self): self.d.set('con_currency.balances:stu', 500) self.d.commit() prior_balance = self.d.get('con_currency.balances:stu') prior_balance *= STAMPS_PER_TAU inf_loop_path = os.path.join(os.path.dirname(__file__), "test_contracts", "inf_loop.s.py") res = self.e.execute( **TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(inf_loop_path), stamps=prior_balance, metering=True, auto_commit=True ) new_balance = self.d.get('con_currency.balances:stu') # Not all stamps will be deducted because it will blow up in the middle of execution self.assertTrue(new_balance < 500) def test_submitting_contract_succeeds_with_enough_stamps(self): prior_balance = self.d.get('con_currency.balances:stu') print(prior_balance) erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") output = self.e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path), auto_commit=True ) print(output) new_balance = self.d.get('con_currency.balances:stu') print(new_balance) self.assertEqual(float(prior_balance - new_balance), output['stamps_used'] / STAMPS_PER_TAU) def test_pending_writes_has_deducted_stamp_amount_prior_to_auto_commit(self): prior_balance = self.d.get('con_currency.balances:stu') erc20_clone_path = os.path.join(os.path.dirname(__file__), "test_contracts", "erc20_clone.s.py") output = self.e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(erc20_clone_path), auto_commit=False ) self.assertNotEquals(self.e.driver.pending_writes['con_currency.balances:stu'], prior_balance) ================================================ FILE: tests/performance/__init__.py ================================================ ================================================ FILE: tests/performance/prof_transfer.py ================================================ import secrets from contracting.storage.driver import Driver from contracting.execution.executor import Executor def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': contract_name, 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } d = Driver() d.flush_full() with open('../../contracting/contracts/submission.s.py') as f: contract = f.read() d.set_contract(name='submission', code=contract) d.commit() recipients = [secrets.token_hex(16) for _ in range(1000)] e = Executor() e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file('../integration/test_contracts/erc20_clone.s.py')) import datetime for i in range(20): now = datetime.datetime.now() # profiler = Profiler() # profiler.start() for r in recipients: res = e.execute(sender='stu', contract_name='con_erc20_clone', function_name='transfer', kwargs={ 'amount': 1, 'to': r }) print(res) # profiler.stop() print(datetime.datetime.now() - now) d.flush_full() # print(profiler.last_session.duration) # print(profiler.output_text(unicode=True, color=True, show_all=True)) ================================================ FILE: tests/performance/test_contracts/__init__.py ================================================ ================================================ FILE: tests/performance/test_contracts/erc20_clone.s.py ================================================ supply = Variable() balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 1000000 balances['colin'] = 100 supply.set(balances['stu'] + balances['colin']) @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount @export def balance_of(account: str): return balances[account] @export def total_supply(): return supply.get() @export def allowance(owner: str, spender: str): return balances[owner, spender] @export def approve(amount: str, to: str): sender = ctx.caller balances[sender, to] += amount return balances[sender, to] @export def transfer_from(amount: int, to: str, main_account: str): sender = ctx.caller assert balances[main_account, sender] >= amount, 'Not enough coins approved to send! You have {} and are trying to spend {}'\ .format(balances[main_account, sender], amount) assert balances[main_account] >= amount, 'Not enough coins to send!' balances[main_account, sender] -= amount balances[main_account] -= amount balances[to] += amount ================================================ FILE: tests/performance/test_contracts/modules/all_in_one.s.py ================================================ @export def call_me(): return call_me_again() @export def call_me_again(): return call_me_again_again() @export def call_me_again_again(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/dynamic_import.s.py ================================================ @export def called_from_a_far(): m = importlib.import_module('all_in_one') return m.call_me_again_again() @export def called_from_a_far_stacked(): m = importlib.import_module('all_in_one') return m.call() ================================================ FILE: tests/performance/test_contracts/modules/module1.s.py ================================================ ''' how the modules import each other. this is to test ctx.caller etc 1 | | 2 3 | | | | 4 5 6 7 | 8 ''' import module2 import module3 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/module2.s.py ================================================ import module4 import module5 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/module3.s.py ================================================ import module6 import module7 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/module4.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/module5.s.py ================================================ import module8 @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/module6.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/module7.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/modules/module8.s.py ================================================ @export def get_context(): return { 'owner': ctx.owner, 'this': ctx.this, 'signer': ctx.signer, 'caller': ctx.caller } ================================================ FILE: tests/performance/test_contracts/submission.s.py ================================================ @__export('submission') def submit_contract(name: str, code: str, owner: Any=None, constructor_args: dict={}): if ctx.caller != 'sys': assert name.startswith('con_'), 'Contract must start with con_!' assert ctx.caller == ctx.signer, 'Contract cannot be called from another contract!' assert len(name) <= 64, 'Contract name length exceeds 64 characters!' assert name.islower(), 'Contract name must be lowercase!' __Contract().submit( name=name, code=code, owner=owner, constructor_args=constructor_args, developer=ctx.caller ) @__export('submission') def change_developer(contract: str, new_developer: str): d = __Contract()._driver.get_var(contract=contract, variable='__developer__') assert ctx.caller == d, 'Sender is not current developer!' __Contract()._driver.set_var( contract=contract, variable='__developer__', value=new_developer ) ================================================ FILE: tests/performance/test_transfer.py ================================================ from unittest import TestCase import secrets from contracting.storage.driver import Driver from contracting.execution.executor import Executor import os def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': f'con_{contract_name}', 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } class TestSandbox(TestCase): def setUp(self): self.d = Driver() self.d.flush_full() self.script_dir = os.path.dirname(os.path.abspath(__file__)) submission_path = os.path.join(self.script_dir, "test_contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.d.set_contract(name='submission', code=contract) self.d.commit() self.recipients = [secrets.token_hex(16) for _ in range(10000)] def tearDown(self): self.d.flush_full() def test_transfer_performance(self): e = Executor() e.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(os.path.join(self.script_dir, "test_contracts", "erc20_clone.s.py"))) for r in self.recipients: e.execute(sender='stu', contract_name='con_erc20_clone', function_name='transfer', kwargs={ 'amount': 1, 'to': r }) ================================================ FILE: tests/security/__init__.py ================================================ ================================================ FILE: tests/security/contracts/builtin_hack_token.s.py ================================================ balances = Hash(default_value=0) @construct def seed(): setattr(balances, '_key', 'erc20.balances') balances['stu'] = 99999999999999999 @export def blah(): return 1 ================================================ FILE: tests/security/contracts/call_infinate_loop.s.py ================================================ import con_infinate_loop @export def call(): con_infinate_loop.loop() ================================================ FILE: tests/security/contracts/con_inf_writes.s.py ================================================ hhash = Hash(default_value=0) @construct def seed(): hashed_data = 'woohoo' while True: hashed_data = hashlib.sha3(hashed_data) hhash[hashed_data] = 'a' @export def dummy(): return 0 ================================================ FILE: tests/security/contracts/constructor_infinate_loop.s.py ================================================ @construct def seed(): i = 0 while True: i += 1 @export def dummy(): return 0 ================================================ FILE: tests/security/contracts/double_spend_gas_attack.s.py ================================================ import con_erc20 @construct def seed(): pass @export def double_spend(receiver: str): allowance = con_erc20.allowance(owner=ctx.caller, spender=ctx.this) con_erc20.transfer_from(amount=allowance, to=receiver, main_account=ctx.caller) i = 0 while True: i += 1 ================================================ FILE: tests/security/contracts/erc20_clone.s.py ================================================ supply = Variable() balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 1000000 balances['colin'] = 100 supply.set(balances['stu'] + balances['colin']) @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount @export def balance_of(account: str): return balances[account] @export def total_supply(): return supply.get() @export def allowance(owner: str, spender: str): return balances[owner, spender] @export def approve(amount: str, to: str): sender = ctx.caller balances[sender, to] += amount return balances[sender, to] @export def transfer_from(amount: int, to: str, main_account: str): sender = ctx.caller assert balances[main_account, sender] >= amount, 'Not enough coins approved to send! You have {} and are trying to spend {}'\ .format(balances[main_account, sender], amount) assert balances[main_account] >= amount, 'Not enough coins to send!' balances[main_account, sender] -= amount balances[main_account] -= amount balances[to] += amount ================================================ FILE: tests/security/contracts/get_set_driver.py ================================================ import erc20 driver = erc20.rt.env.get('__Driver') @construct def seed(): driver.set( key='erc20.balances:stu', value=1 ) @export def dummy(): pass ================================================ FILE: tests/security/contracts/get_set_driver_2.py ================================================ import erc20 z = erc20.rt driver = z.env.get('__Driver') @construct def seed(): driver.set( key='erc20.balances:stu', value=1 ) @export def dummy(): pass ================================================ FILE: tests/security/contracts/hack_tokens.s.py ================================================ balances = Hash(default_value=0) @construct def seed(): balances.key = 'con_erc20.balances' balances['stu'] = 99999999999999999 @export def blah(): return 1 ================================================ FILE: tests/security/contracts/import_hash_from_contract.s.py ================================================ import erc20 @construct def seed(): erc20.balances['stu'] = 999999999999 @export def dummy(): return 0 ================================================ FILE: tests/security/contracts/infinate_loop.s.py ================================================ @export def loop(): i = 0 while True: i += 1 @export def eat_stamps(): while True: pass ================================================ FILE: tests/security/contracts/submission.s.py ================================================ @__export('submission') def submit_contract(name: str, code: str, owner: Any=None, constructor_args: dict={}): if ctx.caller != 'sys': assert name.startswith('con_'), 'Contract must start with con_!' assert ctx.caller == ctx.signer, 'Contract cannot be called from another contract!' assert len(name) <= 64, 'Contract name length exceeds 64 characters!' assert name.islower(), 'Contract name must be lowercase!' __Contract().submit( name=name, code=code, owner=owner, constructor_args=constructor_args, developer=ctx.caller ) @__export('submission') def change_developer(contract: str, new_developer: str): d = __Contract()._driver.get_var(contract=contract, variable='__developer__') assert ctx.caller == d, 'Sender is not current developer!' __Contract()._driver.set_var( contract=contract, variable='__developer__', value=new_developer ) ================================================ FILE: tests/security/test_erc20_token_hacks.py ================================================ from unittest import TestCase from contracting.client import ContractingClient import os class TestTokenHacks(TestCase): def setUp(self): self.c = ContractingClient(signer='stu') self.c.raw_driver.flush_full() self.script_dir = os.path.dirname(os.path.abspath(__file__)) submission_path = os.path.join(self.script_dir, "contracts", "submission.s.py") with open(submission_path) as f: contract = f.read() self.c.raw_driver.set_contract(name='submission', code=contract) self.c.raw_driver.commit() self.c.executor.currency_contract = 'con_erc20' self.c.signer = 'stu' # submit erc20 clone with open(os.path.join(self.script_dir, "contracts", "erc20_clone.s.py")) as f: code = f.read() self.c.submit(code, name='con_erc20', metering=False) self.c.executor.metering = True def tearDown(self): self.c.raw_driver.flush_full() def test_orm_rename_hack(self): # This hack renames the contract property on its own balances hash to modify the erc20 balances token = self.c.get_contract('con_erc20') pre_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) with open(os.path.join(self.script_dir, "contracts", "hack_tokens.s.py")) as f: code = f.read() self.c.submit(code, name='con_token_hack', signer="stu") post_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) # Assert greater because some of the balance is lost to stamps self.assertGreater(pre_hack_balance, post_hack_balance) def test_orm_setattr_hack(self): # This hack uses setattr instead of direct property access to do the same thing as above token = self.c.get_contract('con_erc20') pre_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) with self.assertRaises(Exception): with open(os.path.join(self.script_dir, "contracts", "builtin_hack_token.s.py")) as f: code = f.read() self.c.submit(code, name='token_hack') post_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) # The balance *should not* change between these tests! self.assertEqual(pre_hack_balance, post_hack_balance) def test_double_spend_if_stamps_run_out(self): token = self.c.get_contract('con_erc20') pre_hack_balance_stu = float(str(self.c.get_var('con_erc20', "balances", arguments=["stu"]))) pre_hack_balance_colin = float(str(self.c.get_var('con_erc20', "balances", arguments=["colin"]))) # Approve the "hack" contract to spend stu's tokens tx_amount=10000 token.approve(amount=tx_amount, to='con_hack', stamps=200) with open(os.path.join(self.script_dir, "contracts", "double_spend_gas_attack.s.py")) as f: code = f.read() self.c.submit(code, name='con_hack', metering=True) # Test the double_spend contract # - sends the amount of the "allowance" (set in token.approve as 'tx_amount') # - calls transfer_from to send from 'stu' to 'colin' as 'con_hack' con_hack = self.c.get_contract('con_hack') self.c.raw_driver.commit() with self.assertRaises(AssertionError): # breakpoint() # Should fail when stamp_limit of 200 is reached con_hack.double_spend(receiver='colin', stamps=200) # Since the above call failed, the balance should be the same as before and NOT balance + tx_amount post_hack_balance_stu = float(str(self.c.get_var('con_erc20', "balances", arguments=["stu"]))) post_hack_balance_colin = float(str(self.c.get_var('con_erc20', "balances", arguments=["colin"]))) # Contracting will revert the state if the stamps run out and the transaction fails, which previously was not the case # Stu's POST balance should be less than the pre balance (less the tx_amount) because stamps were also deducted self.assertLess(post_hack_balance_stu, pre_hack_balance_stu + tx_amount) # Colin's balance will not change because the transaction failed and the state was not updated # Assert greater because some of the balance is lost to stamps self.assertEqual(pre_hack_balance_colin, post_hack_balance_colin) def test_stamp_fails_when_calling_infinate_loop_from_another_contract(self): with open(os.path.join(self.script_dir, "contracts", "infinate_loop.s.py")) as f: code = f.read() self.c.submit(code, name='con_infinate_loop') with open(os.path.join(self.script_dir, "contracts", "call_infinate_loop.s.py")) as f: code = f.read() self.c.submit(code, name='con_call_infinate_loop', metering=True) loop = self.c.get_contract('con_call_infinate_loop') with self.assertRaises(AssertionError): loop.call() def test_constructor_with_infinate_loop_fails(self): with self.assertRaises(AssertionError): with open(os.path.join(self.script_dir, "contracts", "constructor_infinate_loop.s.py")) as f: code = f.read() self.c.submit(code, name='con_constructor_infinate_loop', metering=True) def test_infinate_loop_of_writes_undos_everything(self): with self.assertRaises(AssertionError): with open(os.path.join(self.script_dir, "contracts", "con_inf_writes.s.py")) as f: code = f.read() self.c.submit(code, name='con_inf_writes', metering=True) def test_accessing_variable_on_another_contract(self): token = self.c.get_contract('con_erc20') pre_hack_balance_stu = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) try: with open(os.path.join(self.script_dir, "contracts", "import_hash_from_contract.s.py")) as f: code = f.read() self.c.submit(code, name='import_hash_from_contract', metering=False) except: pass post_hack_balance_stu = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) self.assertEqual(pre_hack_balance_stu, post_hack_balance_stu) def test_get_set_driver(self): # This hack uses setattr instead of direct property access to do the same thing as above token = self.c.get_contract('con_erc20') pre_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) #with self.assertRaises(Exception): try: with open(os.path.join(self.script_dir, "contracts", "get_set_driver.py")) as f: code = f.read() self.c.submit(code, name='token_hack', metering=False) except Exception as err: print(err) pass post_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) print() print(post_hack_balance) # The balance *should not* change between these tests! self.assertEqual(pre_hack_balance, post_hack_balance) def test_get_set_driver_2(self): # This hack uses setattr instead of direct property access to do the same thing as above token = self.c.get_contract('con_erc20') pre_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) #with self.assertRaises(Exception): try: with open(os.path.join(self.script_dir, "contracts", "get_set_driver_2.py")) as f: code = f.read() self.c.submit(code, name='token_hack', metering=False) except Exception as err: print(err) pass post_hack_balance = self.c.raw_driver.get_var('con_erc20', "balances", arguments=["stu"]) print() print(post_hack_balance) # The balance *should not* change between these tests! self.assertEqual(pre_hack_balance, post_hack_balance) ================================================ FILE: tests/unit/__init__.py ================================================ ================================================ FILE: tests/unit/contracts/currency.s.py ================================================ balances = Hash() @construct def seed(): balances['stu'] = 1000000 balances['colin'] = 100 @export def transfer(amount: int, to: str): sender = ctx.signer assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount if balances[to] is None: balances[to] = amount else: balances[to] += amount @export def balance(account: str): return balances[account] ================================================ FILE: tests/unit/contracts/exception.s.py ================================================ balances = Hash(default_value=0) @construct def seed(): balances['stu'] = 999 balances['colin'] = 555 @export def transfer(amount: int, to: str): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount raise Exception('This is an exception') @export def balance_of(account: str): return balances[account] ================================================ FILE: tests/unit/contracts/proxythis.py ================================================ @export def proxythis(con: str): return importlib.import_module(con).getthis() @export def nestedproxythis(con: str): return importlib.import_module(con).nested_exported() @export def noproxy(): return ctx.this, ctx.caller ================================================ FILE: tests/unit/contracts/submission.s.py ================================================ @__export('submission') def submit_contract(name: str, code: str, owner: Any=None, constructor_args: dict={}): if ctx.caller != 'sys': assert name.startswith('con_'), 'Contract must start with con_!' assert ctx.caller == ctx.signer, 'Contract cannot be called from another contract!' assert len(name) <= 64, 'Contract name length exceeds 64 characters!' assert name.islower(), 'Contract name must be lowercase!' __Contract().submit( name=name, code=code, owner=owner, constructor_args=constructor_args, developer=ctx.caller ) @__export('submission') def change_developer(contract: str, new_developer: str): d = __Contract()._driver.get_var(contract=contract, variable='__developer__') assert ctx.caller == d, 'Sender is not current developer!' __Contract()._driver.set_var( contract=contract, variable='__developer__', value=new_developer ) ================================================ FILE: tests/unit/contracts/thistest2.py ================================================ @export def exported(): return ctx.this, ctx.caller @export def getthis(): return ctx.this, ctx.caller @export def nested_exported(): return exported() ================================================ FILE: tests/unit/loop_client_test.sh ================================================ #!/usr/bin/env bash count=0 while : do output=$(python test_client.py TestSenecaClient.test_update_master_db_with_incomplete_sb 2>&1 >/dev/null) if [[ $output == *"FAIL:"* ]]; then echo "FAILURE!!" echo "$output" break else count=$((count+1)) echo "Test passed (succ #$count)" fi done ================================================ FILE: tests/unit/precompiled/__init__.py ================================================ ================================================ FILE: tests/unit/precompiled/compiled_token.py ================================================ # Monkey patch for testing, as this is purely for 'interface enforcement' testing from contracting.storage.orm import Variable, Hash class ctx: caller = 1 __supply = Variable(contract='__main__', name='supply') __balances = Hash(default_value=0, contract='__main__', name='balances') def ____(): __balances['stu'] = 1000000 __balances['colin'] = 100 __supply.set(__balances['stu'] + __balances['colin']) def transfer(amount, to): sender = ctx.caller assert __balances[sender] >= amount, 'Not enough coins to send!' __balances[sender] -= amount __balances[to] += amount def balance_of(account): return __balances[account] def total_supply(): return __supply.get() def allowance(owner, spender): return __balances[owner, spender] def approve(amount, to): sender = ctx.caller __balances[sender, to] += amount return __balances[sender, to] def transfer_from(amount, to, main_account): sender = ctx.caller assert __balances[main_account, sender ] >= amount, 'Not enough coins approved to send! You have {} and are trying to spend {}'.format( __balances[main_account, sender], amount) assert __balances[main_account] >= amount, 'Not enough coins to send!' __balances[main_account, sender] -= amount __balances[main_account] -= amount __balances[to] += amount def __private_func(): return 5 ================================================ FILE: tests/unit/precompiled/updated_submission.py ================================================ @__export('submission') def submit_contract(name: str, code: str, owner: Any=None, constructor_args: dict={}): if ctx.caller != 'sys': assert name.startswith('con_'), 'Contract must start with con_!' assert ctx.caller == ctx.signer, 'Contract cannot be called from another contract!' assert len(name) <= 64, 'Contract name length exceeds 64 characters!' assert name.islower(), 'Contract name must be lowercase!' __Contract().submit( name=name, code=code, owner=owner, constructor_args=constructor_args, developer=ctx.caller ) @__export('submission') def change_developer(contract: str, new_developer: str): d = __Contract()._driver.get_var(contract=contract, variable='__developer__') assert ctx.caller == d, 'Sender is not current developer !!!!!!!!' __Contract()._driver.set_var( contract=contract, variable='__developer__', value=new_developer ) ================================================ FILE: tests/unit/test_client.py ================================================ from unittest import TestCase from contracting.client import ContractingClient from contracting.storage.driver import Driver import os from pathlib import Path class TestClient(TestCase): def setUp(self): self.client = None self.driver = Driver() self.script_dir = os.path.dirname(os.path.abspath(__file__)) submission_file_path = os.path.join(self.script_dir, "contracts", "submission.s.py") with open(submission_file_path) as f: self.submission_contract_file = f.read() def tearDown(self): if self.client: self.client.flush() def test_set_submission_updates_contract_file(self): self.client = ContractingClient(driver=self.driver) self.client.flush() submission_1_code = self.client.raw_driver.get('submission.__code__') self.script_dir = os.path.dirname(os.path.abspath(__file__)) submission_file_path = os.path.join(self.script_dir, "precompiled", "updated_submission.py") self.driver.flush_full() self.client.set_submission_contract(filename=submission_file_path) submission_2_code = self.client.raw_driver.get('submission.__code__') self.assertNotEqual(submission_1_code, submission_2_code) def test_can_create_instance_without_submission_contract(self): self.client = ContractingClient(submission_filename=None, driver=self.driver) self.assertIsNotNone(self.client) def test_gets_submission_contract_from_state_if_no_filename_provided(self): self.driver.set_contract(name='submission', code=self.submission_contract_file) self.driver.commit() self.client = ContractingClient(submission_filename=None, driver=self.driver) self.assertIsNotNone(self.client.submission_contract) def test_set_submission_contract__sets_from_submission_filename_property(self): self.client = ContractingClient(driver=self.driver) self.client.raw_driver.flush_full() self.client.submission_contract = None contract = self.client.raw_driver.get_contract('submission') self.assertIsNone(contract) self.assertIsNone(self.client.submission_contract) self.client.set_submission_contract() contract = self.client.raw_driver.get_contract('submission') self.assertIsNotNone(contract) self.assertIsNotNone(self.client.submission_contract) def test_set_submission_contract__sets_from_submission_from_state(self): self.client = ContractingClient(driver=self.driver) self.client.raw_driver.flush_full() self.client.submission_contract = None contract = self.client.raw_driver.get_contract('submission') self.assertIsNone(contract) self.assertIsNone(self.client.submission_contract) self.driver.set_contract(name='submission', code=self.submission_contract_file) self.driver.commit() self.client.set_submission_contract() contract = self.client.raw_driver.get_contract('submission') self.assertIsNotNone(contract) self.assertIsNotNone(self.client.submission_contract) def test_set_submission_contract__no_contract_provided_or_found_raises_AssertionError(self): self.client = ContractingClient(driver=self.driver) self.client.raw_driver.flush_full() self.client.submission_filename = None with self.assertRaises(AssertionError): self.client.set_submission_contract() def test_submit__raises_AssertionError_if_no_submission_contract_set(self): self.client = ContractingClient(submission_filename=None, driver=self.driver) with self.assertRaises(AssertionError): self.client.submit(f="") ================================================ FILE: tests/unit/test_client_keys_prefix.py ================================================ import unittest from contracting.client import ContractingClient class TestClientKeysPrefix(unittest.TestCase): def setUp(self): self.client = ContractingClient() self.client.flush() # Submit two dummy contracts with similar prefixes code_a = """ @export def f(): pass """ code_b = code_a self.client.submit(code_a, name='abc') self.client.submit(code_b, name='abc2') # Write distinct state under each contract to detect leakage self.client.set_var('abc', '__code__', value='X') self.client.set_var('abc2', '__code__', value='Y') # Also add a hash-like key for both self.client.set_var('abc', 'h', arguments=['k'], value=1) self.client.set_var('abc2', 'h', arguments=['k'], value=2) def tearDown(self): self.client.flush() def test_keys_scoped_to_exact_contract(self): abc = self.client.get_contract('abc') keys = abc.keys() # Ensure keys from abc2 are not present self.assertTrue(all(not k.startswith('abc2.') for k in keys)) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/unit/test_context_data_struct.py ================================================ from unittest import TestCase from contracting.execution.runtime import Context class TestContext(TestCase): def test_get_state(self): c = Context(base_state={ 'caller': 'stu', 'signer': 'stu', 'this': 'contract', 'owner': None }) self.assertEqual(c._get_state(), c._base_state) def test_get_state_after_added_state(self): c = Context(base_state={ 'caller': 'stu', 'signer': 'stu', 'this': 'contract', 'owner': None }) new_state = { 'caller': 'stuart', 'signer': 'stuart', 'this': 'contracts', 'owner': 123 } c._add_state(new_state) self.assertEqual(c._get_state(), new_state) def test_pop_state_doesnt_fail_if_none_added(self): c = Context(base_state={ 'caller': 'stu', 'signer': 'stu', 'this': 'contract', 'owner': None }) c._pop_state() self.assertEqual(c._get_state(), c._base_state) def test_pop_state_removes_last_state(self): c = Context(base_state={ 'caller': 'stu', 'signer': 'stu', 'this': 'contract', 'owner': None }) new_state = { 'caller': 'stuart', 'signer': 'stuart', 'this': 'contracts', 'owner': 123 } c._add_state(new_state) self.assertEqual(c._get_state(), new_state) c._pop_state() self.assertEqual(c._get_state(), c._base_state) def test_add_state_doesnt_work_if_this_is_same(self): c = Context(base_state={ 'caller': 'stu', 'signer': 'stu', 'this': 'contract', 'owner': None }) new_state = { 'caller': 'stuart', 'signer': 'stuart', 'this': 'contract', 'owner': 123 } c._add_state(new_state) self.assertEqual(c._get_state(), c._base_state) def test_properties_read(self): c = Context(base_state={ 'caller': 'stu', 'signer': 'stu', 'this': 'contract', 'owner': None }) self.assertEqual(c._base_state['this'], c.this) self.assertEqual(c._base_state['caller'], c.caller) self.assertEqual(c._base_state['signer'], c.signer) self.assertEqual(c._base_state['owner'], c.owner) def test_properties_cant_be_written(self): c = Context(base_state={ 'caller': 'stu', 'signer': 'stu', 'this': 'contract', 'owner': None }) with self.assertRaises(Exception): c.this = 1 with self.assertRaises(Exception): c.caller = 1 with self.assertRaises(Exception): c.signer = 1 with self.assertRaises(Exception): c.owner = 1 ================================================ FILE: tests/unit/test_datetime.py ================================================ from unittest import TestCase from contracting.stdlib.bridge.time import Datetime, Timedelta from datetime import datetime as dt from datetime import timedelta class TestDatetime(TestCase): def test_datetime_variables_set(self): now = dt.now() d = Datetime(now.year, now.month, now.day) self.assertEqual(0, d.microsecond) self.assertEqual(0, d.second) self.assertEqual(0, d.minute) self.assertEqual(0, d.hour) self.assertEqual(now.day, d.day) self.assertEqual(now.month, d.month) self.assertEqual(now.year, d.year) ### # == ### def test_datetime_eq_true(self): now = dt.now() d = Datetime(now.year, now.month, now.day) e = Datetime(now.year, now.month, now.day) self.assertTrue(d == e) def test_datetime_eq_false(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertFalse(d == e) ### # != ### def test_datetime_ne_false(self): now = dt.now() d = Datetime(now.year, now.month, now.day) e = Datetime(now.year, now.month, now.day) self.assertFalse(d != e) def test_datetime_ne_true(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertTrue(d != e) ### # < ### def test_datetime_lt_true(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertTrue(d < e) def test_datetime_lt_false(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertFalse(e < d) ### # > ### def test_datetime_gt_true(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertTrue(e > d) def test_datetime_gt_false(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertFalse(d > e) ### # >= ### def test_datetime_ge_true_g(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertTrue(e >= d) def test_datetime_ge_true_eq(self): now = dt.now() d = Datetime(now.year, now.month, now.day) e = Datetime(now.year, now.month, now.day) self.assertTrue(d >= e) def test_datetime_ge_false_g(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertFalse(d >= e) ### # <= ### def test_datetime_le_true(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertTrue(d <= e) def test_datetime_le_true_eq(self): now = dt.now() d = Datetime(now.year, now.month, now.day) e = Datetime(now.year, now.month, now.day) self.assertTrue(d <= e) def test_datetime_le_false(self): now = dt.now() d = Datetime(now.year, now.month, now.day) then = now + timedelta(days=1) e = Datetime(then.year, then.month, then.day) self.assertFalse(e <= d) def test_datetime_subtraction_to_proper_timedelta(self): d = Datetime(2019, 1, 1) e = Datetime(2018, 1, 1) self.assertEqual((d - e), Timedelta(days=365)) def test_datetime_strptime(self): d = dt(2019, 1, 1) self.assertEqual(str(Datetime.strptime(str(d), '%Y-%m-%d %H:%M:%S')), str(d)) def test_datetime_strptime_invalid_format(self): d = dt(2019, 1, 1) with self.assertRaises(ValueError): Datetime.strptime(str(d), '%Y-%m-%d') def test_datetime_strptime_invalid_date(self): with self.assertRaises(ValueError): Datetime.strptime('2019-02-30 12:00:00', '%Y-%m-%d %H:%M:%S') def test_datetime_strptime_invalid_date_format(self): with self.assertRaises(ValueError): Datetime.strptime('2019-02-30 12:00:00', '%Y-%m-%d %H:%M:%S') def test_datetime_returns_correct_datetime_cls(self): d = dt(2019, 1, 1) self.assertEqual(Datetime.strptime(str(d), '%Y-%m-%d %H:%M:%S'), Datetime(2019, 1, 1)) ================================================ FILE: tests/unit/test_decimal.py ================================================ from decimal import Decimal import decimal import math from contracting.stdlib.bridge.decimal import ContractingDecimal, fix_precision, MAX_DECIMAL, neg_sci_not from unittest import TestCase import unittest class TestDecimal(TestCase): def test_init(self): ContractingDecimal('1.1') def test_init_float(self): ContractingDecimal(1.2) def test_init_int(self): ContractingDecimal(1) def test_bool_true(self): self.assertTrue(ContractingDecimal(1)) def test_bool_false(self): self.assertFalse(ContractingDecimal(0)) def test_eq_whole_numbers(self): self.assertEqual(ContractingDecimal(1), ContractingDecimal(1)) def test_eq_floats(self): self.assertEqual(ContractingDecimal(1.234), ContractingDecimal(1.234)) def test_lt(self): self.assertLess(ContractingDecimal(1), ContractingDecimal(2)) self.assertLess(ContractingDecimal(1.1), ContractingDecimal(2.2)) def test_lte(self): self.assertLessEqual(ContractingDecimal(1), ContractingDecimal(2)) self.assertLessEqual(ContractingDecimal(1.1), ContractingDecimal(2.2)) self.assertLessEqual(ContractingDecimal(2.2), ContractingDecimal(2.2)) def test_gt(self): self.assertGreater(ContractingDecimal(10), ContractingDecimal(2)) self.assertGreater(ContractingDecimal(10.1), ContractingDecimal(2.2)) def test_gte(self): self.assertGreaterEqual(ContractingDecimal(10), ContractingDecimal(2)) self.assertGreaterEqual(ContractingDecimal(10.1), ContractingDecimal(2.2)) self.assertGreaterEqual(ContractingDecimal(2.2), ContractingDecimal(2.2)) def test_str(self): f = ContractingDecimal(1.23445) self.assertEqual(str(f), '1.23445') def test_neg(self): self.assertEqual(-ContractingDecimal(1), ContractingDecimal(-1)) def test_pos(self): self.assertEqual(+ContractingDecimal(1), ContractingDecimal(1)) def test_other_equality(self): self.assertEqual(ContractingDecimal(1), 1) self.assertEqual(ContractingDecimal(1), 1.0) def test_abs(self): self.assertEqual(abs(ContractingDecimal(1)), 1) self.assertEqual(abs(ContractingDecimal(-1)), 1) def test_add(self): self.assertEqual(ContractingDecimal(1) + ContractingDecimal(1), 2) self.assertEqual(ContractingDecimal(1) + ContractingDecimal(10), 11) self.assertEqual(ContractingDecimal(1.23456) + ContractingDecimal(6.54321), ContractingDecimal(7.77777)) def test_arbitrarily_large_number(self): a = ContractingDecimal('38327950288419716939937510.582097494459230781640628620899') b = ContractingDecimal('67523846748184676694051320.005681271452635608277857713427') c = ContractingDecimal('105851797036604393633988830.587778765911866389918486334326') self.assertEqual(a + b, c) def test_zero_equality(self): self.assertEqual(ContractingDecimal(0), 0) def test_sub(self): self.assertEqual(ContractingDecimal(1) - ContractingDecimal(1), 0) self.assertEqual(ContractingDecimal(1) - ContractingDecimal(10), -9) self.assertEqual(ContractingDecimal(1.23456) - ContractingDecimal(6.54321), ContractingDecimal(-5.30865)) def test_add_negs(self): self.assertEqual(ContractingDecimal(1) + ContractingDecimal(-1), 0) def test_radd(self): self.assertEqual(1 + ContractingDecimal(1), 2) self.assertEqual(1 + ContractingDecimal(10), 11) self.assertEqual(1.23456 + ContractingDecimal(6.54321), ContractingDecimal(7.77777)) def test_rsub(self): self.assertEqual(1 - ContractingDecimal(1), 0) self.assertEqual(1 - ContractingDecimal(10), -9) self.assertEqual(1.23456 - ContractingDecimal(6.54321), ContractingDecimal(-5.30865)) def test_mul(self): self.assertEqual(ContractingDecimal(5) * ContractingDecimal(42), 210) self.assertEqual(ContractingDecimal(0) * ContractingDecimal(100), 0) self.assertEqual(ContractingDecimal(-5) * ContractingDecimal(42), -210) self.assertEqual(ContractingDecimal(5.1234) * ContractingDecimal(2.3451), ContractingDecimal('12.01488534')) def test_rmul(self): self.assertEqual(5 * ContractingDecimal(42), 210) self.assertEqual(0 * ContractingDecimal(100), 0) self.assertEqual(-5 * ContractingDecimal(42), -210) self.assertEqual(5.1234 * ContractingDecimal(2.3451), ContractingDecimal('12.01488534')) def test_div(self): self.assertEqual((ContractingDecimal(1) / ContractingDecimal(3)), ContractingDecimal('0.333333333333333333333333333333')) self.assertEqual(ContractingDecimal(3) / ContractingDecimal(1), 3) def test_div_large_decimals(self): a = '0.78164062862089986280348253421170' b = '0.53642401735797937714409102114816' c = ContractingDecimal(a) / ContractingDecimal(b) print(c) def test_fix_precision_cuts_too_low(self): d = Decimal('1.123456789012345678901234567890123') e = Decimal('1.12345678901234567890123456789') self.assertEqual(fix_precision(d), e) def test_fix_precision_cuts_too_high(self): e = Decimal('123456789012345678901234567890') self.assertEqual(fix_precision(e), MAX_DECIMAL) def test_fix_precision_doesnt_cut_high(self): e = Decimal('12345678901234567890123456789') self.assertEqual(fix_precision(e), e) def test_fix_precision_cuts_all_decimals_if_too_high(self): e = Decimal('123456789012345678901234567890.123456') self.assertEqual(fix_precision(e), MAX_DECIMAL) def test_fix_precision_cuts_decimals_if_high_but_not_too_high(self): e = Decimal('12345678901234567890123456789.123456789012345678901234567890') f = Decimal('12345678901234567890123456789.12345678901234567890123456789') self.assertEqual(fix_precision(e), f) def test_contracting_decimal_can_round(self): s = '12345678901234567890123456789.123456789012345678901234567890' self.assertEqual(round(Decimal(s), 10), round(ContractingDecimal(s), 10)) def test_sci_not_whole_number(self): s = '2e-5' expected = '0.00002' self.assertEqual(neg_sci_not(s), expected) def test_sci_not_decimal(self): s = '2.2e-7' expected = '0.00000022' self.assertEqual(neg_sci_not(s), expected) def test_sci_not_e0(self): s = '2e-0' expected = '2' self.assertEqual(neg_sci_not(s), expected) def test_sci_not_extra_precision(self): s = '20e-5' expected = '20e-5' self.assertEqual(neg_sci_not(s), expected) if "__main__" == __name__: unittest.main() ================================================ FILE: tests/unit/test_driver_tombstones.py ================================================ import unittest from contracting.storage.driver import Driver class TestDriverTombstones(unittest.TestCase): def setUp(self): self.driver = Driver(bypass_cache=False) self.driver.flush_full() def tearDown(self): self.driver.flush_full() def test_items_excludes_pending_deletes(self): key = 'con.hash:subkey' self.driver.set(key, 'v1') # Persist first so value exists on disk self.driver.commit() # Now set tombstone in the same transaction context self.driver.set(key, None) items = self.driver.items(prefix='con.hash:') keys = self.driver.keys(prefix='con.hash:') values = self.driver.values(prefix='con.hash:') self.assertNotIn(key, items) self.assertNotIn(key, keys) self.assertEqual(values, []) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/unit/test_encode.py ================================================ from unittest import TestCase from contracting.storage.encoder import encode, decode, safe_repr, convert_dict, MONGO_MAX_INT, MONGO_MIN_INT from contracting.stdlib.bridge.time import Datetime, Timedelta from datetime import datetime from contracting.stdlib.bridge.decimal import ContractingDecimal class TestEncode(TestCase): def test_int_to_bytes(self): i = 1000 b = '1000' self.assertEqual(encode(i), b) def test_str_to_bytes(self): s = 'hello' b = '"hello"' self.assertEqual(encode(s), b) def test_dec_to_bytes(self): d = 1.098409840984 b = '1.098409840984' self.assertEqual(encode(d), b) def test_decode_bytes_to_int(self): b = '1234' i = 1234 self.assertEqual(decode(b), i) def test_decode_bytes_to_str(self): b = '"howdy"' s = 'howdy' self.assertEqual(decode(b), s) def test_decode_bytes_to_dec(self): b = '{"__fixed__":"0.0044997618965276"}' d = ContractingDecimal('0.0044997618965276') # _d is the actual Decimal object included in the wrapped stdlib ContractingDecimal self.assertEqual(decode(b)._d, d) def test_decode_failure(self): b = b'xwow' self.assertIsNone(decode(b)) def test_date_encode(self): d = Datetime(2019, 1, 1) _d = encode(d) self.assertEqual(_d, '{"__time__":[2019,1,1,0,0,0,0]}') def test_date_decode(self): _d = '{"__time__": [2019, 1, 1, 0, 0, 0, 0]}' d = decode(_d) self.assertEqual(Datetime(2019, 1, 1), d) def test_timedelta_encode(self): t = Timedelta(weeks=1, days=1) _t = encode(t) self.assertEqual('{"__delta__":[8,0]}', _t) def test_timedelta_decode(self): _t = '{"__delta__": [8, 0]}' t = decode(_t) self.assertEqual(t, Timedelta(weeks=1, days=1)) def test_int_encode(self): i = 10 self.assertEqual(str(i), encode(i)) def test_int_decode(self): i = '10' self.assertEqual(10, decode(i)) def test_bigint_encode(self): si = MONGO_MIN_INT - 1 bi = MONGO_MAX_INT + 1 self.assertEqual('{"__big_int__":"' + str(bi) + '"}', encode(bi)) self.assertEqual('{"__big_int__":"' + str(si) + '"}', encode(si)) def test_bigint_decode(self): _bi = '{"__big_int__":' + str(MONGO_MAX_INT+1) + '}' self.assertEqual(decode(_bi), MONGO_MAX_INT+1) def test_encode_ints_nested_list(self): d = {'lists':[ {'i': 123,'bi': MONGO_MAX_INT} ]} expected = '{"lists":[{"i":123,"bi":{"__big_int__":"' + str(MONGO_MAX_INT) + '"}}]}' self.assertEqual(encode(d), expected) def test_encode_dict_with_list_containing_different_types(self): d = {'lists':[ {'i': 123,'bi': MONGO_MAX_INT}, 'hello' ]} expected = '{"lists":[{"i":123,"bi":{"__big_int__":"' + str(MONGO_MAX_INT) + '"}},"hello"]}' self.assertEqual(encode(d), expected) def test_encode_ints_nested_dict(self): d = {'d': {'bi': MONGO_MAX_INT, 'str': 'hello'}} expected = '{"d":{"bi":{"__big_int__":"' + str(MONGO_MAX_INT) + '"},"str":"hello"}}' self.assertEqual(encode(d), expected) def test_safe_repr_non_object(self): a = str(1) b = safe_repr(1) self.assertEqual(a, b) def test_safe_repr_arbitrary_object(self): class Object: pass a = Object() b = Object() self.assertEqual(safe_repr(a), safe_repr(b)) def test_safe_repr_decimal_object(self): a = Timedelta(weeks=1, days=1) b = Timedelta(weeks=1, days=1) self.assertEqual(safe_repr(a), safe_repr(b)) def test_safe_repr_decimal_object_different_not_equal(self): a = Timedelta(weeks=1, days=1) b = Timedelta(weeks=2, days=1) self.assertNotEqual(safe_repr(a), safe_repr(b)) def test_safe_repr_assertion_error_string(self): a = AssertionError('Hello') b = AssertionError('Hello') self.assertEqual(safe_repr(a), safe_repr(b)) def test_contracting_decimal(self): c = ContractingDecimal(a=123.456) b = '{"__fixed__":"123.456"}' self.assertEqual(encode(c), b) def test_decode_fixed_trailing_doesnt_get_rid_of_zeros_properly(self): b = '{"__fixed__":"1.10"}' d = decode(b) def test_encoding_fixed_trailing_zeros(self): b = ContractingDecimal('123.000000') e = encode(b) print(e) def test_convert_returns_normal_dict(self): d = { 1: 2, "a": "b" } d2 = convert_dict(d) self.assertEqual(d, d2) def test_convert_bigint(self): d = {'bigint': {'__big_int__': str(2**65)}} expected = {'bigint': 2**65} self.assertDictEqual(convert_dict(d), expected) def test_convert_contracting_decimal(self): d = { 'kwargs': { '__fixed__': '0.1234' } } expected = { 'kwargs': ContractingDecimal('0.1234') } self.assertEqual(expected, convert_dict(d)) def test_convert_contracting_datetime(self): d = { 'kwargs': { "__time__": [ 2021, 4, 29, 21, 30, 54, 0 ] } } expected = { 'kwargs': Datetime(2021, 4, 29, 21, 30, 54, 0) } self.assertEqual(expected, convert_dict(d)) def test_convert_contracting_timedelta(self): d = { 'kwargs': { "__delta__": [8, 0] } } expected = { 'kwargs': Timedelta(days=8, seconds=0) } self.assertEqual(expected, convert_dict(d)) def test_convert_contracting_bytes(self): d = { 'kwargs': { "__bytes__": "123456" } } expected = { 'kwargs': b'\x124V' } self.assertEqual(expected, convert_dict(d)) def test_multiple_conversions(self): d = { 'kwargs': { '__fixed__': '0.1234' }, 'kwargs2': { "__time__": [ 2021, 4, 29, 21, 30, 54, 0 ] }, 'kwargs3': { "__delta__": [8, 0] }, 'kwargs4': { "__bytes__": "123456" } } expected = { 'kwargs': ContractingDecimal('0.1234'), 'kwargs2': Datetime(2021, 4, 29, 21, 30, 54, 0), 'kwargs3': Timedelta(days=8, seconds=0), 'kwargs4': b'\x124V', } self.assertEqual(expected, convert_dict(d)) def test_nested_dictionaries(self): d = { 'kwargs': { '__fixed__': '0.1234' }, 'thing': { 'thing2': { "__time__": [ 2021, 4, 29, 21, 30, 54, 0 ] }, } } expected = { 'kwargs': ContractingDecimal('0.1234'), 'thing': { 'thing2': Datetime(2021, 4, 29, 21, 30, 54, 0), } } d2 = convert_dict(d) self.assertEqual(expected, d2) def test_lists(self): d = { 'kwargs': [ { '__fixed__': '0.1234' }, { '__fixed__': '0.1235' }, { '__fixed__': '0.1236' }, { '__fixed__': '0.1237' }, { '__fixed__': '0.1238' }, ] } expected = { 'kwargs': [ ContractingDecimal('0.1234'), ContractingDecimal('0.1235'), ContractingDecimal('0.1236'), ContractingDecimal('0.1237'), ContractingDecimal('0.1238'), ] } d2 = convert_dict(d) self.assertEqual(expected, d2) ================================================ FILE: tests/unit/test_imports_stdlib.py ================================================ from unittest import TestCase from contracting.stdlib.bridge import imports from types import ModuleType from contracting.storage.orm import Hash, Variable import os class TestImports(TestCase): def setUp(self): scope = {} self.script_dir = os.path.dirname(os.path.abspath(__file__)) compiled_token_file_path = os.path.join(self.script_dir, "precompiled", "compiled_token.py") with open(compiled_token_file_path) as f: code = f.read() exec(code, scope) m = ModuleType('testing') vars(m).update(scope) del vars(m)['__builtins__'] self.module = m def test_func_correct_type(self): def sup(x, y): return x + y s = imports.Func(name='sup', args=('x', 'y')) self.assertTrue(s.is_of(sup)) def test_func_incorrect_name(self): def sup(x, y): return x + y s = imports.Func(name='not_much', args=('x', 'y')) self.assertFalse(s.is_of(sup)) def test_func_incorrect_args(self): def sup(a, b): return a + b s = imports.Func(name='sup', args=('x', 'y')) self.assertFalse(s.is_of(sup)) def test_func_correct_with_kwargs(self): def sup(x=100, y=200): return x + y s = imports.Func(name='sup', args=('x', 'y')) self.assertTrue(s.is_of(sup)) def test_func_correct_with_annotations(self): def sup(x: int, y: int): return x + y s = imports.Func(name='sup', args=('x', 'y')) self.assertTrue(s.is_of(sup)) def test_func_correct_with_kwargs_and_annotations(self): def sup(x: int=100, y: int=100): return x + y s = imports.Func(name='sup', args=('x', 'y')) self.assertTrue(s.is_of(sup)) def test_func_correct_private(self): def __sup(a, b): return a + b s = imports.Func(name='sup', args=('a', 'b'), private=True) self.assertTrue(s.is_of(__sup)) def test_func_false_private(self): def __sup(a, b): return a + b s = imports.Func(name='sup', args=('x', 'y'), private=True) self.assertFalse(s.is_of(__sup)) def test_var_fails_if_type_not_of_datum(self): with self.assertRaises(AssertionError): imports.Var('blah', str) def test_enforce_interface_works_all_public_funcs(self): interface = [ imports.Func('transfer', args=('amount', 'to')), imports.Func('balance_of', args=('account',)), imports.Func('total_supply'), imports.Func('allowance', args=('owner', 'spender')), imports.Func('approve', args=('amount', 'to')), imports.Func('transfer_from', args=('amount', 'to', 'main_account')) ] self.assertTrue(imports.enforce_interface(self.module, interface)) def test_enforce_interface_works_on_subset_funcs(self): interface = [ imports.Func('transfer', args=('amount', 'to')), imports.Func('balance_of', args=('account',)), imports.Func('total_supply'), imports.Func('allowance', args=('owner', 'spender')), imports.Func('transfer_from', args=('amount', 'to', 'main_account')) ] self.assertTrue(imports.enforce_interface(self.module, interface)) def test_enforce_interface_fails_on_wrong_funcs(self): interface = [ imports.Func('transfer', args=('amount', 'to')), imports.Func('balance_of', args=('account',)), imports.Func('spooky'), imports.Func('allowance', args=('owner', 'spender')), imports.Func('transfer_from', args=('amount', 'to', 'main_account')) ] self.assertFalse(imports.enforce_interface(self.module, interface)) def test_enforce_interface_on_resources(self): interface = [ imports.Var('supply', Variable), imports.Var('balances', Hash), ] self.assertTrue(imports.enforce_interface(self.module, interface)) def test_complete_enforcement(self): interface = [ imports.Func('transfer', args=('amount', 'to')), imports.Func('balance_of', args=('account',)), imports.Func('total_supply'), imports.Func('allowance', args=('owner', 'spender')), imports.Func('approve', args=('amount', 'to')), imports.Func('transfer_from', args=('amount', 'to', 'main_account')), imports.Var('supply', Variable), imports.Var('balances', Hash) ] self.assertTrue(imports.enforce_interface(self.module, interface)) def test_private_function_enforcement(self): interface = [ imports.Func('private_func', private=True), ] self.assertTrue(imports.enforce_interface(self.module, interface)) def test_complete_enforcement_with_private_func(self): interface = [ imports.Func('transfer', args=('amount', 'to')), imports.Func('balance_of', args=('account',)), imports.Func('total_supply'), imports.Func('allowance', args=('owner', 'spender')), imports.Func('approve', args=('amount', 'to')), imports.Func('private_func', private=True), imports.Func('transfer_from', args=('amount', 'to', 'main_account')), imports.Var('supply', Variable), imports.Var('balances', Hash) ] self.assertTrue(imports.enforce_interface(self.module, interface)) ================================================ FILE: tests/unit/test_linter.py ================================================ from unittest import TestCase from contracting.compilation.linter import Linter import ast from contracting.compilation.whitelists import ALLOWED_AST_TYPES, ALLOWED_ANNOTATION_TYPES, VIOLATION_TRIGGERS class TestLinter(TestCase): def setUp(self): self.l = Linter() def test_linter(self): # log = get_logger("TestSenecaLinter") data = ''' @export def a(): b = 10 return b ''' print("stu code: \n{}".format(data)) ptree = ast.parse(data) status = self.l.check(ptree) self.l.dump_violations() if status is None: print("Success!") else: print("Failed!") self.assertEqual(status, None) # TODO - Verify this tetst is working as expected. def test_good_ast_type(self): # Dictionary to handle special cases of AST nodes that require arguments special_cases = { ast.Index: lambda: ast.Index(value=ast.Str(s='test')), } for t in ALLOWED_AST_TYPES: if t in special_cases: _t = special_cases[t]() else: _t = t() self.l.ast_types(_t, 1) self.assertListEqual([], self.l._violations) def test_bad_ast_type(self): err = 'Line 1 : S1- Illegal contracting syntax type used : AsyncFunctionDef' t = ast.AsyncFunctionDef() self.l.ast_types(t, 1) self.l.dump_violations() self.assertMultiLineEqual(err, self.l._violations[0]) def test_not_system_variable(self): v = 'package' self.l.not_system_variable(v, 1) self.l.dump_violations() self.assertListEqual([], self.l._violations) def test_system_variable(self): v = '__package__' err = "Line 1 : S2- Illicit use of '_' before variable : __package__" self.l.not_system_variable(v, 1) self.l.dump_violations() self.assertMultiLineEqual(err, self.l._violations[0]) ''' Is blocking all underscore variables really the solution to preventing access to system variables? ''' def test_not_system_variable_ast(self): code = ''' @export def a(): __ruh_roh__ = 'shaggy' ''' err = "Line 4 : S2- Illicit use of '_' before variable : __ruh_roh__" c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertMultiLineEqual(err, self.l._violations[0]) def test_not_system_variable_ast_success(self): code = ''' @export def a(): ruh_roh = 'shaggy' ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk, None) self.assertListEqual([], self.l._violations) # def test_visit_async_func_def_fail(self): # err = 'Error : Illegal AST type: AsyncFunctionDef' # n = compilation.AsyncFunctionDef() # # self.l.visit_AsyncFunctionDef(n) # self.l.dump_violations() # self.assertMultiLineEqual(err, self.l._violations[0]) def test_visit_async_func_def_fail_code(self): code = ''' @export async def a(): ruh_roh = 'shaggy' def b(): c = 1 + 2 ''' err = 'Line 3: S7- Illicit use of Async functions' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(len(chk), 3) self.assertMultiLineEqual(err, self.l._violations[0]) #TODO failing # def test_visit_class_fail(self): # err = 'Error : Illegal AST type: ClassDef' # n = compilation.ClassDef() # self.l.visit_ClassDef(n) # self.l.dump_violations() # self.assertMultiLineEqual(err, self.l._violations[0]) def test_visit_class_fail_code(self): code = ''' class Scooby: pass ''' err = 'Line 2: S6- Illicit use of classes' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(len(chk), 3) self.assertMultiLineEqual(err, self.l._violations[0]) def test_visit_try_except_fail_code(self): code = ''' @export def try_it(): try: a = 0 except: a = 1 ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() print(self.l._violations) def test_accessing_system_vars(self): code = ''' @export def a(): ruh_roh = 'shaggy' ruh_roh.__dir__() ''' err = "Line 5 : S2- Illicit use of '_' before variable : __dir__" c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertMultiLineEqual(err, self.l._violations[0]) def test_accessing_attribute(self): code = ''' @export def a(): ruh_roh = 'shaggy' ruh_roh.capitalize() ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk, None) self.assertListEqual([], self.l._violations) #TODO failed test case def test_no_nested_imports(self): code = ''' @export def a(): import something ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk, ['Line 3: S3- Illicit use of Nested imports']) def test_no_nested_imports_works(self): code = ''' @export def a(): ruh_roh = 'shaggy' ruh_roh.capitalize() ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk, None) self.assertListEqual([], self.l._violations) def test_augassign(self): code = ''' @export def a(): b = 0 b += 1 ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk, None) self.assertListEqual([], self.l._violations) def test_no_import_from(self): code = ''' from something import a @export def a(): b = 0 b += 1 ''' err = 'Line 2: S4- ImportFrom compilation nodes not yet supported' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertMultiLineEqual(err, self.l._violations[0]) # disabling import check it would be done by compiler # def test_import_non_existent_contract(self): # code = ''' # import something # @export # def a(): # b = 0 # b += 1 # ''' # err = 'Line 2: S5- Contract not found in lib: something' # # c = compilation.parse(code) # self.l.check(c) # self.l.dump_violations() # self.assertMultiLineEqual(err, self.l._violations[0]) def test_final_checks_set_properly(self): code = ''' def a(): b = 0 b += 1 ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk, ['Line 0: S13- No valid contracting decorator found']) self.assertFalse(self.l._is_one_export) def test_collect_function_defs(self): code = ''' @export def a(): return 42 @export def b(): return 1000000 @export def x(): return 64 @export def y(): return 24 ''' c = ast.parse(code) self.l._collect_function_defs(c) self.l.dump_violations() self.assertEqual(self.l._functions, ['a', 'b', 'x', 'y']) def test_assignment_of_import(self): code = ''' import import_this @export def test(): a = import_this.howdy() a -= 1000 return a ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() def test_good_orm_initialization(self): code = ''' v = Variable() @export def set(i: int): v.set(i) ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(self.l._violations, []) def test_bad_orm_initialization(self): code = ''' v = Variable(contract='currency', name='stus_balance') @export def set(i: int): v.set(i) ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(chk[0], 'Line 2: S11- Illicit keyword overloading for ORM assignments') def test_multi_targets_orm_fails(self): code = ''' v, x = Variable() @export def set(i): v.set(i) ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() print(chk) self.assertEqual(chk[0], 'Line 2: S12- Multiple targets to ORM definition detected') def test_multi_decorator_fails(self): code = ''' @construct @export def kaboom(): print('i like to break things') ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk[0], 'Line 4: S10- Illicit use of multiple decorators: Detected: 2 MAX limit: 1') def test_invalid_decorator_fails(self): code = ''' @contracting_invalid def wont_work(): print('i hope') ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(chk[1], 'Line 3: S8- Invalid decorator used: valid list: contracting_invalid') def test_multiple_constructors_fails(self): code = ''' @construct def seed_1(): pass @construct def seed_1(): pass @export def seed_5(): pass ''' c = ast.parse(code) chk = self.l.check(c) self.l.dump_violations() self.assertEqual(len(chk),1) self.assertEqual(self.l._violations, ['Line 7: S9- Multiple use of constructors detected']) def test_function_str_annotation(self): code = ''' @export def greeting(name: str): return 'Hello ' + name ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(chk, None) def test_function_dict_annotation(self): code = ''' @export def greeting(name: dict): return 'Hello ' + name ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(chk, None) def test_function_bad_annotation(self): code = ''' @export def greeting(name: mytype): return 'Hello ' + name ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(chk, ['Line 3 : S16- Illegal argument annotation used : mytype']) def test_function_none_annotation(self): code = ''' @export def greeting(name): return 'Hello ' + name ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(chk, ['Line 3 : S17- No valid argument annotation found']) def test_none_return_annotation(self): code = ''' @export def greeting(name: str): return 'Hello ' + name ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(self.l._violations, []) def test_contract_annotation(self): code =''' @export def transfer(amount, to): sender = ctx.caller assert balances[sender] >= amount, 'Not enough coins to send!' balances[sender] -= amount balances[to] += amount def greeting(name): return 'Hello ' + name ''' c = ast.parse(code) chk = self.l.check(c) self.assertEqual(chk, ['Line 3 : S17- No valid argument annotation found']) ## ANNOTATIONS ARE OKAY # def test_function_return_annotation(self): # code = ''' #@export #def greeting(name: str) -> str: # return 'Hello ' + name #''' # c = ast.parse(code) # chk = self.l.check(c) # # self.assertEqual(['Line 2 : S18- Illegal use of return annotation : str'], chk) def test_violations_sorted_by_line_number(self): code = ''' class Illegal1: # Line 2 pass @export def good_function(): pass from something import stuff # Line 8 class Illegal2: # Line 10 pass ''' c = ast.parse(code) violations = self.l.check(c) self.assertIsNotNone(violations) self.assertEqual(len(violations), 5) line_numbers = [int(v.split(':')[0].split()[1]) for v in violations] self.assertEqual(line_numbers, sorted(line_numbers)) self.assertTrue(violations[0].startswith('Line 2')) # No export decorator self.assertTrue(violations[1].startswith('Line 2')) # First class definition self.assertTrue(violations[2].startswith('Line 9')) # ImportFrom self.assertTrue(violations[3].startswith('Line 11')) # Second class definition self.assertTrue(violations[4].startswith('Line 11')) # Second class definition ================================================ FILE: tests/unit/test_module.py ================================================ from unittest import TestCase from contracting.execution.module import * from contracting.storage.driver import Driver import types import glob import os class TestDatabase(TestCase): def setUp(self): self.d = Driver() self.d.flush_full() def tearDown(self): self.d.flush_full() def test_push_and_get_contract(self): code = 'a = 123' name = 'test' self.d.set_contract(name, code) _code = self.d.get_contract(name) self.assertEqual(code, _code, 'Pushing and getting contracts is not working.') def test_flush(self): code = 'a = 123' name = 'test' self.d.set_contract(name, code) self.d.commit() self.d.flush_full() self.assertIsNone(self.d.get_contract(name)) class TestDatabaseLoader(TestCase): def setUp(self): self.dl = DatabaseLoader() def test_init(self): self.assertTrue(isinstance(self.dl.d, Driver), 'self.d is not a Database object.') def test_create_module(self): self.assertEqual(self.dl.create_module(None), None, 'self.create_module should return None') def test_exec_module(self): module = types.ModuleType('test') self.dl.d.set_contract('test', 'b = 1337') self.dl.exec_module(module) self.dl.d.flush_full() self.assertEqual(module.b, 1337) def test_exec_module_nonattribute(self): module = types.ModuleType('test') self.dl.d.set_contract('test', 'b = 1337') self.dl.exec_module(module) self.dl.d.flush_full() with self.assertRaises(AttributeError): module.a def test_module_representation(self): module = types.ModuleType('howdy') self.assertEqual(self.dl.module_repr(module), "") class TestInstallLoader(TestCase): def test_install_loader(self): uninstall_database_loader() self.assertNotIn(DatabaseFinder, sys.meta_path) install_database_loader() self.assertIn(DatabaseFinder, sys.meta_path) uninstall_database_loader() self.assertNotIn(DatabaseFinder, sys.meta_path) def test_integration_and_importing(self): dl = DatabaseLoader() dl.d.set_contract('testing', 'a = 1234567890') dl.d.commit() install_database_loader() import testing #dl.d.flush() self.assertEqual(testing.a, 1234567890) driver = Driver() class TestModuleLoadingIntegration(TestCase): def setUp(self): sys.meta_path.append(DatabaseFinder) driver.flush_full() self.script_dir = os.path.dirname(os.path.abspath(__file__)) contracts = glob.glob(os.path.join(self.script_dir, "test_sys_contracts", "*.py")) for contract in contracts: name = contract.split('/')[-1] name = name.split('.')[0] with open(contract) as f: code = f.read() driver.set_contract(name=name, code=code) driver.commit() def tearDown(self): sys.meta_path.remove(DatabaseFinder) driver.flush_full() def test_get_code_string(self): ctx = types.ModuleType('ctx') code = '''import module1 print("now i can run my functions!") ''' exec(code, vars(ctx)) print('ok do it again') exec(code, vars(ctx)) ================================================ FILE: tests/unit/test_new_driver.py ================================================ import unittest import os from shutil import rmtree from datetime import datetime from contracting.storage.driver import Driver class TestDriver(unittest.TestCase): def setUp(self): # Setup a fresh instance of Driver and ensure a clean storage environment self.driver = Driver(bypass_cache=False) self.driver.flush_full() def tearDown(self): # Clean up any state that might affect other tests self.driver.flush_full() def test_set_and_get(self): key = 'test_key' value = 'test_value' self.driver.set(key, value) self.driver.commit() retrieved_value = self.driver.get(key) self.assertEqual(retrieved_value, value) def test_find(self): key = 'test_key' value = 'test_value' self.driver.set(key, value) self.driver.commit() retrieved_value = self.driver.find(key) self.assertEqual(retrieved_value, value) def test_keys_from_disk(self): key1 = 'test_key1' key2 = 'test_key2' value = 'test_value' self.driver.set(key1, value) self.driver.set(key2, value) self.driver.commit() keys = self.driver.keys_from_disk() self.assertIn(key1, keys) self.assertIn(key2, keys) def test_iter_from_disk(self): key1 = 'test_key1' key2 = 'test_key2' prefix_key = 'prefix_key' value = 'test_value' self.driver.set(key1, value) self.driver.set(key2, value) self.driver.set(prefix_key, value) self.driver.commit() keys = self.driver.iter_from_disk(prefix=prefix_key) self.assertIn(prefix_key, keys) self.assertNotIn(key1, keys) self.assertNotIn(key2, keys) def test_items(self): prefix_key = 'prefix_key' value = 'test_value' self.driver.set(prefix_key, value) self.driver.commit() items = self.driver.items(prefix=prefix_key) self.assertIn(prefix_key, items) self.assertEqual(items[prefix_key], value) def test_delete_key_from_disk(self): key = 'test_key' value = 'test_value' self.driver.set(key, value) self.driver.commit() self.driver.delete_key_from_disk(key) retrieved_value = self.driver.value_from_disk(key) self.assertIsNone(retrieved_value) def test_flush_cache(self): key = 'test_key' value = 'test_value' self.driver.set(key, value) self.driver.flush_cache() self.assertFalse(self.driver.pending_writes) def test_flush_disk(self): key = 'test_key' value = 'test_value' self.driver.set(key, value) self.driver.commit() self.driver.flush_disk() self.assertFalse(self.driver.get(key)) def test_commit(self): key = 'test_key' value = 'test_value' self.driver.set(key, value) self.driver.commit() retrieved_value = self.driver.get(key) self.assertEqual(retrieved_value, value) def test_get_all_contract_state(self): key = 'contract.key' value = 'contract_value' self.driver.set(key, value) self.driver.commit() contract_state = self.driver.get_all_contract_state() self.assertIn(key, contract_state) self.assertEqual(contract_state[key], value) def test_transaction_writes(self): key = 'test_key' value = 'test_value' self.driver.set(key, value, is_txn_write=True) # self.driver.commit() transaction_writes = self.driver.transaction_writes self.assertIn(key, transaction_writes) self.assertEqual(transaction_writes[key], value) def test_clear_transaction_writes(self): key = 'test_key' value = 'test_value' self.driver.set(key, value) # self.driver.commit() self.driver.clear_transaction_writes() transaction_writes = self.driver.transaction_writes self.assertNotIn(key, transaction_writes) def test_get_run_state(self): # We can't test this function here since we are not running a real blockchain. pass if __name__ == '__main__': unittest.main() ================================================ FILE: tests/unit/test_orm.py ================================================ from unittest import TestCase from contracting import constants from contracting.storage.driver import Driver from contracting.storage.orm import Datum, Variable, ForeignHash, ForeignVariable, Hash, LogEvent from contracting.stdlib.bridge.decimal import ContractingDecimal # from contracting.stdlib.env import gather # Variable = gather()['Variable'] # Hash = gather()['Hash'] # ForeignVariable = gather()['ForeignVariable'] # ForeignHash = gather()['ForeignHash'] driver = Driver() class TestDatum(TestCase): def setUp(self): driver.flush_full() def tearDown(self): driver.flush_full() def test_init(self): d = Datum('stustu', 'test', driver) self.assertEqual(d._key, driver.make_key('stustu', 'test')) class TestVariable(TestCase): def setUp(self): driver.flush_full() def tearDown(self): #_driver.flush_full() pass def test_set(self): contract = 'stustu' name = 'balance' delimiter = constants.INDEX_SEPARATOR raw_key = '{}{}{}'.format(contract, delimiter, name) v = Variable(contract, name, driver=driver) v.set(1000) self.assertEqual(driver.get(raw_key), 1000) def test_get(self): contract = 'stustu' name = 'balance' delimiter = constants.INDEX_SEPARATOR raw_key = '{}{}{}'.format(contract, delimiter, name) driver.set(raw_key, 1234) v = Variable(contract, name, driver=driver) _v = v.get() self.assertEqual(_v, 1234) def test_set_get(self): contract = 'stustu' name = 'balance' v = Variable(contract, name, driver=driver) v.set(1000) _v = v.get() self.assertEqual(_v, 1000) def test_default_value(self): contract = 'stustu' name = 'balance' v = Variable(contract, name, driver=driver, default_value=999) self.assertEqual(v.get(), 999) v.set(123) self.assertEqual(v.get(), 123) v.set(None) self.assertEqual(v.get(), 999) def test_mutable_default_is_copied(self): contract = 'stustu' name = 'cfg' v = Variable(contract, name, driver=driver, default_value={'a': []}) first = v.get() first['a'].append(1) second = v.get() self.assertEqual(second, {'a': []}) class TestHash(TestCase): def setUp(self): driver.flush_full() def tearDown(self): driver.flush_full() def test_set(self): contract = 'stustu' name = 'balance' delimiter = constants.INDEX_SEPARATOR raw_key_1 = '{}{}{}'.format(contract, delimiter, name) raw_key_1 += ':stu' h = Hash(contract, name, driver=driver) h._set('stu', 1234) driver.commit() self.assertEqual(driver.get(raw_key_1), 1234) def test_get(self): contract = 'stustu' name = 'balance' delimiter = constants.INDEX_SEPARATOR raw_key_1 = '{}{}{}'.format(contract, delimiter, name) raw_key_1 += ':stu' driver.set(raw_key_1, 1234) h = Hash(contract, name, driver=driver) self.assertEqual(h._get('stu'), 1234) def test_set_get(self): contract = 'stustu' name = 'balance' h = Hash(contract, name, driver=driver) h._set('stu', 1234) _h = h._get('stu') self.assertEqual(_h, 1234) h._set('colin', 5678) _h2 = h._get('colin') self.assertEqual(_h2, 5678) def test_setitem(self): contract = 'blah' name = 'scoob' delimiter = constants.INDEX_SEPARATOR h = Hash(contract, name, driver=driver) prefix = '{}{}{}{}'.format(contract, delimiter, name, h._delimiter) h['stu'] = 9999999 raw_key = '{}stu'.format(prefix) self.assertEqual(driver.get(raw_key), 9999999) def test_getitem(self): contract = 'blah' name = 'scoob' delimiter = constants.INDEX_SEPARATOR h = Hash(contract, name, driver=driver) prefix = '{}{}{}{}'.format(contract, delimiter, name, h._delimiter) raw_key = '{}stu'.format(prefix) driver.set(raw_key, 54321) self.assertEqual(h['stu'], 54321) def test_setitems(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) h['stu'] = 123 h['stu', 'raghu'] = 1000 driver.commit() val = driver.get('blah.scoob:stu:raghu') self.assertEqual(val, 1000) def test_setitem_delimiter_illegal(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) with self.assertRaises(AssertionError): h['stu:123'] = 123 def test_setitems_too_many_dimensions_fails(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) with self.assertRaises(Exception): h['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] = 1000 def test_setitems_key_too_large(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) key = 'a' * 1025 with self.assertRaises(Exception): h[key] = 100 def test_setitem_value_too_large(self): pass def test_setitems_keys_too_large(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) key1 = 'a' * 800 key2 = 'b' * 100 key3 = 'c' * 200 with self.assertRaises(Exception): h[key1, key2, key3] = 100 def test_getitems_keys(self): contract = 'blah' name = 'scoob' delimiter = constants.INDEX_SEPARATOR h = Hash(contract, name, driver=driver) prefix = '{}{}{}{}'.format(contract, delimiter, name, h._delimiter) raw_key = '{}stu:raghu'.format(prefix) driver.set(raw_key, 54321) driver.commit() self.assertEqual(h['stu', 'raghu'], 54321) def test_getsetitems(self): contract = 'blah' name = 'scoob' delimiter = constants.INDEX_SEPARATOR h = Hash(contract, name, driver=driver) h['stu', 'raghu'] = 999 driver.commit() self.assertEqual(h['stu', 'raghu'], 999) def test_getitems_keys_too_large(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) key1 = 'a' * 800 key2 = 'b' * 100 key3 = 'c' * 200 with self.assertRaises(Exception): x = h[key1, key2, key3] def test_getitems_too_many_dimensions_fails(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) with self.assertRaises(Exception): a = h['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] def test_getitems_key_too_large(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver) key = 'a' * 1025 with self.assertRaises(Exception): a = h[key] def test_getitem_returns_default_value_if_none(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver, default_value=0) self.assertEqual(h['hello'], 0) def test_get_all_when_none_exist(self): contract = 'blah' name = 'scoob' h = Hash(contract, name, driver=driver, default_value=0) all =h.all() self.assertEqual(all, []) def test_get_all_after_setting(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh['1'] = 123 hsh['2'] = 456 hsh['3'] = 789 l = [123, 456, 789] # TODO - this ok ? :D # driver.commit() # we care about whats included, not order self.assertSetEqual(set(hsh.all()), set(l)) def test_items_returns_kv_pairs(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh['1'] = 123 hsh['2'] = 456 hsh['3'] = 789 # driver.commit() kvs = { 'blah.scoob:3': 789, 'blah.scoob:1': 123, 'blah.scoob:2': 456 } got = hsh._items() self.assertDictEqual(kvs, got) def test_items_multi_hash_returns_kv_pairs(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh[0, '1'] = 123 hsh[0, '2'] = 456 hsh[0, '3'] = 789 hsh[1, '1'] = 999 hsh[1, '2'] = 888 hsh[1, '3'] = 777 # driver.commit() kvs = { 'blah.scoob:0:3': 789, 'blah.scoob:0:1': 123, 'blah.scoob:0:2': 456 } got = hsh._items(0) self.assertDictEqual(kvs, got) def test_items_multi_hash_returns_all(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh[0, '1'] = 123 hsh[0, '2'] = 456 hsh[0, '3'] = 789 hsh[1, '1'] = 999 hsh[1, '2'] = 888 hsh[1, '3'] = 777 # driver.commit() kvs = { 'blah.scoob:0:3': 789, 'blah.scoob:0:1': 123, 'blah.scoob:0:2': 456, 'blah.scoob:1:3': 777, 'blah.scoob:1:1': 999, 'blah.scoob:1:2': 888 } got = hsh._items() self.assertDictEqual(kvs, got) def test_items_clear_deletes_only_multi_hash(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh[0, '1'] = 123 hsh[0, '2'] = 456 hsh[0, '3'] = 789 hsh[1, '1'] = 999 hsh[1, '2'] = 888 hsh[1, '3'] = 777 # driver.commit() kvs = { 'blah.scoob:0:3': 789, 'blah.scoob:0:1': 123, 'blah.scoob:0:2': 456 } hsh.clear(1) # driver.commit() got = hsh._items() self.assertDictEqual(kvs, got) def test_all_multihash_returns_values(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh[0, '1'] = 123 hsh[0, '2'] = 456 hsh[0, '3'] = 789 hsh[1, '1'] = 999 hsh[1, '2'] = 888 hsh[1, '3'] = 777 l = [123, 456, 789] # TODO # Test works when below line is commented out - not sure if our driver works differently now # driver.commit() # we care about whats included, not order self.assertSetEqual(set(hsh.all(0)), set(l)) def test_multihash_multiple_dims_clear_behaves_similar_to_single_dim(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh[1, 0, '1'] = 123 hsh[1, 0, '2'] = 456 hsh[1, 0, '3'] = 789 hsh[1, 1, '1'] = 999 hsh[1, 1, '2'] = 888 hsh[1, 1, '3'] = 777 # driver.commit() kvs = { 'blah.scoob:1:0:3': 789, 'blah.scoob:1:0:1': 123, 'blah.scoob:1:0:2': 456 } hsh.clear(1, 1) # driver.commit() got = hsh._items() self.assertDictEqual(kvs, got) def test_multihash_multiple_dims_all_gets_items_similar_to_single_dim(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh[1, 0, '1'] = 123 hsh[1, 0, '2'] = 456 hsh[1, 0, '3'] = 789 hsh[1, 1, '1'] = 999 hsh[1, 1, '2'] = 888 hsh[1, 1, '3'] = 777 l = [123, 456, 789] # driver.commit() # we care about whats included, not order self.assertSetEqual(set(hsh.all(1, 0)), set(l)) def test_clear_items_deletes_all_key_value_pairs(self): contract = 'blah' name = 'scoob' hsh = Hash(contract, name, driver=driver, default_value=0) hsh['1'] = 123 hsh['2'] = 456 hsh['3'] = 789 # TODO - test works without commit - is ok # driver.commit() kvs = { 'blah.scoob:3': 789, 'blah.scoob:1': 123, 'blah.scoob:2': 456 } got = hsh._items() self.assertDictEqual(kvs, got) hsh.clear() # driver.commit() got = hsh._items() self.assertDictEqual({}, got) class TestForeignVariable(TestCase): def setUp(self): driver.flush_full() def tearDown(self): driver.flush_full() def test_set(self): contract = 'stustu' name = 'balance' f_contract = 'colinbucks' f_name = 'balances' f = ForeignVariable(contract, name, f_contract, f_name, driver=driver) with self.assertRaises(ReferenceError): f.set('poo') def test_get(self): # set up the foreign variable contract = 'stustu' name = 'balance' f_contract = 'colinbucks' f_name = 'balances' f = ForeignVariable(contract, name, f_contract, f_name, driver=driver) # set the variable using the foreign names (assuming this is another contract namespace) v = Variable(f_contract, f_name, driver=driver) v.set('howdy') self.assertEqual(f.get(), 'howdy') class TestForeignHash(TestCase): def setUp(self): driver.flush_full() def tearDown(self): #_driver.flush_full() pass def test_set(self): # set up the foreign variable contract = 'stustu' name = 'balance' f_contract = 'colinbucks' f_name = 'balances' f = ForeignHash(contract, name, f_contract, f_name, driver=driver) with self.assertRaises(ReferenceError): f._set('stu', 1234) def test_get(self): # set up the foreign variable contract = 'stustu' name = 'balance' f_contract = 'colinbucks' f_name = 'balances' f = ForeignHash(contract, name, f_contract, f_name, driver=driver) h = Hash(f_contract, f_name, driver=driver) h._set('howdy', 555) self.assertEqual(f._get('howdy'), 555) def test_setitem(self): # set up the foreign variable contract = 'stustu' name = 'balance' f_contract = 'colinbucks' f_name = 'balances' f = ForeignHash(contract, name, f_contract, f_name, driver=driver) with self.assertRaises(ReferenceError): f['stu'] = 1234 def test_getitem(self): # set up the foreign variable contract = 'stustu' name = 'balance' f_contract = 'colinbucks' f_name = 'balances' f = ForeignHash(contract, name, f_contract, f_name, driver=driver) h = Hash(f_contract, f_name, driver=driver) h['howdy'] = 555 self.assertEqual(f['howdy'], 555) class TestLogEvent(TestCase): def setUp(self): # Define the event arguments self.args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float)} } # Create a LogEvent instance self.log_event = LogEvent(contract="test_contract", name="con_some_contract", event="Transfer", params=self.args) self.contract = "test_contract" self.name = "Transfer" self.driver = driver def test_log_event(self): contract = 'currency' name = 'Transfer' args = { 'from': { 'type': str, 'idx': True }, 'to': { 'type': str, 'idx': True }, 'amount': { 'type': (int, float) } } le = LogEvent(contract, name, event=name, params=args, driver=driver) def test_log_event_with_max_indexed_args(self): contract = 'currency' name = 'Transfer' args = { 'from': { 'type': str, 'idx': True }, 'to': { 'type': str, 'idx': True }, 'amount': { 'type': (int, float), 'idx': True } } # This should not raise an assertion error le = LogEvent(contract, name, event=name, params=args, driver=driver) self.assertIsInstance(le, LogEvent) def test_log_event_with_too_many_indexed_args(self): contract = 'currency' name = 'Transfer' args = { 'from': { 'type': str, 'idx': True }, 'to': { 'type': str, 'idx': True }, 'amount': { 'type': (int, float), 'idx': True }, 'extra': { 'type': str, 'idx': True } } # This should raise an assertion error with self.assertRaisesRegex(AssertionError, "Args must have at most three indexed arguments."): LogEvent(contract, name, event=name, params=args, driver=driver) def test_write_event_success(self): # Define the event data data = { "from": "Alice", "to": "Bob", "amount": 100 } # Call the write_event method self.log_event.write_event(data) # No assertions needed here if no exceptions are raised def test_write_event_missing_argument(self): # Define the event data with a missing argument data = { "from": "Alice", "amount": 100 } with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Data must have the same number of arguments as specified in the event.", str(context.exception)) def test_write_event_wrong_type(self): # Define the event data with a wrong type data = { "from": "Alice", "to": "Bob", "amount": "one hundred" } with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Argument amount is the wrong type!", str(context.exception)) def test_write_event_with_empty_data(self): # Test with an empty dictionary data = {} with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Data must have the same number of arguments as specified in the event.", str(context.exception)) def test_write_event_with_none(self): # Test with None as data data = None with self.assertRaises(TypeError) as context: self.log_event.write_event(data) self.assertIn("object of type 'NoneType' has no len()", str(context.exception)) def test_write_event_with_invalid_argument_names(self): # Define the event arguments correctly args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float)} } # Create a LogEvent instance log_event = LogEvent(self.contract, self.name, event=self.name, params=args, driver=self.driver) # Define event data with an unexpected argument name data = { "from": "Alice", "to": "Bob", "unexpected_arg": 100 } # This should raise an assertion error with self.assertRaises(AssertionError) as context: log_event.write_event(data) self.assertIn("Unexpected argument unexpected_arg in the data dictionary.", str(context.exception)) class TestLogEventBoundaryIndexedArgs(TestCase): def setUp(self): # Common setup for the tests self.contract = "test_contract" self.name = "Transfer" self.driver = driver # Assuming driver is defined elsewhere def test_log_event_with_exactly_three_indexed_args(self): # Define arguments with exactly three indexed arguments args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float), "idx": True} } # This should not raise an assertion error log_event = LogEvent(self.contract, self.name, event=self.name, params=args, driver=self.driver) self.assertIsInstance(log_event, LogEvent) def test_log_event_with_more_than_three_indexed_args(self): # Define arguments with more than three indexed arguments args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float), "idx": True}, "extra": {"type": str, "idx": True} } # This should raise an assertion error with self.assertRaises(AssertionError) as context: LogEvent(self.contract, self.name, event=self.name, params=args, driver=self.driver) self.assertIn("Args must have at most three indexed arguments.", str(context.exception)) import random import string class TestLogEventTypeEnforcementFuzz(TestCase): def setUp(self): # Define the event arguments self.args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float)} } # Create a LogEvent instance self.log_event = LogEvent(contract="test_contract", name="con_some_contract", event="Transfer", params=self.args) def random_string(self, length=10): return ''.join(random.choices(string.ascii_letters + string.digits, k=length)) def test_write_event_with_random_data(self): # Generate random data for each argument for _ in range(100): # Run 100 iterations for fuzz testing data = { "from": self.random_string(), "to": self.random_string(), "amount": random.choice([self.random_string(), None, [], {}, set(), object()]) } with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Argument amount is the wrong type!", str(context.exception)) def test_write_event_with_random_structures(self): # Test with random structures for the 'amount' field random_structures = [None, [], {}, set(), object(), lambda x: x, b"bytes", (1, 2, 3)] for structure in random_structures: data = { "from": "Alice", "to": "Bob", "amount": structure } with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Argument amount is the wrong type!", str(context.exception)) def test_write_event_with_random_numeric_types(self): # Test with various numeric types that are not allowed random_numeric_types = [ complex(1, 1), # float('nan'), # Doesnt raise IS THIS OK ? # float('inf'), # Doesn't raise IS THIS OK ? # -float('inf') # Doesn't raise IS THIS OK ? ] for num in random_numeric_types: data = { "from": "Alice", "to": "Bob", "amount": num } with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Argument amount is the wrong type!", str(context.exception)) class TestLogEventInvalidArgumentNames(TestCase): def setUp(self): # Define the event arguments self.args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float)} } # Create a LogEvent instance self.log_event = LogEvent(contract="test_contract", name="con_some_contract", event="Transfer", params=self.args) def test_write_event_with_invalid_argument_names(self): # Define event data with an unexpected argument name data = { "from": "Alice", "to": "Bob", "unexpected_arg": 100 } # This should raise an assertion error with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Unexpected argument unexpected_arg in the data dictionary.", str(context.exception)) class TestLogEventLargeData(TestCase): def setUp(self): # Define the event arguments self.args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float)} } # Create a LogEvent instance self.log_event = LogEvent(contract="test_contract", name="con_some_contract", event="Transfer", params=self.args) def test_write_event_with_large_data(self): # Generate a large string for the 'from' and 'to' fields large_string = 'A' * 10**6 # 1 million characters # Generate a large number for the 'amount' field large_number = 10**18 # A very large number # Define the event data with large values data = { "from": large_string, "to": large_string, "amount": large_number } # Call the write_event method and ensure it completes without error try: self.log_event.write_event(data) success = True except Exception as e: success = False print(f"Error occurred: {e}") self.assertFalse(success, "write_event should not handle large data without errors.") class TestLogEventInvalidDataTypes(TestCase): def setUp(self): # Define the event arguments self.args = { "from": {"type": str, "idx": True}, "to": {"type": str, "idx": True}, "amount": {"type": (int, float)} } # Create a LogEvent instance self.log_event = LogEvent(contract="test_contract", name="con_some_contract", event="Transfer", params=self.args) def test_write_event_with_invalid_string_type(self): # Use an invalid type (e.g., list) for the 'from' field data = { "from": ["Alice"], # Invalid type: list instead of str "to": "Bob", "amount": 100 } # This should raise an assertion error with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Argument from is the wrong type!", str(context.exception)) def test_write_event_with_invalid_numeric_type(self): # Use an invalid type (e.g., string) for the 'amount' field data = { "from": "Alice", "to": "Bob", "amount": "one hundred" # Invalid type: str instead of int/float/ContractingDecimal } # This should raise an assertion error with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Argument amount is the wrong type!", str(context.exception)) def test_write_event_with_unexpected_object_type(self): # Use an unexpected object type for the 'to' field data = { "from": "Alice", "to": object(), # Invalid type: object instead of str "amount": 100 } # This should raise an assertion error with self.assertRaises(AssertionError) as context: self.log_event.write_event(data) self.assertIn("Argument to is the wrong type!", str(context.exception)) class TestLogEventNonStandardTypes(TestCase): def setUp(self): # Common setup for the tests self.contract = "test_contract" self.name = "Transfer" self.driver = driver # Assuming driver is defined elsewhere def test_log_event_with_non_standard_type(self): # Define arguments with a non-standard type (e.g., list) args = { "from": {"type": list, "idx": True}, # Invalid type: list "to": {"type": str, "idx": True}, "amount": {"type": (int, float, ContractingDecimal)} } # This should raise an assertion error with self.assertRaises(AssertionError) as context: LogEvent(self.contract, self.name, event=self.name, params=args, driver=self.driver) self.assertIn("Each type in args must be str, int, float, decimal or bool.", str(context.exception)) def test_log_event_with_custom_object_type(self): # Define arguments with a custom object type class CustomType: pass args = { "from": {"type": CustomType, "idx": True}, # Invalid type: CustomType "to": {"type": str, "idx": True}, "amount": {"type": (int, float)} } # This should raise an assertion error with self.assertRaises(AssertionError) as context: LogEvent(self.contract, self.name, event=self.name, params=args, driver=self.driver) self.assertIn("Each type in args must be str, int, float, decimal or bool.", str(context.exception)) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/unit/test_parser.py ================================================ from contracting.compilation import parser from contracting.compilation.compiler import ContractingCompiler from unittest import TestCase class TestParser(TestCase): def setUp(self): self.compiler = ContractingCompiler() def test_methods_for_contract_single_function(self): code = ''' @export def test_func(arg: str, arg2: int): return arg, arg2 ''' compiled = self.compiler.parse_to_code(code) expected = [{ 'name': 'test_func', 'arguments': [ { 'name': 'arg', 'type': 'str' }, { 'name': 'arg2', 'type': 'int' } ] }] got = parser.methods_for_contract(compiled) self.assertListEqual(expected, got) def test_methods_for_contract_datetime(self): code = ''' @export def thing(arg: datetime.datetime, arg2: datetime.timedelta): return 123 ''' compiled = self.compiler.parse_to_code(code) got = parser.methods_for_contract(compiled) expected = [ { 'name': 'thing', 'arguments': [ { 'name': 'arg', 'type': 'datetime.datetime' }, { 'name': 'arg2', 'type': 'datetime.timedelta' } ] } ] self.assertEqual(expected, got) def test_methods_for_contract_multiple_functions_and_privates(self): code = ''' @export def test_func(arg: str, arg2: int): return arg, arg2 @export def another_one(something: Any, something_else: dict): a = 123 b = 456 return add(a, b) def add(a, b): return a + b ''' compiled = self.compiler.parse_to_code(code) expected = [{ 'name': 'test_func', 'arguments': [ { 'name': 'arg', 'type': 'str' }, { 'name': 'arg2', 'type': 'int' } ], }, { 'name': 'another_one', 'arguments': [ { 'name': 'something', 'type': 'Any' }, { 'name': 'something_else', 'type': 'dict' } ] }] got = parser.methods_for_contract(compiled) self.assertListEqual(expected, got) def test_variables_for_contract_passes_election_house(self): code = ''' # Convenience I = importlib #Policies policies = Hash() # Policy interface policy_interface = [ I.Func('vote', args=('vk', 'obj')), I.Func('current_value') ] @export def register_policy(contract: str): if policies[contract] is None: # Attempt to import the contract to make sure it is already submitted p = I.import_module(contract) # Assert ownership is election_house and interface is correct assert I.owner_of(p) == ctx.this, \ 'Election house must control the policy contract!' assert I.enforce_interface(p, policy_interface), \ 'Policy contract does not follow the correct interface' policies[contract] = True else: raise Exception('Policy already registered') @export def current_value_for_policy(policy: str): assert policies.get(policy) is not None, 'Invalid policy.' p = I.import_module(policy) return p.current_value() @export def vote(policy: str, value: Any): # Verify policy has been registered assert policies.get(policy) is not None, 'Invalid policy.' p = I.import_module(policy) p.vote(vk=ctx.caller, obj=value) ''' compiled = self.compiler.parse_to_code(code) got = parser.variables_for_contract(compiled) expected = { 'variables': [], 'hashes': ['policies'] } self.assertDictEqual(got, expected) def test_variables_for_contract_multiple_variables(self): code = ''' v1 = Variable() v2 = Variable() v3 = Variable() @export def something(): return 1 ''' compiled = self.compiler.parse_to_code(code) got = parser.variables_for_contract(compiled) expected = { 'variables': ['v1', 'v2', 'v3'], 'hashes': [] } self.assertDictEqual(got, expected) def test_variables_for_contract_multiple_hashes(self): code = ''' h1 = Hash() h2 = Hash() h3 = Hash() @export def something(): return 1 ''' compiled = self.compiler.parse_to_code(code) got = parser.variables_for_contract(compiled) expected = { 'variables': [], 'hashes': ['h1', 'h2', 'h3'] } self.assertDictEqual(got, expected) def test_variables_mix(self): code = ''' v1 = Variable() v2 = Variable() v3 = Variable() h1 = Hash() h2 = Hash() h3 = Hash() @export def something(): return 1 ''' compiled = self.compiler.parse_to_code(code) got = parser.variables_for_contract(compiled) expected = { 'variables': ['v1', 'v2', 'v3'], 'hashes': ['h1', 'h2', 'h3'] } self.assertDictEqual(got, expected) ================================================ FILE: tests/unit/test_revert_on_exception.py ================================================ import unittest from contracting.storage.driver import Driver from contracting.execution.executor import Executor from contracting.constants import STAMPS_PER_TAU from xian.processor import TxProcessor from contracting.client import ContractingClient import contracting import random import string import os import sys from loguru import logger # Get the directory where the script is located script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) # Change the current working directory os.chdir(script_dir) def submission_kwargs_for_file(f): # Get the file name only by splitting off directories split = f.split('/') split = split[-1] # Now split off the .s split = split.split('.') contract_name = split[0] with open(f) as file: contract_code = file.read() return { 'name': f'con_{contract_name}', 'code': contract_code, } TEST_SUBMISSION_KWARGS = { 'sender': 'stu', 'contract_name': 'submission', 'function_name': 'submit_contract' } class MyTestCase(unittest.TestCase): def setUp(self): self.c = ContractingClient() self.tx_processor = TxProcessor(client=self.c) # Hard load the submission contract self.d = self.c.raw_driver self.d.flush_full() self.script_dir = os.path.dirname(os.path.abspath(__file__)) submission_file_path = os.path.join(self.script_dir, "contracts", "submission.s.py") with open(submission_file_path) as f: contract = f.read() self.d.set_contract(name='submission', code=contract) currency_file_path = os.path.join(self.script_dir, "contracts", "currency.s.py") with open(currency_file_path) as f: contract = f.read() self.d.set_contract(name='currency', code=contract) self.c.executor.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(currency_file_path), metering=False, auto_commit=True) exception_file_path = os.path.join(self.script_dir, "contracts", "exception.s.py") with open(exception_file_path) as f: contract = f.read() self.d.set_contract(name='exception', code=contract) self.c.executor.execute(**TEST_SUBMISSION_KWARGS, kwargs=submission_kwargs_for_file(exception_file_path), metering=False, auto_commit=True) self.d.commit() def test_exception(self): prior_balance = self.d.get('con_exception.balances:stu') logger.debug(f"Prior balance (exception): {prior_balance}") output = self.tx_processor.process_tx({ "payload": {'sender': 'stu', 'contract': 'con_exception', 'function': 'transfer', 'kwargs': {'amount': 100, 'to': 'colin'},"stamps_supplied":1000}, "metadata": {"signature":"abc"},"b_meta":{"nanos":0, "hash":"0x0","height":0, "chain_id":"xian-1"}}) logger.debug(f"Output (exception): {output}") new_balance = self.d.get('con_exception.balances:stu') logger.debug(f"New balance (exception): {new_balance}") self.assertEqual(prior_balance, new_balance) def test_non_exception(self): prior_balance = self.d.get('con_currency.balances:stu') output = self.tx_processor.process_tx({ "payload": {'sender': 'stu', 'contract': 'con_currency', 'function': 'transfer', 'kwargs': {'amount': 100, 'to': 'colin'},"stamps_supplied":1000}, "metadata": {"signature":"abc"},"b_meta":{"nanos":0,"hash":"0x0","height":0, "chain_id":"xian-1"}}) new_balance = self.d.get('con_currency.balances:stu') logger.debug(f"New balance (non-exception): {new_balance}") self.assertEqual(prior_balance - 100, new_balance) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/unit/test_runtime.py ================================================ from unittest import TestCase from contracting.execution import runtime import sys import psutil import os class TestRuntime(TestCase): def tearDown(self): runtime.rt.tracer.stop() runtime.rt.clean_up() def test_tracer_works_roughly(self): stamps = 1000000 # This doesnt work because we are only metering things with _contract_ in Globals runtime.rt.set_up(stmps=stamps, meter=True) globals()['__contract__'] = True x = max([i for i in range(2)]) globals()['__contract__'] = False runtime.rt.tracer.stop() used = runtime.rt.tracer.get_stamp_used() runtime.rt.clean_up() self.assertLess(stamps - used, stamps) def test_tracer_bypass_records_no_stamps(self): stamps = 1000 runtime.rt.set_up(stmps=stamps, meter=False) a = 5 b = 5 runtime.rt.tracer.stop() used = runtime.rt.tracer.get_stamp_used() runtime.rt.clean_up() self.assertEqual(stamps - used, stamps) def test_arbitrary_modification_of_stamps_works(self): stamps = 1000 sub = 500 runtime.rt.set_up(stmps=stamps, meter=True) globals()['__contract__'] = True a = 5 globals()['__contract__'] = False runtime.rt.tracer.stop() used_1 = runtime.rt.tracer.get_stamp_used() runtime.rt.tracer.set_stamp(stamps - sub) used_2 = runtime.rt.tracer.get_stamp_used() runtime.rt.clean_up() print(used_1, used_2) def test_starting_and_stopping_tracer_works_roughly(self): stamps = 1000000 runtime.rt.set_up(stmps=stamps, meter=True) globals()['__contract__'] = True x = max([i for i in range(4)]) globals()['__contract__'] = False runtime.rt.tracer.stop() used_1 = runtime.rt.tracer.get_stamp_used() runtime.rt.clean_up() stamps = 10000000 runtime.rt.set_up(stmps=stamps, meter=True) globals()['__contract__'] = True x = max([i for i in range(1)]) runtime.rt.tracer.stop() x = max([i for i in range(1)]) runtime.rt.tracer.stop() globals()['__contract__'] = False used_2 = runtime.rt.tracer.get_stamp_used() runtime.rt.clean_up() self.assertGreater(used_1, used_2) def test_modifying_stamps_during_tracing(self): stamps = 10000 runtime.rt.set_up(stmps=stamps, meter=True) a = 5 b = 5 runtime.rt.tracer.stop() c = 5 d = 5 e = 5 runtime.rt.clean_up() used_1 = runtime.rt.tracer.get_stamp_used() stamps = 5000 runtime.rt.set_up(stmps=stamps, meter=True) a = 5 b = 5 runtime.rt.tracer.stop() used_1 = runtime.rt.tracer.get_stamp_used() runtime.rt.set_up(stmps=stamps - used_1, meter=True) c = 5 d = 5 e = 5 runtime.rt.clean_up() used_2 = runtime.rt.tracer.get_stamp_used() print(used_1, used_2) def test_add_exists(self): stamps = 1000 runtime.rt.set_up(stmps=stamps, meter=True) runtime.rt.tracer.add_cost(900) runtime.rt.tracer.stop() used_1 = runtime.rt.tracer.get_stamp_used() runtime.rt.clean_up() print(used_1) def test_deduct_write_adjusts_total_writes(self): stamps = 1000 runtime.rt.set_up(stmps=stamps, meter=True) self.assertEqual(runtime.rt.writes, 0) runtime.rt.deduct_write('a', 'bad') self.assertEqual(runtime.rt.writes, 4) runtime.rt.clean_up() def test_deduct_write_fails_if_too_many_writes(self): stamps = 1000 runtime.rt.set_up(stmps=stamps, meter=True) self.assertEqual(runtime.rt.writes, 0) runtime.rt.deduct_write('a', 'bad') self.assertEqual(runtime.rt.writes, 4) with self.assertRaises(AssertionError): runtime.rt.deduct_write('a', 'b' * 32 * 1024) runtime.rt.clean_up() ================================================ FILE: tests/unit/test_state_management.py ================================================ import unittest from xian.processor import TxProcessor from contracting.client import ContractingClient from xian.services.simulator import Simulator from xian.constants import Constants import os import pathlib class MyTestCase(unittest.TestCase): def setUp(self): constants = Constants() self.c = ContractingClient(storage_home=constants.STORAGE_HOME) self.tx_processor = TxProcessor(client=self.c) self.stamp_calculator = Simulator() self.d = self.c.raw_driver self.d.flush_full() script_dir = os.path.dirname(os.path.abspath(__file__)) submission_contract_path = os.path.join(script_dir, "contracts", "submission.s.py") with open(submission_contract_path) as f: contract = f.read() self.d.set_contract(name="submission", code=contract) def deploy_broken_stuff(self): # Get the directory where the script is located script_dir = os.path.dirname(os.path.abspath(__file__)) proxythis_path = os.path.join(script_dir, "contracts", "proxythis.py") with open(proxythis_path) as f: contract = f.read() self.c.submit( contract, name="con_proxythis", ) self.proxythis = self.c.get_contract("con_proxythis") thistest2_path = os.path.join(script_dir, "contracts", "thistest2.py") with open(thistest2_path) as f: contract = f.read() self.c.submit( contract, name="con_thistest2", ) self.thistest2 = self.c.get_contract("con_thistest2") def test_submit(self): self.deploy_broken_stuff() self.assertEqual(self.proxythis.proxythis(con="con_thistest2", signer="address"), ("con_thistest2", "con_proxythis")) self.assertEqual(self.proxythis.noproxy(signer="address"), ("con_proxythis", "address")) self.assertEqual(self.proxythis.nestedproxythis(con="con_thistest2", signer="address"), ("con_thistest2", "con_proxythis")) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/unit/test_stdlib_hashing.py ================================================ from unittest import TestCase from contracting.stdlib.bridge.hashing import sha256, sha3 class TestHashing(TestCase): def test_sha3(self): secret = '1a54390942257a70bb843c1bd94eb996' _hash = '6c839446b4d4fa2582af5011730c680b3ee39929f041b7bee6f376211cc710f7' self.assertEqual(_hash, sha3(secret)) def test_sha256(self): secret = '842b65a7d48e3a3c3f0e9d37eaced0b2' _hash = 'eaf48a02d3a4bb3aeb0ecb337f6efb026ee0bbc460652510cff929de78935514' self.assertEqual(_hash, sha256(secret)) ================================================ FILE: tests/unit/test_sys_contracts/__init__.py ================================================ ================================================ FILE: tests/unit/test_sys_contracts/bad_lint.s.py ================================================ def no_exports(): return 'hahaha' ================================================ FILE: tests/unit/test_sys_contracts/compile_this.s.py ================================================ @export def good_function(): return 5 def another_function(): return 100 ================================================ FILE: tests/unit/test_sys_contracts/currency.s.py ================================================ xrate = Variable() seed_amount = Variable() balances = Hash() allowed = Hash() @construct def seed(): xrate = 1.0 seed_amount.set(1000000) balances['reserves'] = 0 founder_wallets = [ '324ee2e3544a8853a3c5a0ef0946b929aa488cbe7e7ee31a0fef9585ce398502', 'a103715914a7aae8dd8fddba945ab63a169dfe6e37f79b4a58bcf85bfd681694', '20da05fdba92449732b3871cc542a058075446fedb41430ee882e99f9091cc4d', 'ed19061921c593a9d16875ca660b57aa5e45c811c8cf7af0cfcbd23faa52cbcd', 'cb9bfd4b57b243248796e9eb90bc4f0053d78f06ce68573e0fdca422f54bb0d2', 'c1f845ad8967b93092d59e4ef56aef3eba49c33079119b9c856a5354e9ccdf84' ] for w in founder_wallets: balances[w] = seed_amount.get() def assert_stamps(stamps): assert balances[ctx.signer] >= stamps, \ "Not enough funds to submit stamps" def submit_stamps(stamps): stamps *= xrate balances[ctx.signer] -= stamps balances['reserves'] += stamps @export def transfer(to, amount): assert balances[ctx.signer] - amount >= 0, \ 'Sender balance must be non-negative!!!' balances[ctx.sender] -= amount balances[to] += amount @export def approve(spender, amount): allowed[ctx.sender][spender] = amount @export def transfer_from(approver, spender, amount): assert allowed[approver][spender] >= amount assert balances[approver] >= amount allowed[approver][spender] -= amount balances[approver] -= amount balances[spender] += amount ================================================ FILE: tests/unit/test_sys_contracts/good_lint.s.py ================================================ @export def exports(): return 'huehuehue' ================================================ FILE: tests/unit/test_sys_contracts/module1.py ================================================ ''' how the modules import each other. this is to test ctx.caller etc 1 | | 2 3 | | | | 4 5 6 7 | 8 ''' import module2 import module3 print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module2.py ================================================ import module4 import module5 print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module3.py ================================================ import module6 import module7 print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module4.py ================================================ print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module5.py ================================================ import module8 print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module6.py ================================================ print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module7.py ================================================ print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module8.py ================================================ print('{} called from {}, signed by {}'.format(ctx.this, ctx.caller, ctx.signer)) ================================================ FILE: tests/unit/test_sys_contracts/module_func.py ================================================ supply = Variable() balances = Hash(default_value=0) @construct def seed(): balances['test'] = 100 supply.set(balances['test']) @export def test_func(status=None): return status @export def test_keymod(deduct): balances['test'] -= deduct return balances['test'] ================================================ FILE: tests/unit/test_timedelta.py ================================================ from unittest import TestCase from contracting.stdlib.bridge.time import Timedelta, WEEKS, DAYS, HOURS, MINUTES, SECONDS, Datetime from datetime import datetime as dt from datetime import timedelta import decimal class TestTimedelta(TestCase): def test_implementation_mimics_actual_timedelta(self): t = Timedelta(days=10, minutes=10, seconds=10) _t = timedelta(days=10, minutes=10, seconds=10) self.assertEqual(t._timedelta, _t) def test_constants_work(self): self.assertEqual(WEEKS._timedelta, timedelta(weeks=1)) self.assertEqual(DAYS._timedelta, timedelta(days=1)) self.assertEqual(HOURS._timedelta, timedelta(hours=1)) self.assertEqual(MINUTES._timedelta, timedelta(minutes=1)) self.assertEqual(SECONDS._timedelta, timedelta(seconds=1)) def test_eq_true(self): t = Timedelta(days=1) _t = Timedelta(days=1) self.assertTrue(t == _t) def test_eq_false(self): t = Timedelta(days=1) _t = Timedelta(days=10) self.assertFalse(t == _t) def test_gt_true(self): t = Timedelta(days=10) _t = Timedelta(days=1) self.assertTrue(t > _t) def test_gt_false(self): t = Timedelta(days=1) _t = Timedelta(days=10) self.assertFalse(t > _t) def test_ge_true(self): t = Timedelta(days=10) _t = Timedelta(days=1) self.assertTrue(t >= _t) def test_ge_false(self): t = Timedelta(days=1) _t = Timedelta(days=10) self.assertFalse(t >= _t) def test_ge_true_eq(self): t = Timedelta(days=10) _t = Timedelta(days=10) self.assertTrue(t >= _t) def test_lt_true(self): t = Timedelta(days=10) _t = Timedelta(days=1) self.assertFalse(t < _t) def test_lt_false(self): t = Timedelta(days=10) _t = Timedelta(days=1) self.assertFalse(t < _t) def test_le_true(self): t = Timedelta(days=10) _t = Timedelta(days=1) self.assertFalse(t <= _t) def test_le_false(self): t = Timedelta(days=10) _t = Timedelta(days=1) self.assertFalse(t <= _t) def test_le_true_eq(self): t = Timedelta(days=10) _t = Timedelta(days=10) self.assertTrue(t <= _t) def test_ne_true(self): t = Timedelta(days=10) _t = Timedelta(days=1) self.assertTrue(t != _t) def test_ne_false(self): t = Timedelta(days=10) _t = Timedelta(days=10) self.assertFalse(t != _t) def test_addition_works_days(self): t_add = Timedelta(days=10) + Timedelta(days=1) t_done = Timedelta(days=11) org = timedelta(days=11) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_addition_works_seconds(self): t_add = Timedelta(seconds=10) + Timedelta(seconds=1) t_done = Timedelta(seconds=11) org = timedelta(seconds=11) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_addition_works_days_and_seconds(self): t_add = Timedelta(days=10, seconds=10) + Timedelta(days=1, seconds=1) t_done = Timedelta(days=11, seconds=11) org = timedelta(days=11, seconds=11) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_subtraction_works_days(self): t_add = Timedelta(days=10) - Timedelta(days=1) t_done = Timedelta(days=9) org = timedelta(days=9) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_subtraction_works_seconds(self): t_add = Timedelta(seconds=10) - Timedelta(seconds=1) t_done = Timedelta(seconds=9) org = timedelta(seconds=9) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_subtraction_works_days_and_seconds(self): t_add = Timedelta(days=10, seconds=10) - Timedelta(days=1, seconds=1) t_done = Timedelta(days=9, seconds=9) org = timedelta(days=9, seconds=9) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_multiplication_works(self): t_add = Timedelta(days=10) * Timedelta(days=3) t_done = Timedelta(days=30) org = timedelta(days=30) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_multiplication_works_seconds(self): t_add = Timedelta(seconds=10) * Timedelta(seconds=3) t_done = Timedelta(seconds=30) org = timedelta(seconds=30) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_multiplication_works_days_and_seconds(self): SECONDS_IN_A_DAY = 86400 t_add = Timedelta(days=10, seconds=10) * Timedelta(days=3, seconds=3) t_done = Timedelta(seconds=(30 + (30*SECONDS_IN_A_DAY))) org = timedelta(seconds=(30 + (30*SECONDS_IN_A_DAY))) self.assertEqual(t_add, t_done) self.assertEqual(t_add._timedelta, org) def test_addition_not_implemented(self): with self.assertRaises(TypeError): Timedelta(days=10, seconds=10) + 5 def test_subtraction_not_implemented(self): with self.assertRaises(TypeError): Timedelta(days=10, seconds=10) - 5 def test_multiplication_with_int_works(self): self.assertEqual(Timedelta(days=10, seconds=10) * 5, Timedelta(days=50, seconds=50)) self.assertEqual((Timedelta(days=10, seconds=10) * 5)._timedelta, timedelta(days=50, seconds=50)) def test_multiplication_does_not_work_with_decimal(self): with self.assertRaises(TypeError): Timedelta(days=10, seconds=10) * decimal.Decimal(0.1) def test_get_seconds_works(self): weeks = 0 days = 0 hours = 0 minutes = 0 seconds = 10 t = Timedelta(weeks, days, hours, minutes, seconds) self.assertEqual(t.seconds, 10) def test_get_minutes_works(self): weeks = 0 days = 0 hours = 0 minutes = 12 seconds = 10 t = Timedelta(weeks, days, hours, minutes, seconds) self.assertEqual(t.minutes, 12) def test_get_hours_works(self): weeks = 0 days = 0 hours = 4 minutes = 12 seconds = 10 t = Timedelta(weeks, days, hours, minutes, seconds) self.assertEqual(t.hours, 4) def test_get_days_works(self): weeks = 0 days = 8 hours = 4 minutes = 12 seconds = 10 t = Timedelta(weeks, days, hours, minutes, seconds) self.assertEqual(t.days, 8) def test_get_weeks_works(self): weeks = 2 days = 1 hours = 4 minutes = 12 seconds = 10 t = Timedelta(weeks, days, hours, minutes, seconds) self.assertEqual(t.weeks, 2) def test_larger_components_returns_as_expected(self): weeks = 2 days = 1 hours = 4 minutes = 12 seconds = 10 t = Timedelta(weeks, days, hours, minutes, seconds) self.assertEqual(t.days, 15) self.assertEqual(t.hours, 364) def test_adding_timedelta_to_datetime_returns_correct(self): t = Timedelta(days=1) d = Datetime(2020, 10, 1) d2 = t + d self.assertEqual(d2, Datetime(2020, 10, 2)) def test_subtracting_timedelta_to_datetime_returns_correct(self): t = Timedelta(days=1) d = Datetime(2020, 10, 1) d2 = t - d self.assertEqual(d2, Datetime(2020, 9, 30))