diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..ba2dab8ca --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,45 @@ +name: "Build workflow" +on: + pull_request: + push: + branches: + - master +jobs: + build-test: + runs-on: ubuntu-20.04 + container: python:${{ matrix.python }}-slim + strategy: + matrix: + python: ['3.6', '3.7', '3.8', '3.9'] + steps: + - run: python3 --version + - name: Check out code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install pip dependencies + run: pip install -r requirements.txt + - name: Build and Test + run: | + pytest + mypy pyteal + python3 -c "import pyteal" scripts/generate_init.py --check + black --check . + upload-to-pypi: + runs-on: ubuntu-20.04 + needs: ['build-test'] + if: ${{ github.event_name == 'push' && contains(github.ref, 'master') && startsWith(github.ref, 'refs/tags') }} + steps: + - name: Check out code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install dependencies + run: pip install wheel + - name: Build package + run: python setup.py sdist bdist_wheel + - name: Release + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b844389a3..000000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: python - -install: - - pip install -r requirements.txt - -script: - - pytest - - mypy pyteal - - python3 -c "import pyteal" scripts/generate_init.py --check - - black --check . - -jobs: - include: - - stage: Testing - name: Python 3.6 - python: "3.6" - - name: Python 3.7 - python: "3.7" - - name: Python 3.8 - python: "3.8" - - name: Python 3.9 - python: "3.9" - - stage: Release - name: PyPi release - python: "3.9" - script: skip - if: tag IS present AND type = push - deploy: - provider: pypi - username: __token__ - on: - tags: true - branch: master - distributions: "sdist bdist_wheel" diff --git a/pyteal/ast/app.py b/pyteal/ast/app.py index b8c49ab45..23e265094 100644 --- a/pyteal/ast/app.py +++ b/pyteal/ast/app.py @@ -93,7 +93,7 @@ def optedIn(cls, account: Expr, app: Expr) -> "App": Txn.Accounts or is Txn.Sender, must be evaluated to bytes). app: An index into Txn.ForeignApps that corresponds to the application to read from, must be evaluated to uint64 (or, since v4, an application id that appears in - Txn.ForeignApps or is the CurrentApplicationID, must be evaluated to bytes). + Txn.ForeignApps or is the CurrentApplicationID, must be evaluated to int). """ require_type(account, TealType.anytype) require_type(app, TealType.uint64) @@ -123,7 +123,7 @@ def localGetEx(cls, account: Expr, app: Expr, key: Expr) -> MaybeValue: Txn.Accounts or is Txn.Sender, must be evaluated to bytes). app: An index into Txn.ForeignApps that corresponds to the application to read from, must be evaluated to uint64 (or, since v4, an application id that appears in - Txn.ForeignApps or is the CurrentApplicationID, must be evaluated to bytes). + Txn.ForeignApps or is the CurrentApplicationID, must be evaluated to int). key: The key to read from the account's local state. Must evaluate to bytes. """ require_type(account, TealType.anytype) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 136be53c9..0bf688d28 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -49,6 +49,16 @@ def __init__( ) ) + for var, var_type in implementation.__annotations__.items(): + if var_type is not Expr: + stub = "Return" if var == "return" else ("parameter " + var) + + raise TealInputError( + "Function has {} of disallowed type {}. Only type Expr is allowed".format( + stub, var_type + ) + ) + self.implementation = implementation self.implementationParams = sig.parameters self.returnType = returnType diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index a283b3a55..c60da0907 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -28,6 +28,18 @@ def fn10Args(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): lam2Args = lambda a1, a2: Return() lam10Args = lambda a1, a2, a3, a4, a5, a6, a7, a8, a9, a10: Return() + def fnWithExprAnnotations(a: Expr, b: Expr) -> Expr: + return Return() + + def fnWithOnlyReturnExprAnnotations(a, b) -> Expr: + return Return() + + def fnWithOnlyArgExprAnnotations(a: Expr, b: Expr): + return Return() + + def fnWithPartialExprAnnotations(a, b: Expr) -> Expr: + return Return() + cases = ( (fn0Args, 0, "fn0Args"), (fn1Args, 1, "fn1Args"), @@ -37,6 +49,10 @@ def fn10Args(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): (lam1Args, 1, ""), (lam2Args, 2, ""), (lam10Args, 10, ""), + (fnWithExprAnnotations, 2, "fnWithExprAnnotations"), + (fnWithOnlyReturnExprAnnotations, 2, "fnWithOnlyReturnExprAnnotations"), + (fnWithOnlyArgExprAnnotations, 2, "fnWithOnlyArgExprAnnotations"), + (fnWithPartialExprAnnotations, 2, "fnWithPartialExprAnnotations"), ) for (fn, numArgs, name) in cases: @@ -72,18 +88,43 @@ def fnWithKeywordArgs(a, *, b): def fnWithVariableArgs(a, *b): return Return() + def fnWithNonExprReturnAnnotation(a, b) -> TealType.uint64: + return Return() + + def fnWithNonExprParamAnnotation(a, b: TealType.uint64): + return Return() + cases = ( - 1, - None, - fnWithDefaults, - fnWithKeywordArgs, - fnWithVariableArgs, + (1, "TealInputError('Input to SubroutineDefinition is not callable'"), + (None, "TealInputError('Input to SubroutineDefinition is not callable'"), + ( + fnWithDefaults, + "TealInputError('Function has a parameter with a default value, which is not allowed in a subroutine: b'", + ), + ( + fnWithKeywordArgs, + "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type KEYWORD_ONLY'", + ), + ( + fnWithVariableArgs, + "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type VAR_POSITIONAL'", + ), + ( + fnWithNonExprReturnAnnotation, + "TealInputError('Function has Return of disallowed type TealType.uint64. Only type Expr is allowed'", + ), + ( + fnWithNonExprParamAnnotation, + "TealInputError('Function has parameter b of disallowed type TealType.uint64. Only type Expr is allowed'", + ), ) - for case in cases: - with pytest.raises(TealInputError): + for case, msg in cases: + with pytest.raises(TealInputError) as e: SubroutineDefinition(case, TealType.none) + assert msg in str(e), "failed for case [{}]".format(case) + def test_subroutine_declaration(): cases = (