Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into refactor/use-string-c…
Browse files Browse the repository at this point in the history
…onst-for-arc4-string

# Conflicts:
#	examples/sizes.txt
#	src/puya/ir/builder/main.py
#	test_cases/arc4_types/out/Arc4MutableParamsContract.approval.mir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_1.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_10.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_11.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_12.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_13.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_2.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_3.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_4.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_5.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_6.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_7.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_8.ir
#	test_cases/arc4_types/out/Arc4MutableParamsContract.ssa.opt_pass_9.ir
#	test_cases/arc4_types/out/module.awst
#	test_cases/arc4_types/out/mutable_params.O0.log
#	test_cases/arc4_types/out/tuples.O0.log
#	test_cases/arc4_types/out_unoptimized/Arc4MutableParamsContract.approval.teal
#	test_cases/arc4_types/out_unoptimized/Arc4MutableParamsContract.destructured.ir
#	test_cases/arc4_types/puya.log
#	test_cases/compile/out_unoptimized/HelloFactory.approval.teal
#	test_cases/typed_abi_call/out_unoptimized/Greeter.approval.teal
  • Loading branch information
tristanmenzel committed Nov 11, 2024
2 parents cf34d34 + fe7a0ea commit 352aaf9
Show file tree
Hide file tree
Showing 298 changed files with 24,343 additions and 6,596 deletions.
1 change: 0 additions & 1 deletion docs/compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ puyapy [-h] [--version] [-O {0,1,2}]
| `--output-arc32`, `--no-output-arc32` | Output {contract}.arc32.json ARC-32 app spec file if the contract is an ARC-4 contract | `True` |
| `--output-client`, `--no-output-client` | Output Algorand Python contract client for typed ARC4 ABI calls | `False` |
| `--output-bytecode`, `--no-output-bytecode` | Output AVM bytecode | `False` |
| `--match-algod-bytecode` | When outputting bytecode (via `--output-bytecode` or compiled programs), ensure bytecode matches algod output, by disabling additional optimizations | False |
| `--out-dir OUT_DIR` | The path for outputting artefacts | Same folder as contract |
| `--log-level {notset,debug,info,warning,error,critical}` | Minimum level to log to console | `info` |
| `-g {0,1,2}`, `--debug-level {0,1,2}` | Output debug information level<br /> `0` = No debug annotations <br /> `1` = Output debug annotations <br /> `2` = Reserved for future use, currently the same as `1` | `1` |
Expand Down
1 change: 1 addition & 0 deletions docs/language-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ lg-ops
lg-opcode-budget
lg-arc4
lg-arc28
lg-calling-apps
lg-compile
lg-unsupported-python-features
```
88 changes: 21 additions & 67 deletions docs/lg-arc4.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ from algopy import arc4
FourBytes: t.TypeAlias = arc4.StaticArray[arc4.Byte, t.Literal[4]]
```


### Address
**Type:** `algopy.arc4.Address`
**Encoding:** A byte array 32 bytes long
**Native equivalent:** [`algopy.Account`](#algopy.Account)

Address represents an Algorand address's public key, and can be used instead of `algopy.Account` when needing to
reference an address in an ARC4 struct, tuple or return type. It is a subclass of `arc4.StaticArray[arc4.Byte, typing.Literal[32]]`

### Dynamic arrays

**Type:** `algopy.arc4.DynamicArray`
Expand Down Expand Up @@ -138,9 +147,11 @@ ARC4 Tuples are immutable statically sized arrays of mixed item types. Item type

**Type:** `algopy.arc4.Struct`
**Encoding:** See [ARC4 Container Packing](#ARC4-Container-Packing)
**Native equivalent:** _none_
**Native equivalent:** `typing.NamedTuple`

ARC4 Structs are mutable named tuples. Items can be accessed and mutated via names instead of indexes.
ARC4 Structs are named tuples. The class keyword `frozen` can be used to indicate if a struct can be mutated.
Items can be accessed and mutated via names instead of indexes. Structs do not have a `.native` property,
but a NamedTuple can be used in ABI methods are will be encoded/decode to an ARC4 struct automatically.

```python
import typing
Expand All @@ -149,7 +160,7 @@ from algopy import arc4

Decimal: typing.TypeAlias = arc4.UFixedNxM[typing.Literal[64], typing.Literal[9]]

class Vector(arc4.Struct, kw_only=True):
class Vector(arc4.Struct, kw_only=True, frozen=True):
x: Decimal
y: Decimal
```
Expand Down Expand Up @@ -200,70 +211,13 @@ class Reference(ARC4Contract):
...

```
### Mutability

To ensure semantic compatability the compiler will also check for any usages of mutable ARC4 types (arrays and structs) and ensure that any additional references are copied using the `.copy()` method.

## Typed clients

[`arc4.abi_call`](#algopy.arc4.abi_call) can be used to do type safe calls to an ABI method of another contract, these
calls can be expressed in a few ways.

### ARC4Contract method

An ARC4Contract method written in Algorand Python can be referenced directly e.g.

```python
from algopy import arc4, subroutine

class HelloWorldContract(arc4.ARC4Contract):

def hello(self, name: arc4.String) -> arc4.String: ... # implementation omitted

@subroutine
def call_another_contract() -> None:
result, txn = arc4.abi_call(HelloWorldContract.hello, arc4.String("World"), app=...)
assert result == "Hello, World"
```

### ARC4Client method

A ARC4Client client represents the ARC4 abimethods of a smart contract and can be used to call abimethods in a type safe way

ARC4Client's can be produced by using `puyapy --output-client=True` when compiling a smart contract
(this would be useful if you wanted to publish a client for consumption by other smart contracts)
An ARC4Client can also be be generated from an ARC-32 application.json using `puyapy-clientgen`
e.g. `puyapy-clientgen examples/hello_world_arc4/out/HelloWorldContract.arc32.json`, this would be
the recommended approach for calling another smart contract that is not written in Algorand Python or does not provide the source

```python
from algopy import arc4, subroutine

class HelloWorldClient(arc4.ARC4Client):

def hello(self, name: arc4.String) -> arc4.String: ...

@subroutine
def call_another_contract() -> None:
# can reference another algopy contract method
result, txn = arc4.abi_call(HelloWorldClient.hello, arc4.String("World"), app=...)
assert result == "Hello, World"
```

### Method signature or name

An ARC4 method selector can be used e.g. `"hello(string)string` along with a type index to specify the return type.
Additionally just a name can be provided and the method signature will be inferred e.g.
Python values are passed by reference, and when an object (eg. an array or struct) is mutated in one place, all references to that object see the mutated version. In Python this is managed via the heap.
In Algorand Python these mutable values are instead stored on the stack, so when an additional reference is made (i.e. by assigning to another variable) a copy is added to the stack.
Which means if one reference is mutated, the other references would not see the change.
In order to keep the semantics the same, the compiler forces the addition of `.copy()` each time a new reference to the same object to match what will happen on the AVM.

```python
from algopy import arc4, subroutine


@subroutine
def call_another_contract() -> None:
# can reference a method selector
result, txn = arc4.abi_call[arc4.String]("hello(string)string", arc4.String("Algo"), app=...)
assert result == "Hello, Algo"

# can reference a method name, the method selector is inferred from arguments and return type
result, txn = arc4.abi_call[arc4.String]("hello", "There", app=...)
assert result == "Hello, There"
```
Struct types can be indicated as `frozen` which will eliminate the need for a `.copy()` as long as the struct also contains no mutable fields (such as arrays or another mutable struct)
142 changes: 142 additions & 0 deletions docs/lg-calling-apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Calling other applications

The preferred way to call other smart contracts is using [`algopy.arc4.abi_call`](#algopyarc4abi_call), [`algopy.arc4.arc4_create`](#algopyarc4arc4_create) or
[`algopy.arc4.arc4_update`](#algopyarc4arc4_update). These methods support type checking and encoding of arguments, decoding of results, group transactions,
and in the case of `arc4_create` and `arc4_update` automatic inclusion of approval and clear state programs.

## `algopy.arc4.abi_call`

[`algopy.arc4.abi_call`](#algopy.arc4.abi_call) can be used to call other ARC4 contracts, the first argument should refer to
an ARC4 method either by referencing an Algorand Python [`algopy.arc4.ARC4Contract`](#algopy.arc4.ARC4Contract) method,
an [`algopy.arc4.ARC4Client`](#algopy.arc4.ARC4Client) method generated from an ARC-32 app spec, or a string representing
the ARC4 method signature or name.
The following arguments should then be the arguments required for the call, these arguments will be type checked and converted where appropriate.
Any other related transaction parameters such as `app_id`, `fee` etc. can also be provided as keyword arguments.

If the ARC4 method returns an ARC4 result then the result will be a tuple of the ARC4 result and the inner transaction.
If the ARC4 method does not return a result, or if the result type is not fully qualified then just the inner transaction is returned.

```python
from algopy import Application, ARC4Contract, String, arc4, subroutine

class HelloWorld(ARC4Contract):

@arc4.abimethod()
def greet(self, name: String) -> String:
return "Hello " + name

@subroutine
def call_existing_application(app: Application) -> None:
greeting, greet_txn = arc4.abi_call(HelloWorld.greet, "there", app_id=app)

assert greeting == "Hello there"
assert greet_txn.app_id == 1234
```


### Alternative ways to use `arc4.abi_call`

#### ARC4Client method

A ARC4Client client represents the ARC4 abimethods of a smart contract and can be used to call abimethods in a type safe way

ARC4Client's can be produced by using `puyapy --output-client=True` when compiling a smart contract
(this would be useful if you wanted to publish a client for consumption by other smart contracts)
An ARC4Client can also be be generated from an ARC-32 application.json using `puyapy-clientgen`
e.g. `puyapy-clientgen examples/hello_world_arc4/out/HelloWorldContract.arc32.json`, this would be
the recommended approach for calling another smart contract that is not written in Algorand Python or does not provide the source

```python
from algopy import arc4, subroutine

class HelloWorldClient(arc4.ARC4Client):

def hello(self, name: arc4.String) -> arc4.String: ...

@subroutine
def call_another_contract() -> None:
# can reference another algopy contract method
result, txn = arc4.abi_call(HelloWorldClient.hello, arc4.String("World"), app=...)
assert result == "Hello, World"
```

#### Method signature or name

An ARC4 method selector can be used e.g. `"hello(string)string` along with a type index to specify the return type.
Additionally just a name can be provided and the method signature will be inferred e.g.

```python
from algopy import arc4, subroutine


@subroutine
def call_another_contract() -> None:
# can reference a method selector
result, txn = arc4.abi_call[arc4.String]("hello(string)string", arc4.String("Algo"), app=...)
assert result == "Hello, Algo"

# can reference a method name, the method selector is inferred from arguments and return type
result, txn = arc4.abi_call[arc4.String]("hello", "There", app=...)
assert result == "Hello, There"
```


## `algopy.arc4.arc4_create`

[`algopy.arc4.arc4_create`](#algopy.arc4.arc4_create) can be used to create ARC4 applications, and will automatically populate required fields for app creation (such as approval program, clear state program, and global/local state allocation).

Like [`algopy.arc4.abi_call`](lg-transactions.md#arc4-application-calls) it handles ARC4 arguments and provides ARC4 return values.

If the compiled programs and state allocation fields need to be customized (for example due to [template variables](#within-other-contracts)),
this can be done by passing a [`algopy.CompiledContract`](#algopy.CompiledContract) via the `compiled` keyword argument.

```python
from algopy import ARC4Contract, String, arc4, subroutine

class HelloWorld(ARC4Contract):

@arc4.abimethod()
def greet(self, name: String) -> String:
return "Hello " + name

@subroutine
def create_new_application() -> None:
hello_world_app = arc4.arc4_create(HelloWorld).created_app

greeting, _txn = arc4.abi_call(HelloWorld.greet, "there", app_id=hello_world_app)

assert greeting == "Hello there"
```

## `algopy.arc4.arc4_update`

[`algopy.arc4.arc4_update`](#algopy.arc4.arc4_update) is used to update an existing ARC4 application and will automatically populate the required approval and clear state program fields.

Like [`algopy.arc4.abi_call`](lg-transactions.md#arc4-application-calls) it handles ARC4 arguments and provides ARC4 return values.

If the compiled programs need to be customized (for example due to (for example due to [template variables](#within-other-contracts)),
this can be done by passing a [`algopy.CompiledContract`](#algopy.CompiledContract) via the `compiled` keyword argument.

```python
from algopy import Application, ARC4Contract, String, arc4, subroutine

class NewApp(ARC4Contract):

@arc4.abimethod()
def greet(self, name: String) -> String:
return "Hello " + name

@subroutine
def update_existing_application(existing_app: Application) -> None:
hello_world_app = arc4.arc4_update(NewApp, app_id=existing_app)

greeting, _txn = arc4.abi_call(NewApp.greet, "there", app_id=hello_world_app)

assert greeting == "Hello there"
```

## Using `itxn.ApplicationCall`

If the application being called is not an ARC4 contract, or an application specification is not available,
then [`algopy.itxn.ApplicationCall`](#algopy.itxn.ApplicationCall) can be used. This approach is generally more verbose
than the above approaches, so should only be used if required. See [here](./lg-transactions.md#create-an-arc4-application-and-then-call-it) for an example
65 changes: 0 additions & 65 deletions docs/lg-compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ Once compiled, this bytecode can be utilized to construct AVM Application Call t

The `--output-bytecode` option can be used to generate `.bin` files for smart contracts and logic signatures, producing an approval and clear program for each smart contract.

```{note}
The Puya compiler incorporates several optimizations that are not present in the bytecode output generated by the
[`/v2/teal/compile`](https://developer.algorand.org/docs/rest-apis/algod/#post-v2tealcompile) endpoint.
When comparing the outputs of PuyaPy and Algod these differences may be observed.
To disable these optimizations and produce bytecode identical to Algod use the `--match-algod-bytecode` option.
```

## Obtaining bytecode within other contracts

The [`compile_contract`](#algopy.compile_contract) function takes an Algorand Python smart contract class and returns a [`CompiledContract`](#algopy.CompiledContract),
Expand All @@ -23,64 +16,6 @@ This compiled contract can then be used to create an [`algopy.itxn.ApplicationCa
The [`compile_logicsig`](#algopy.compile_logicsig) takes an Algorand Python logic signature and returns a [`CompiledLogicSig`](#algopy.CompiledLogicSig), which can be used to
verify if a transaction has been signed by a particular logic signature.

## ARC4 contracts

Additional functions are available for [creating](lg-compile.md#create) and [updating](lg-compile.md#update) ARC4 applications on-chain via an inner transaction.

### Create

[`algopy.arc4.arc4_create`](#algopy.arc4.arc4_create) can be used to create ARC4 applications, and will automatically populate required fields for app creation (such as approval program, clear state program, and global/local state allocation).

Like [`algopy.arc4.abi_call`](lg-transactions.md#arc4-application-calls) it handles ARC4 arguments and provides ARC4 return values.

If the compiled programs and state allocation fields need to be customized (for example due to [template variables](#within-other-contracts)),
this can be done by passing a [`algopy.CompiledContract`](#algopy.CompiledContract) via the `compiled` keyword argument.

```python
from algopy import ARC4Contract, String, arc4, subroutine

class HelloWorld(ARC4Contract):

@arc4.abimethod()
def greet(self, name: String) -> String:
return "Hello " + name

@subroutine
def create_new_application() -> None:
hello_world_app = arc4.arc4_create(HelloWorld).created_app

greeting, _txn = arc4.abi_call(HelloWorld.greet, "there", app_id=hello_world_app)

assert greeting == "Hello there"
```

### Update

[`algopy.arc4.arc4_update`](#algopy.arc4.arc4_update) is used to update an existing ARC4 application and will automatically populate the required approval and clear state program fields.

Like [`algopy.arc4.abi_call`](lg-transactions.md#arc4-application-calls) it handles ARC4 arguments and provides ARC4 return values.

If the compiled programs need to be customized (for example due to (for example due to [template variables](#within-other-contracts)),
this can be done by passing a [`algopy.CompiledContract`](#algopy.CompiledContract) via the `compiled` keyword argument.

```python
from algopy import Application, ARC4Contract, String, arc4, subroutine

class NewApp(ARC4Contract):

@arc4.abimethod()
def greet(self, name: String) -> String:
return "Hello " + name

@subroutine
def update_existing_application(existing_app: Application) -> None:
hello_world_app = arc4.arc4_update(NewApp, app_id=existing_app)

greeting, _txn = arc4.abi_call(NewApp.greet, "there", app_id=hello_world_app)

assert greeting == "Hello there"
```

## Template variables
Algorand Python supports defining [`algopy.TemplateVar`](#algopy.TemplateVar) variables that can be substituted during compilation.

Expand Down
Loading

0 comments on commit 352aaf9

Please sign in to comment.