├── twig ├── utils │ ├── __init__.py │ └── compiler.py ├── backends │ ├── __init__.py │ └── vyper.py ├── __init__.py ├── exceptions.py ├── filesystem.py └── compiler.py ├── docs ├── _static │ └── .suppress-sphinx-build-warning ├── releases.rst ├── index.rst ├── overview.rst ├── Makefile └── conf.py ├── tests ├── core │ ├── test_import.py │ ├── test_filesystem.py │ └── test_compiler.py ├── assets │ ├── registry.vy │ ├── crowdfund.vy │ └── simple_open_auction.vy ├── test_name_registry.py └── conftest.py ├── .project-template ├── template_vars.txt ├── refill_template_vars.sh └── fill_template_vars.sh ├── pytest.ini ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── contracts └── name_registry.vy ├── .bumpversion.cfg ├── .circleci ├── merge_pr.sh └── config.yml ├── .travis.yml ├── LICENSE ├── tox.ini ├── Makefile ├── .gitignore ├── setup.py └── README.md /twig/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_static/.suppress-sphinx-build-warning: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /twig/backends/__init__.py: -------------------------------------------------------------------------------- 1 | from .vyper import VyperBackend # noqa: F401 2 | -------------------------------------------------------------------------------- /tests/core/test_import.py: -------------------------------------------------------------------------------- 1 | def test_import(): 2 | import twig # noqa: F401 3 | -------------------------------------------------------------------------------- /.project-template/template_vars.txt: -------------------------------------------------------------------------------- 1 | twig 2 | twig 3 | twig 4 | twig 5 | twig 6 | A tool for Ethereum smart contract development. 7 | -------------------------------------------------------------------------------- /twig/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | TWIG_DIR = Path(__file__).parent 4 | 5 | CONTRACTS_DIR = TWIG_DIR.parent / "contracts" 6 | -------------------------------------------------------------------------------- /.project-template/refill_template_vars.sh: -------------------------------------------------------------------------------- 1 | TEMPLATE_DIR=$(dirname $(readlink -f "$0")) 2 | <"$TEMPLATE_DIR/template_vars.txt" "$TEMPLATE_DIR/fill_template_vars.sh" 3 | -------------------------------------------------------------------------------- /docs/releases.rst: -------------------------------------------------------------------------------- 1 | Release Notes 2 | ============= 3 | 4 | v0.1.0-alpha.1 5 | -------------- 6 | 7 | - Launched repository, claimed names for pip, RTD, github, etc 8 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts= -v --showlocals --durations 10 3 | python_paths= . 4 | xfail_strict=true 5 | 6 | [pytest-watch] 7 | runner= pytest --failed-first --maxfail=1 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What was wrong? 2 | 3 | Issue # 4 | 5 | ## How was it fixed? 6 | 7 | Summary of approach. 8 | 9 | #### Cute Animal Picture 10 | 11 | ![put a cute animal picture link inside the parentheses]() 12 | -------------------------------------------------------------------------------- /twig/exceptions.py: -------------------------------------------------------------------------------- 1 | class TwigError(Exception): 2 | """ 3 | Base class for all Twig errors. 4 | """ 5 | 6 | pass 7 | 8 | 9 | class CompilerError(TwigError): 10 | """ 11 | Raised when an error occurs during compilation. 12 | """ 13 | 14 | pass 15 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | twig 2 | ============================== 3 | 4 | A tool for Ethereum smart contract development. 5 | 6 | Contents 7 | -------- 8 | 9 | .. toctree:: 10 | :maxdepth: 3 11 | 12 | twig 13 | overview 14 | releases 15 | 16 | 17 | Indices and tables 18 | ------------------ 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | -------------------------------------------------------------------------------- /twig/filesystem.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Iterable 3 | 4 | SOURCES_GLOB = "**/*.vy" 5 | 6 | 7 | def collect_sources(path: Path, glob: str = SOURCES_GLOB) -> Iterable[Path]: 8 | if path.is_dir(): 9 | return Path(path).glob(glob) 10 | else: 11 | raise FileNotFoundError(f"{path} is not a valid directory.") 12 | -------------------------------------------------------------------------------- /tests/core/test_filesystem.py: -------------------------------------------------------------------------------- 1 | from twig.filesystem import collect_sources 2 | 3 | 4 | def test_collect_sources(test_contracts_dir): 5 | sources = collect_sources(test_contracts_dir) 6 | filenames = [source.name for source in sources] 7 | assert set(filenames) == set( 8 | ["registry.vy", "simple_open_auction.vy", "crowdfund.vy"] 9 | ) 10 | -------------------------------------------------------------------------------- /contracts/name_registry.vy: -------------------------------------------------------------------------------- 1 | registry: map(bytes[100], address) 2 | 3 | @public 4 | def register(name: bytes[100], owner: address): 5 | assert self.registry[name] == ZERO_ADDRESS # check name has not been set yet. 6 | self.registry[name] = owner 7 | 8 | 9 | @public 10 | @constant 11 | def lookup(name: bytes[100]) -> address: 12 | return self.registry[name] 13 | -------------------------------------------------------------------------------- /tests/assets/registry.vy: -------------------------------------------------------------------------------- 1 | registry: map(bytes[100], address) 2 | 3 | @public 4 | def register(name: bytes[100], owner: address): 5 | assert self.registry[name] == ZERO_ADDRESS # check name has not been set yet. 6 | self.registry[name] = owner 7 | 8 | 9 | @public 10 | @constant 11 | def lookup(name: bytes[100]) -> address: 12 | return self.registry[name] 13 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0-alpha.5 3 | commit = True 4 | tag = True 5 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P[^.]*)\.(?P\d+))? 6 | serialize = 7 | {major}.{minor}.{patch}-{stage}.{devnum} 8 | {major}.{minor}.{patch} 9 | 10 | [bumpversion:part:stage] 11 | optional_value = stable 12 | first_value = stable 13 | values = 14 | alpha 15 | beta 16 | stable 17 | 18 | [bumpversion:part:devnum] 19 | 20 | [bumpversion:file:setup.py] 21 | search = version='{current_version}', 22 | replace = version='{new_version}', 23 | 24 | -------------------------------------------------------------------------------- /.circleci/merge_pr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then 4 | PR_INFO_URL=https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$CIRCLE_PR_NUMBER 5 | PR_BASE_BRANCH=$(curl -L "$PR_INFO_URL" | python -c 'import json, sys; obj = json.load(sys.stdin); sys.stdout.write(obj["base"]["ref"])') 6 | git fetch origin +"$PR_BASE_BRANCH":circleci/pr-base 7 | # We need these config values or git complains when creating the 8 | # merge commit 9 | git config --global user.name "Circle CI" 10 | git config --global user.email "circleci@example.com" 11 | git merge --no-edit circleci/pr-base 12 | fi 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _If this is a bug report, please fill in the following sections. 2 | If this is a feature request, delete and describe what you would like with examples._ 3 | 4 | ## What was wrong? 5 | 6 | ### Code that produced the error 7 | 8 | ```py 9 | CODE_TO_REPRODUCE 10 | ``` 11 | 12 | ### Full error output 13 | 14 | ```sh 15 | ERROR_HERE 16 | ``` 17 | 18 | ### Expected Result 19 | 20 | _This section may be deleted if the expectation is "don't crash"._ 21 | 22 | ```sh 23 | EXPECTED_RESULT 24 | ``` 25 | 26 | ### Environment 27 | 28 | ```sh 29 | # run this: 30 | $ python -m eth_utils 31 | 32 | # then copy the output here: 33 | OUTPUT_HERE 34 | ``` 35 | 36 | ## How can it be fixed? 37 | 38 | Fill this section in if you know how this could or should be fixed. 39 | -------------------------------------------------------------------------------- /tests/test_name_registry.py: -------------------------------------------------------------------------------- 1 | # Example test file, safe to delete 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def name_registry_package(twig_deployer): 8 | # returns an ethpm.package instance loaded with a "name_registry" deployment 9 | return twig_deployer.deploy("name_registry") 10 | 11 | 12 | @pytest.fixture 13 | def name_registry(name_registry_package): 14 | # returns a name_registry web3.contract instance 15 | return name_registry_package.deployments.get_instance("name_registry") 16 | 17 | 18 | def test_name_registry_lookup(name_registry, name_registry_package): 19 | w3 = name_registry_package.w3 20 | name = b"Ongo Gablogian" 21 | name_registry.functions.register(name, w3.eth.accounts[0]).transact() 22 | registered_address = name_registry.functions.lookup(name).call() 23 | assert registered_address == w3.eth.accounts[0] 24 | -------------------------------------------------------------------------------- /twig/backends/vyper.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, Dict, Iterable, Tuple 3 | 4 | from eth_utils import to_dict 5 | 6 | from twig.filesystem import collect_sources 7 | from twig.utils.compiler import create_raw_asset_data 8 | 9 | 10 | class VyperBackend: 11 | def compile(self, sources_dir: Path) -> Dict[str, Any]: 12 | return generate_vyper_compiler_output(sources_dir) 13 | 14 | 15 | @to_dict 16 | def generate_vyper_compiler_output(sources_dir: Path) -> Iterable[Tuple[str, Any]]: 17 | all_sources = collect_sources(sources_dir) 18 | for source in all_sources: 19 | contract_file = str(source).split("/")[-1] 20 | contract_type = contract_file.split(".")[0] 21 | # todo fix to accomodate multiple types in a single contract file. 22 | yield contract_file, {contract_type: create_raw_asset_data(source.read_text())} 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | dist: trusty 4 | matrix: 5 | include: 6 | # 7 | # Python 3.5 testing 8 | # 9 | # lint 10 | - python: "3.5" 11 | env: TOX_POSARGS="-e lint" 12 | # doctest 13 | - python: "3.5" 14 | env: TOX_POSARGS="-e doctest" 15 | # core 16 | - python: "3.5" 17 | env: TOX_POSARGS="-e py35-core" 18 | # 19 | # Python 3.6 testing 20 | # 21 | # core 22 | - python: "3.6" 23 | env: TOX_POSARGS="-e py36-core" 24 | # 25 | # pypy3 testing 26 | # 27 | # core 28 | - python: "pypy3.5" 29 | env: TOX_POSARGS="-e pypy3-core" 30 | cache: 31 | - pip: true 32 | install: 33 | - travis_retry pip install pip setuptools --upgrade 34 | - travis_retry pip install tox 35 | before_script: 36 | - python --version 37 | - pip --version 38 | - pip freeze 39 | script: 40 | - tox $TOX_POSARGS 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Jason Carver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist= 3 | py{36}-core 4 | lint 5 | doctest 6 | 7 | [isort] 8 | combine_as_imports=True 9 | force_sort_within_sections=True 10 | include_trailing_comma=True 11 | known_third_party=hypothesis,pytest 12 | known_first_party=twig 13 | line_length=88 14 | multi_line_output=3 15 | use_parentheses=True 16 | force_grid_wrap=0 17 | 18 | [flake8] 19 | max-line-length= 100 20 | exclude= venv*,.tox,docs,build 21 | ignore= 22 | 23 | [testenv] 24 | usedevelop=True 25 | commands= 26 | core: pytest {posargs:tests/core} 27 | doctest: make -C {toxinidir}/docs doctest 28 | basepython = 29 | doctest: python 30 | py36: python3.6 31 | extras= 32 | test 33 | doctest: doc 34 | whitelist_externals=make 35 | 36 | [testenv:lint] 37 | basepython=python 38 | extras=lint 39 | commands= 40 | flake8 {toxinidir}/twig {toxinidir}/tests --exclude twig/contracts/ 41 | mypy --follow-imports=silent --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs --disallow-any-generics -p twig 42 | black --check --diff {toxinidir}/twig/ --check --diff {toxinidir}/tests/ 43 | isort --recursive {toxinidir}/twig {toxinidir}/tests 44 | -------------------------------------------------------------------------------- /twig/utils/compiler.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, Dict, Iterable, Tuple 3 | 4 | from eth_utils import to_dict, to_tuple 5 | 6 | from ethpm.tools import builder as b 7 | from vyper import compile_code 8 | 9 | 10 | @to_tuple 11 | def generate_inline_sources( 12 | compiler_output: Dict[str, Any], sources_dir: Path 13 | ) -> Iterable[Dict[str, str]]: 14 | for path in compiler_output.keys(): 15 | contract_type = path.split("/")[-1].split(".")[0] 16 | yield b.inline_source( 17 | contract_type, compiler_output, package_root_dir=sources_dir 18 | ) 19 | 20 | 21 | @to_tuple 22 | def generate_contract_types( 23 | compiler_output: Dict[str, Any] 24 | ) -> Iterable[Dict[str, Any]]: 25 | for path in compiler_output.keys(): 26 | contract_type = path.split("/")[-1].split(".")[0] 27 | yield b.contract_type(contract_type, compiler_output) 28 | 29 | 30 | @to_dict 31 | def create_raw_asset_data(source: str) -> Iterable[Tuple[str, Any]]: 32 | out = compile_code(source, ["abi", "bytecode", "bytecode_runtime"]) 33 | yield "abi", out["abi"] 34 | yield "evm", create_raw_bytecode_object(out) 35 | 36 | 37 | @to_dict 38 | def create_raw_bytecode_object( 39 | compiler_output: Dict[str, Any] 40 | ) -> Iterable[Tuple[str, Dict[str, Any]]]: 41 | yield "bytecode", {"object": compiler_output["bytecode"]} 42 | yield "deployedBytecode", {"object": compiler_output["bytecode_runtime"]} 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CURRENT_SIGN_SETTING := $(shell git config commit.gpgSign) 2 | 3 | .PHONY: clean-pyc clean-build docs 4 | 5 | help: 6 | @echo "clean-build - remove build artifacts" 7 | @echo "clean-pyc - remove Python file artifacts" 8 | @echo "lint - check style with flake8" 9 | @echo "test - run tests quickly with the default Python" 10 | @echo "testall - run tests on every Python version with tox" 11 | @echo "release - package and upload a release" 12 | @echo "dist - package" 13 | 14 | clean: clean-build clean-pyc 15 | 16 | clean-build: 17 | rm -fr build/ 18 | rm -fr dist/ 19 | rm -fr *.egg-info 20 | 21 | clean-pyc: 22 | find . -name '*.pyc' -exec rm -f {} + 23 | find . -name '*.pyo' -exec rm -f {} + 24 | find . -name '*~' -exec rm -f {} + 25 | 26 | lint: 27 | tox -elint 28 | 29 | lint-roll: 30 | isort --recursive twig tests 31 | $(MAKE) lint 32 | 33 | test: 34 | pytest tests 35 | 36 | test-all: 37 | tox 38 | 39 | build-docs: 40 | sphinx-apidoc -o docs/ . setup.py "*conftest*" 41 | $(MAKE) -C docs clean 42 | $(MAKE) -C docs html 43 | $(MAKE) -C docs doctest 44 | 45 | docs: build-docs 46 | open docs/_build/html/index.html 47 | 48 | linux-docs: build-docs 49 | xdg-open docs/_build/html/index.html 50 | 51 | release: clean 52 | git config commit.gpgSign true 53 | bumpversion $(bump) 54 | git push upstream && git push upstream --tags 55 | python setup.py sdist bdist_wheel 56 | twine upload dist/* 57 | git config commit.gpgSign "$(CURRENT_SIGN_SETTING)" 58 | 59 | dist: clean 60 | python setup.py sdist bdist_wheel 61 | ls -l dist 62 | -------------------------------------------------------------------------------- /.project-template/fill_template_vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | PROJECT_ROOT=$(dirname $(dirname $(python -c 'import os, sys; sys.stdout.write(os.path.realpath(sys.argv[1]))' "$0"))) 8 | 9 | echo "What is your python module name?" 10 | read MODULE_NAME 11 | 12 | echo "What is your pypi package name? (default: $MODULE_NAME)" 13 | read PYPI_INPUT 14 | PYPI_NAME=${PYPI_INPUT:-$MODULE_NAME} 15 | 16 | echo "What is your github project name? (default: $PYPI_NAME)" 17 | read REPO_INPUT 18 | REPO_NAME=${REPO_INPUT:-$PYPI_NAME} 19 | 20 | echo "What is your readthedocs.org project name? (default: $PYPI_NAME)" 21 | read RTD_INPUT 22 | RTD_NAME=${RTD_INPUT:-$PYPI_NAME} 23 | 24 | echo "What is your project name (ex: at the top of the README)? (default: $REPO_NAME)" 25 | read PROJECT_INPUT 26 | PROJECT_NAME=${PROJECT_INPUT:-$REPO_NAME} 27 | 28 | echo "What is a one-liner describing the project?" 29 | read SHORT_DESCRIPTION 30 | 31 | _replace() { 32 | local find_cmd=(find "$PROJECT_ROOT" ! -perm -u=x ! -path '*/.git/*' -type f) 33 | 34 | if [[ $(uname) == Darwin ]]; then 35 | "${find_cmd[@]}" -exec sed -i '' "$1" {} + 36 | else 37 | "${find_cmd[@]}" -exec sed -i "$1" {} + 38 | fi 39 | } 40 | _replace "s//$MODULE_NAME/g" 41 | _replace "s//$PYPI_NAME/g" 42 | _replace "s//$REPO_NAME/g" 43 | _replace "s//$RTD_NAME/g" 44 | _replace "s//$PROJECT_NAME/g" 45 | _replace "s//$SHORT_DESCRIPTION/g" 46 | 47 | mkdir -p "$PROJECT_ROOT/$MODULE_NAME" 48 | touch "$PROJECT_ROOT/$MODULE_NAME/__init__.py" 49 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | from twig import CONTRACTS_DIR 7 | from twig.backends import VyperBackend 8 | from twig.compiler import Compiler 9 | from web3 import Web3 10 | 11 | pytest_plugins = ["pytest_ethereum.plugins"] 12 | 13 | 14 | @pytest.fixture 15 | def w3(): 16 | w3 = Web3(Web3.EthereumTesterProvider()) 17 | w3.eth.defaultAccount = w3.eth.accounts[0] 18 | return w3 19 | 20 | 21 | @pytest.fixture 22 | def test_contracts_dir(): 23 | return Path(__file__).parent / "assets" 24 | 25 | 26 | @pytest.fixture 27 | def test_contracts_manifest(tmpdir, test_contracts_dir): 28 | vyper_backend = VyperBackend() 29 | p = tmpdir.mkdir("test_contracts") 30 | tmp = p.join("1.0.0.json") 31 | manifest = Compiler(test_contracts_dir, vyper_backend).get_simple_manifest( 32 | "twig", "1.0.0" 33 | ) 34 | tmp.write(json.dumps(manifest, sort_keys=True, separators=(",", ":"))) 35 | return Path(p) / "1.0.0.json" 36 | 37 | 38 | @pytest.fixture 39 | def twig_contracts_manifest(tmpdir): 40 | vyper_backend = VyperBackend() 41 | p = tmpdir.mkdir("twig_contracts") 42 | tmp = p.join("1.0.0.json") 43 | manifest = Compiler(CONTRACTS_DIR, vyper_backend).get_simple_manifest( 44 | "twig", "1.0.0" 45 | ) 46 | tmp.write(json.dumps(manifest, sort_keys=True, separators=(",", ":"))) 47 | return Path(p) / "1.0.0.json" 48 | 49 | 50 | @pytest.fixture 51 | def test_deployer(deployer, test_contracts_manifest): 52 | return deployer(test_contracts_manifest) 53 | 54 | 55 | @pytest.fixture 56 | def twig_deployer(deployer, twig_contracts_manifest): 57 | return deployer(twig_contracts_manifest) 58 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | 5 | Start your own Vyper project 6 | ---------------------------- 7 | 8 | Twig lets you write tests using `web3.py `__ and `pytest-ethereum `__ against your `Vyper `__ smart contracts. 9 | 10 | 11 | Twig setup 12 | ~~~~~~~~~~ 13 | 14 | - clone twig repo 15 | - ``python -m venv venv`` (note: requires >=python3.6) 16 | - ``. venv/bin/activate`` 17 | - ``pip install -e .`` 18 | 19 | 20 | Project setup 21 | ~~~~~~~~~~~~~ 22 | 23 | .. NOTE:: ``contracts/name_registry.vy`` and ``tests/test_name_registry.py`` have been included as examples on how to get started, but are safe to delete. 24 | 25 | - create your vyper contract file in ``contracts/`` 26 | (i.e. ``root/contracts/example.vy``) 27 | 28 | - write your vyper contract test file in ``tests/`` 29 | (i.e. ``root/tests/test_example.py``) 30 | 31 | - create your contract instance to test against using ``pytest-ethereum``'s `deployer None: 14 | self.sources_dir = sources_dir 15 | self.backend = backend 16 | self.output: Dict[str, Any] = None 17 | 18 | def compile(self) -> None: 19 | if self.output: 20 | raise CompilerError( 21 | "This instance of Compiler already contains compiler output." 22 | ) 23 | self.output = self.backend.compile(self.sources_dir) 24 | 25 | def get_contract_types(self) -> Dict[str, Any]: 26 | if not self.output: 27 | self.compile() 28 | return generate_contract_types(self.output) 29 | 30 | def get_source_tree(self) -> Dict[str, str]: 31 | if not self.output: 32 | self.compile() 33 | return generate_inline_sources(self.output, self.sources_dir) 34 | 35 | def get_simple_manifest(self, name: str, version: str) -> Manifest: 36 | composed_contract_types = self.get_contract_types() 37 | composed_inline_sources = self.get_source_tree() 38 | manifest = b.build( 39 | {}, 40 | b.package_name(name), 41 | b.version(version), 42 | b.manifest_version("2"), 43 | *composed_inline_sources, 44 | *composed_contract_types, 45 | b.validate(), 46 | ) 47 | return manifest 48 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | 3 | # heavily inspired by https://raw.githubusercontent.com/pinax/pinax-wiki/6bd2a99ab6f702e300d708532a6d1d9aa638b9f8/.circleci/config.yml 4 | 5 | common: &common 6 | working_directory: ~/repo 7 | steps: 8 | - checkout 9 | - run: 10 | name: merge pull request base 11 | command: ./.circleci/merge_pr.sh 12 | - run: 13 | name: merge pull request base (2nd try) 14 | command: ./.circleci/merge_pr.sh 15 | when: on_fail 16 | - run: 17 | name: merge pull request base (3nd try) 18 | command: ./.circleci/merge_pr.sh 19 | when: on_fail 20 | - restore_cache: 21 | keys: 22 | - cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} 23 | - run: 24 | name: install dependencies 25 | command: pip install --user tox 26 | - run: 27 | name: run tox 28 | command: ~/.local/bin/tox -r 29 | - save_cache: 30 | paths: 31 | - .hypothesis 32 | - .tox 33 | - ~/.cache/pip 34 | - ~/.local 35 | - ./eggs 36 | key: cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} 37 | 38 | jobs: 39 | doctest: 40 | <<: *common 41 | docker: 42 | - image: circleci/python:3.6 43 | environment: 44 | TOXENV: doctest 45 | lint: 46 | <<: *common 47 | docker: 48 | - image: circleci/python:3.6 49 | environment: 50 | TOXENV: lint 51 | py36-core: 52 | <<: *common 53 | docker: 54 | - image: circleci/python:3.6 55 | environment: 56 | TOXENV: py36-core 57 | workflows: 58 | version: 2 59 | test: 60 | jobs: 61 | - doctest 62 | - lint 63 | - py36-core 64 | -------------------------------------------------------------------------------- /tests/assets/crowdfund.vy: -------------------------------------------------------------------------------- 1 | # Setup private variables (only callable from within the contract) 2 | 3 | struct Funder : 4 | sender: address 5 | value: wei_value 6 | 7 | funders: map(int128, Funder) 8 | nextFunderIndex: int128 9 | beneficiary: address 10 | deadline: public(timestamp) 11 | goal: public(wei_value) 12 | refundIndex: int128 13 | timelimit: public(timedelta) 14 | 15 | 16 | # Setup global variables 17 | @public 18 | def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): 19 | self.beneficiary = _beneficiary 20 | self.deadline = block.timestamp + _timelimit 21 | self.timelimit = _timelimit 22 | self.goal = _goal 23 | 24 | 25 | # Participate in this crowdfunding campaign 26 | @public 27 | @payable 28 | def participate(): 29 | assert block.timestamp < self.deadline, "deadline not met (yet)" 30 | 31 | nfi: int128 = self.nextFunderIndex 32 | 33 | self.funders[nfi] = Funder({sender: msg.sender, value: msg.value}) 34 | self.nextFunderIndex = nfi + 1 35 | 36 | 37 | # Enough money was raised! Send funds to the beneficiary 38 | @public 39 | def finalize(): 40 | assert block.timestamp >= self.deadline, "deadline not met (yet)" 41 | assert self.balance >= self.goal, "invalid balance" 42 | 43 | selfdestruct(self.beneficiary) 44 | 45 | # Not enough money was raised! Refund everyone (max 30 people at a time 46 | # to avoid gas limit issues) 47 | @public 48 | def refund(): 49 | assert block.timestamp >= self.deadline and self.balance < self.goal 50 | 51 | ind: int128 = self.refundIndex 52 | 53 | for i in range(ind, ind + 30): 54 | if i >= self.nextFunderIndex: 55 | self.refundIndex = self.nextFunderIndex 56 | return 57 | 58 | send(self.funders[i].sender, self.funders[i].value) 59 | clear(self.funders[i]) 60 | 61 | self.refundIndex = ind + 30 62 | -------------------------------------------------------------------------------- /tests/core/test_compiler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | 5 | from twig.backends import VyperBackend 6 | from twig.compiler import Compiler 7 | 8 | logging.getLogger("evm").setLevel(logging.INFO) 9 | 10 | 11 | @pytest.fixture 12 | def compiler(test_contracts_dir): 13 | return Compiler(sources_dir=test_contracts_dir, backend=VyperBackend()) 14 | 15 | 16 | def test_compiler(compiler): 17 | assert compiler.output is None 18 | assert len(compiler.get_source_tree()) == 3 19 | assert len(compiler.get_contract_types()) == 3 20 | assert isinstance(compiler.backend, VyperBackend) 21 | assert compiler.output is not None 22 | auction_data = [ 23 | data["simple_open_auction"] 24 | for data in compiler.output.values() 25 | if data.get("simple_open_auction") 26 | ][0] 27 | assert "evm" in auction_data 28 | assert "bytecode" in auction_data["evm"] 29 | assert "deployedBytecode" in auction_data["evm"] 30 | 31 | 32 | def test_compiler_creates_valid_registry_package_and_deployment(test_deployer, w3): 33 | registry_package = test_deployer.deploy("registry") 34 | registry_contract = registry_package.deployments.get_instance("registry") 35 | registry_contract.functions.register(b"xxx", w3.eth.accounts[0]).transact() 36 | actual = registry_contract.functions.lookup(b"xxx").call() 37 | assert actual == w3.eth.accounts[0] 38 | 39 | 40 | def test_compiler_creates_valid_auction_package_and_deployment(test_deployer, w3): 41 | auction_package = test_deployer.deploy( 42 | "simple_open_auction", w3.eth.accounts[0], 100 43 | ) 44 | w3 = auction_package.w3 45 | auction_contract = auction_package.deployments.get_instance("simple_open_auction") 46 | auction_start = auction_contract.functions.auctionStart().call() 47 | auction_end = auction_contract.functions.auctionEnd().call() 48 | assert auction_end - auction_start == 100 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | .eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | venv* 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | # Complexity 40 | output/*.html 41 | output/*/index.html 42 | 43 | # Sphinx 44 | docs/_build 45 | docs/modules.rst 46 | docs/*.internal.rst 47 | docs/*.utils.rst 48 | docs/*._utils.* 49 | 50 | # Blockchain 51 | chains 52 | 53 | # Hypothese Property base testing 54 | .hypothesis 55 | 56 | # tox/pytest cache 57 | .cache 58 | 59 | # Test output logs 60 | logs 61 | ### JetBrains template 62 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 63 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 64 | 65 | # User-specific stuff: 66 | .idea/workspace.xml 67 | .idea/tasks.xml 68 | .idea/dictionaries 69 | .idea/vcs.xml 70 | .idea/jsLibraryMappings.xml 71 | 72 | # Sensitive or high-churn files: 73 | .idea/dataSources.ids 74 | .idea/dataSources.xml 75 | .idea/dataSources.local.xml 76 | .idea/sqlDataSources.xml 77 | .idea/dynamic.xml 78 | .idea/uiDesigner.xml 79 | 80 | # Gradle: 81 | .idea/gradle.xml 82 | .idea/libraries 83 | 84 | # Mongo Explorer plugin: 85 | .idea/mongoSettings.xml 86 | 87 | # VIM temp files 88 | *.swp 89 | 90 | ## File-based project format: 91 | *.iws 92 | 93 | ## Plugin-specific files: 94 | 95 | # IntelliJ 96 | /out/ 97 | 98 | # mpeltonen/sbt-idea plugin 99 | .idea_modules/ 100 | 101 | # JIRA plugin 102 | atlassian-ide-plugin.xml 103 | 104 | # Crashlytics plugin (for Android Studio and IntelliJ) 105 | com_crashlytics_export_strings.xml 106 | crashlytics.properties 107 | crashlytics-build.properties 108 | fabric.properties 109 | 110 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import ( 4 | setup, 5 | find_packages, 6 | ) 7 | 8 | extras_require = { 9 | 'test': [ 10 | "pytest>=3.6.0", 11 | "pytest-ethereum>=0.1.3a6,<1", 12 | "pytest-xdist", 13 | "tox>=2.9.1,<3", 14 | ], 15 | 'lint': [ 16 | "black>=18.6b4,<19", 17 | "flake8==3.4.1", 18 | "isort>=4.2.15,<5", 19 | "mypy<0.600", 20 | ], 21 | 'doc': [ 22 | "Sphinx>=1.6.5,<2", 23 | "sphinx_rtd_theme>=0.1.9", 24 | ], 25 | 'dev': [ 26 | "bumpversion>=0.5.3,<1", 27 | "pytest-watch>=4.1.0,<5", 28 | "wheel", 29 | "twine", 30 | "ipython", 31 | ], 32 | } 33 | 34 | extras_require['dev'] = ( 35 | extras_require['dev'] + 36 | extras_require['test'] + 37 | extras_require['lint'] + 38 | extras_require['doc'] 39 | ) 40 | 41 | setup( 42 | name='twig', 43 | # *IMPORTANT*: Don't manually change the version here. Use `make bump`, as described in readme 44 | version='0.1.0-alpha.5', 45 | description="""twig: A tool for Ethereum smart contract development.""", 46 | long_description_markdown_filename='README.md', 47 | author='Nick Gheorghita', 48 | author_email='nickg@ethereum.org', 49 | url='https://github.com/ethereum/twig', 50 | include_package_data=True, 51 | install_requires=[ 52 | "eth-utils>=1.4.1,<2", 53 | "ethpm>=0.1.4a10,<1", 54 | "web3[tester]>=5.0.0a3,<6", 55 | "vyper>=0.1.0b6,<1", 56 | ], 57 | setup_requires=['setuptools-markdown'], 58 | python_requires='>=3.6, <4', 59 | extras_require=extras_require, 60 | py_modules=['twig'], 61 | license="MIT", 62 | zip_safe=False, 63 | keywords='ethereum', 64 | packages=find_packages(exclude=["tests", "tests.*"]), 65 | classifiers=[ 66 | 'Development Status :: 2 - Pre-Alpha', 67 | 'Intended Audience :: Developers', 68 | 'License :: OSI Approved :: MIT License', 69 | 'Natural Language :: English', 70 | 'Programming Language :: Python :: 3', 71 | 'Programming Language :: Python :: 3.6', 72 | ], 73 | ) 74 | -------------------------------------------------------------------------------- /tests/assets/simple_open_auction.vy: -------------------------------------------------------------------------------- 1 | # Open Auction 2 | 3 | # Auction params 4 | # Beneficiary receives money from the highest bidder 5 | beneficiary: public(address) 6 | auctionStart: public(timestamp) 7 | auctionEnd: public(timestamp) 8 | 9 | # Current state of auction 10 | highestBidder: public(address) 11 | highestBid: public(wei_value) 12 | 13 | # Set to true at the end, disallows any change 14 | ended: public(bool) 15 | 16 | # Keep track of refunded bids so we can follow the withdraw pattern 17 | pendingReturns: public(map(address, wei_value)) 18 | 19 | # Create a simple auction with `_bidding_time` 20 | # seconds bidding time on behalf of the 21 | # beneficiary address `_beneficiary`. 22 | @public 23 | def __init__(_beneficiary: address, _bidding_time: timedelta): 24 | self.beneficiary = _beneficiary 25 | self.auctionStart = block.timestamp 26 | self.auctionEnd = self.auctionStart + _bidding_time 27 | 28 | # Bid on the auction with the value sent 29 | # together with this transaction. 30 | # The value will only be refunded if the 31 | # auction is not won. 32 | @public 33 | @payable 34 | def bid(): 35 | # Check if bidding period is over. 36 | assert block.timestamp < self.auctionEnd 37 | # Check if bid is high enough 38 | assert msg.value > self.highestBid 39 | # Track the refund for the previous high bidder 40 | self.pendingReturns[self.highestBidder] += self.highestBid 41 | # Track new high bid 42 | self.highestBidder = msg.sender 43 | self.highestBid = msg.value 44 | 45 | # Withdraw a previously refunded bid. The withdraw pattern is 46 | # used here to avoid a security issue. If refunds were directly 47 | # sent as part of bid(), a malicious bidding contract could block 48 | # those refunds and thus block new higher bids from coming in. 49 | @public 50 | def withdraw(): 51 | pending_amount: wei_value = self.pendingReturns[msg.sender] 52 | self.pendingReturns[msg.sender] = 0 53 | send(msg.sender, pending_amount) 54 | 55 | # End the auction and send the highest bid 56 | # to the beneficiary. 57 | @public 58 | def endAuction(): 59 | # It is a good guideline to structure functions that interact 60 | # with other contracts (i.e. they call functions or send Ether) 61 | # into three phases: 62 | # 1. checking conditions 63 | # 2. performing actions (potentially changing conditions) 64 | # 3. interacting with other contracts 65 | # If these phases are mixed up, the other contract could call 66 | # back into the current contract and modify the state or cause 67 | # effects (Ether payout) to be performed multiple times. 68 | # If functions called internally include interaction with external 69 | # contracts, they also have to be considered interaction with 70 | # external contracts. 71 | 72 | # 1. Conditions 73 | # Check if auction endtime has been reached 74 | assert block.timestamp >= self.auctionEnd 75 | # Check if this function has already been called 76 | assert not self.ended 77 | 78 | # 2. Effects 79 | self.ended = True 80 | 81 | # 3. Interaction 82 | send(self.beneficiary, self.highestBid) 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # twig 2 | 3 | [![Join the chat at https://gitter.im/ethereum/twig](https://badges.gitter.im/ethereum/twig.svg)](https://gitter.im/ethereum/twig?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Build Status](https://circleci.com/gh/ethereum/twig.svg?style=shield)](https://circleci.com/gh/ethereum/twig) 5 | [![PyPI version](https://badge.fury.io/py/twig.svg)](https://badge.fury.io/py/twig) 6 | [![Python versions](https://img.shields.io/pypi/pyversions/twig.svg)](https://pypi.python.org/pypi/twig) 7 | [![Docs build](https://readthedocs.org/projects/twig-eth/badge/?version=latest)](https://twig-eth.readthedocs.io/en/latest/) 8 | 9 | 10 | A tool for Ethereum smart contract development. 11 | 12 | Read more in the [documentation on ReadTheDocs](https://twig-eth.readthedocs.io/). [View the change log](https://twig-eth.readthedocs.io/en/latest/releases.html). 13 | 14 | ## Quickstart 15 | 16 | ```sh 17 | pip install twig 18 | ``` 19 | 20 | ## Developer Setup 21 | 22 | If you would like to hack on twig, please check out the 23 | [Ethereum Development Tactical Manual](https://github.com/pipermerriam/ethereum-dev-tactical-manual) 24 | for information on how we do: 25 | 26 | - Testing 27 | - Pull Requests 28 | - Code Style 29 | - Documentation 30 | 31 | ### Development Environment Setup 32 | 33 | You can set up your dev environment with: 34 | 35 | ```sh 36 | git clone git@github.com:ethereum/twig.git 37 | cd twig 38 | virtualenv -p python3 venv 39 | . venv/bin/activate 40 | pip install -e .[dev] 41 | ``` 42 | 43 | ### Testing Setup 44 | 45 | During development, you might like to have tests run on every file save. 46 | 47 | Show flake8 errors on file change: 48 | 49 | ```sh 50 | # Test flake8 51 | when-changed -v -s -r -1 twig/ tests/ -c "clear; flake8 twig tests && echo 'flake8 success' || echo 'error'" 52 | ``` 53 | 54 | Run multi-process tests in one command, but without color: 55 | 56 | ```sh 57 | # in the project root: 58 | pytest --numprocesses=4 --looponfail --maxfail=1 59 | # the same thing, succinctly: 60 | pytest -n 4 -f --maxfail=1 61 | ``` 62 | 63 | Run in one thread, with color and desktop notifications: 64 | 65 | ```sh 66 | cd venv 67 | ptw --onfail "notify-send -t 5000 'Test failure ⚠⚠⚠⚠⚠' 'python 3 test on twig failed'" ../tests ../twig 68 | ``` 69 | 70 | ### Release setup 71 | 72 | For Debian-like systems: 73 | ``` 74 | apt install pandoc 75 | ``` 76 | 77 | To release a new version: 78 | 79 | ```sh 80 | make release bump=$$VERSION_PART_TO_BUMP$$ 81 | ``` 82 | 83 | #### How to bumpversion 84 | 85 | The version format for this repo is `{major}.{minor}.{patch}` for stable, and 86 | `{major}.{minor}.{patch}-{stage}.{devnum}` for unstable (`stage` can be alpha or beta). 87 | 88 | To issue the next version in line, specify which part to bump, 89 | like `make release bump=minor` or `make release bump=devnum`. This is typically done from the 90 | master branch, except when releasing a beta (in which case the beta is released from master, 91 | and the previous stable branch is released from said branch). To include changes made with each 92 | release, update "docs/releases.rst" with the changes, and apply commit directly to master 93 | before release. 94 | 95 | If you are in a beta version, `make release bump=stage` will switch to a stable. 96 | 97 | To issue an unstable version when the current version is stable, specify the 98 | new version explicitly, like `make release bump="--new-version 4.0.0-alpha.1 devnum"` 99 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/web3.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/web3.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/web3" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/web3" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # twig documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Oct 16 20:43:24 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | #sys.path.insert(0, os.path.abspath('.')) 19 | 20 | import os 21 | 22 | DIR = os.path.dirname('__file__') 23 | with open (os.path.join(DIR, '../setup.py'), 'r') as f: 24 | for line in f: 25 | if 'version=' in line: 26 | setup_version = line.split('\'')[1] 27 | break 28 | 29 | # -- General configuration ------------------------------------------------ 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | #needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | 'sphinx.ext.autodoc', 39 | 'sphinx.ext.doctest', 40 | 'sphinx.ext.intersphinx', 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix of source filenames. 47 | source_suffix = '.rst' 48 | 49 | # The encoding of source files. 50 | #source_encoding = 'utf-8-sig' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = 'twig' 57 | copyright = '2018, Jason Carver, Piper Merriam' 58 | 59 | __version__ = setup_version 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = '.'.join(__version__.split('.')[:2]) 66 | # The full version, including alpha/beta/rc tags. 67 | release = __version__ 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | #language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | #today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | #today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = [ 82 | '_build', 83 | 'modules.rst', 84 | ] 85 | 86 | # The reST default role (used for this markup: `text`) to use for all 87 | # documents. 88 | #default_role = None 89 | 90 | # If true, '()' will be appended to :func: etc. cross-reference text. 91 | #add_function_parentheses = True 92 | 93 | # If true, the current module name will be prepended to all description 94 | # unit titles (such as .. function::). 95 | #add_module_names = True 96 | 97 | # If true, sectionauthor and moduleauthor directives will be shown in the 98 | # output. They are ignored by default. 99 | #show_authors = False 100 | 101 | # The name of the Pygments (syntax highlighting) style to use. 102 | pygments_style = 'sphinx' 103 | 104 | # A list of ignored prefixes for module index sorting. 105 | #modindex_common_prefix = [] 106 | 107 | # If true, keep warnings as "system message" paragraphs in the built documents. 108 | #keep_warnings = False 109 | 110 | 111 | # -- Options for HTML output ---------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | html_theme = 'sphinx_rtd_theme' 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | #html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as html_title. 129 | #html_short_title = None 130 | 131 | # The name of an image file (relative to this directory) to place at the top 132 | # of the sidebar. 133 | #html_logo = None 134 | 135 | # The name of an image file (within the static path) to use as favicon of the 136 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 137 | # pixels large. 138 | #html_favicon = None 139 | 140 | # Add any paths that contain custom static files (such as style sheets) here, 141 | # relative to this directory. They are copied after the builtin static files, 142 | # so a file named "default.css" will overwrite the builtin "default.css". 143 | html_static_path = ['_static'] 144 | 145 | # Add any extra paths that contain custom files (such as robots.txt or 146 | # .htaccess) here, relative to this directory. These files are copied 147 | # directly to the root of the documentation. 148 | #html_extra_path = [] 149 | 150 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 151 | # using the given strftime format. 152 | #html_last_updated_fmt = '%b %d, %Y' 153 | 154 | # If true, SmartyPants will be used to convert quotes and dashes to 155 | # typographically correct entities. 156 | #html_use_smartypants = True 157 | 158 | # Custom sidebar templates, maps document names to template names. 159 | #html_sidebars = {} 160 | 161 | # Additional templates that should be rendered to pages, maps page names to 162 | # template names. 163 | #html_additional_pages = {} 164 | 165 | # If false, no module index is generated. 166 | #html_domain_indices = True 167 | 168 | # If false, no index is generated. 169 | #html_use_index = True 170 | 171 | # If true, the index is split into individual pages for each letter. 172 | #html_split_index = False 173 | 174 | # If true, links to the reST sources are added to the pages. 175 | #html_show_sourcelink = True 176 | 177 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 178 | #html_show_sphinx = True 179 | 180 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 181 | #html_show_copyright = True 182 | 183 | # If true, an OpenSearch description file will be output, and all pages will 184 | # contain a tag referring to it. The value of this option must be the 185 | # base URL from which the finished HTML is served. 186 | #html_use_opensearch = '' 187 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 189 | #html_file_suffix = None 190 | 191 | # Output file base name for HTML help builder. 192 | htmlhelp_basename = 'twigdoc' 193 | 194 | 195 | # -- Options for LaTeX output --------------------------------------------- 196 | 197 | latex_elements = { 198 | # The paper size ('letterpaper' or 'a4paper'). 199 | #'papersize': 'letterpaper', 200 | 201 | # The font size ('10pt', '11pt' or '12pt'). 202 | #'pointsize': '10pt', 203 | 204 | # Additional stuff for the LaTeX preamble. 205 | #'preamble': '', 206 | } 207 | 208 | # Grouping the document tree into LaTeX files. List of tuples 209 | # (source start file, target name, title, 210 | # author, documentclass [howto, manual, or own class]). 211 | latex_documents = [ 212 | ('index', 'twig.tex', 'twig Documentation', 213 | 'Jason Carver', 'manual'), 214 | ] 215 | 216 | # The name of an image file (relative to this directory) to place at the top of 217 | # the title page. 218 | #latex_logo = None 219 | 220 | # For "manual" documents, if this is true, then toplevel headings are parts, 221 | # not chapters. 222 | #latex_use_parts = False 223 | 224 | # If true, show page references after internal links. 225 | #latex_show_pagerefs = False 226 | 227 | # If true, show URL addresses after external links. 228 | #latex_show_urls = False 229 | 230 | # Documents to append as an appendix to all manuals. 231 | #latex_appendices = [] 232 | 233 | # If false, no module index is generated. 234 | #latex_domain_indices = True 235 | 236 | 237 | # -- Options for manual page output --------------------------------------- 238 | 239 | # One entry per manual page. List of tuples 240 | # (source start file, name, description, authors, manual section). 241 | man_pages = [ 242 | ('index', 'twig', 'twig Documentation', 243 | ['Jason Carver'], 1) 244 | ] 245 | 246 | # If true, show URL addresses after external links. 247 | #man_show_urls = False 248 | 249 | 250 | # -- Options for Texinfo output ------------------------------------------- 251 | 252 | # Grouping the document tree into Texinfo files. List of tuples 253 | # (source start file, target name, title, author, 254 | # dir menu entry, description, category) 255 | texinfo_documents = [ 256 | ('index', 'twig', 'twig Documentation', 257 | 'Jason Carver', 'twig', 'A tool for Ethereum smart contract development.', 258 | 'Miscellaneous'), 259 | ] 260 | 261 | # Documents to append as an appendix to all manuals. 262 | #texinfo_appendices = [] 263 | 264 | # If false, no module index is generated. 265 | #texinfo_domain_indices = True 266 | 267 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 268 | #texinfo_show_urls = 'footnote' 269 | 270 | # If true, do not generate a @detailmenu in the "Top" node's menu. 271 | #texinfo_no_detailmenu = False 272 | 273 | # -- Intersphinx configuration ------------------------------------------------ 274 | 275 | intersphinx_mapping = { 276 | 'python': ('https://docs.python.org/3.5', None), 277 | } 278 | 279 | # -- Doctest configuration ---------------------------------------- 280 | 281 | import doctest 282 | 283 | doctest_default_flags = (0 284 | | doctest.DONT_ACCEPT_TRUE_FOR_1 285 | | doctest.ELLIPSIS 286 | | doctest.IGNORE_EXCEPTION_DETAIL 287 | | doctest.NORMALIZE_WHITESPACE 288 | ) 289 | --------------------------------------------------------------------------------