Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arc4 types examples #6

Merged
merged 23 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
# pyright: reportMissingModuleSource=false
import typing as t

from algopy import (
ARC4Contract,
GlobalState,
String,
UInt64,
arc4,
urange,
)
from algopy.arc4 import abimethod


class Arc4Types(ARC4Contract):

# example: ARC4_UINT64
@abimethod()
def add_arc4_uint64(self, a: arc4.UInt64, b: arc4.UInt64) -> arc4.UInt64:
"""
Math operations (like a + b) are not supported on arc4.UInt64 types
since they are internally represented as byte arrays in the AVM.
Use the .native property to perform arithmetic operations.
"""

# Use the native integers to perform arithmetic
c = a.native + b.native

return arc4.UInt64(c)
iskysun96 marked this conversation as resolved.
Show resolved Hide resolved

# example: ARC4_UINT64

# example: ARC4_UINTN
@abimethod()
def add_arc4_uint_n(
self, a: arc4.UInt8, b: arc4.UInt16, c: arc4.UInt32, d: arc4.UInt64
) -> arc4.UInt64:
"""
The encoding of arc4 integers will be smaller if it uses fewer bits.
Ultimately, they are all represented with native UInt64.
"""
assert a.bytes.length == 1
assert b.bytes.length == 2
assert c.bytes.length == 4
assert d.bytes.length == 8
iskysun96 marked this conversation as resolved.
Show resolved Hide resolved

total = a.native + b.native + c.native + d.native

return arc4.UInt64(total)

# example: ARC4_UINTN

# example: ARC4_BIGUINT
@abimethod()
def add_arc4_biguint_n(
self, a: arc4.UInt128, b: arc4.UInt256, c: arc4.UInt512
) -> arc4.UInt512:
"""
Integers with larger bit size are supported up to 512 bits.
Ultimately, they are all represented with native BigUInt.
"""
assert a.bytes.length == 16
assert b.bytes.length == 32
assert c.bytes.length == 64

total = a.native + b.native + c.native

return arc4.UInt512(total)

# example: ARC4_BIGUINT

# example: ARC4_BYTES
@abimethod()
def arc4_byte(self, a: arc4.Byte) -> arc4.Byte:
"""
An arc4.Byte is essentially an alias for an 8-bit integer.
"""
return arc4.Byte(a.native + 1)

# example: ARC4_BYTES

# example: ARC4_ADDRESS
@abimethod()
def arc4_address_properties(self, address: arc4.Address) -> UInt64:
underlying_bytes = ( # noqa: F841
Copy link
Collaborator

@gabrielkuettel gabrielkuettel Dec 4, 2024

Choose a reason for hiding this comment

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

# noqa: F841 is to ignore unused variables? Maybe log or return underlying_bytes and others so you can remove these comments and keep the examples clean

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah. If I want to do that, I would have to construct a tuple and return various types. I thought that would complicate the example more.

Copy link
Collaborator

Choose a reason for hiding this comment

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

could just log it, no? e.g., log(underlying_bytes)

address.bytes
) # This will return the underlying bytes of the address.

account = (
address.native
) # This will return the account type of the given address.

bal = account.balance # returns the balance of the account
total_asset = ( # noqa: F841
account.total_assets
) # returns the total assets held in the account

return bal

@abimethod()
def arc4_address_return(self, address: arc4.Address) -> arc4.Address:

account = (
address.native
) # This will return the account type of the given address.

"""
You can't return an Account type because it is a reference type.
Convert the Account type to arc4.Address type and return it.
"""
converted_address = arc4.Address(account)

assert converted_address == address

return converted_address

# example: ARC4_ADDRESS


# example: ARC4_STATIC_ARRAY
AliasedStaticArray: t.TypeAlias = arc4.StaticArray[arc4.UInt8, t.Literal[1]]


class Arc4StaticArray(ARC4Contract):

@abimethod()
def arc4_static_array(self) -> None:
"""
You can create a static array directly from the contract.
"""
static_uint32_array = arc4.StaticArray(
arc4.UInt32(1), arc4.UInt32(10), arc4.UInt32(255), arc4.UInt32(128)
)

total = UInt64(0)
for uint32_item in static_uint32_array:
total += uint32_item.native

assert total == 1 + 10 + 255 + 128

"""
You can also create a static array using a type alias.
"""
aliased_static = AliasedStaticArray(arc4.UInt8(101))

index = UInt64(0)

assert (aliased_static[0].native + aliased_static[index].native) == 202

aliased_static[0] = arc4.UInt8(202)

assert aliased_static[0].native == 202

"""
You can't add or pop values from a static array because it has a fixed size.
so this won't compile:
aliased_static.pop()
"""


# example: ARC4_STATIC_ARRAY

# example: ARC4_DYNAMIC_ARRAY
goodbye: t.TypeAlias = arc4.DynamicArray[arc4.String]


class Arc4DynamicArray(ARC4Contract):

@abimethod
def goodbye(self, name: arc4.String) -> goodbye:
bye = goodbye(arc4.String("Good bye "), name)

return bye

@abimethod()
def hello(self, name: arc4.String) -> String:
"""
Dynamic Arrays have variable size and capacity.
They are similar to native Python lists because they can also append, extend, and pop.
"""
dynamic_string_array = arc4.DynamicArray[arc4.String](arc4.String("Hello "))

extension = arc4.DynamicArray[arc4.String](name, arc4.String("!"))
dynamic_string_array.extend(extension)

copied_dynamic_string_array = dynamic_string_array.copy()
copied_dynamic_string_array.pop()
copied_dynamic_string_array.pop()
copied_dynamic_string_array.append(arc4.String("world!"))

greeting = String()
for x in dynamic_string_array:
greeting += x.native

return greeting

# example: ARC4_DYNAMIC_ARRAY

# example: ARC4_DYNAMIC_BYTES
@abimethod()
def arc4_dynamic_bytes(self) -> arc4.DynamicBytes:
"""arc4.DynamicBytes are essentially an arc4.DynamicArray[arc4.Bytes] and some convenience methods."""
dynamic_bytes = arc4.DynamicBytes(b"\xFF\xFF\xFF")
iskysun96 marked this conversation as resolved.
Show resolved Hide resolved

# arc4.DynamicBytes can return the native bytearray instead of accessing every single index of the array.
# This is only true for arc4.DynamicBytes because, as an example, an arc4.DynamicArray[arc4.UInt64]
# doesn't have a native equivalent.
native_dynamic_bytes = dynamic_bytes.native # noqa: F841

dynamic_bytes[0] = arc4.Byte(0)

dynamic_bytes.extend(arc4.DynamicBytes(b"\xAA\xBB\xCC"))
dynamic_bytes.pop()
dynamic_bytes.append(arc4.Byte(255))

return dynamic_bytes

# example: ARC4_DYNAMIC_BYTES


# example: ARC4_STRUCT
class Todo(arc4.Struct):
task: arc4.String
completed: arc4.Bool


Todos: t.TypeAlias = arc4.DynamicArray[Todo]


class Arc4Struct(ARC4Contract):

def __init__(self) -> None:
self.todos = Todos()

@abimethod()
def add_todo(self, task: arc4.String) -> Todos:
todo = Todo(task=task, completed=arc4.Bool(False)) # noqa: FBT003

if not self.todos:
self.todos = Todos(todo.copy())
else:
self.todos.append(todo.copy())

return self.todos

@abimethod()
def complete_todo(self, task: arc4.String) -> None:

for index in urange(self.todos.length):
if self.todos[index].task == task:
self.todos[index].completed = arc4.Bool(True) # noqa: FBT003
break

@abimethod()
def return_todo(self, task: arc4.String) -> Todo:
todo_to_return: Todo

exist = False
for index in urange(self.todos.length):

if self.todos[index].task == task:
todo_to_return = self.todos[index].copy()
exist = True

assert exist

return todo_to_return


# example: ARC4_STRUCT

# example: ARC4_TUPLE
contact_info_tuple = arc4.Tuple[
arc4.String, arc4.String, arc4.UInt64
] # name, email, phone


class Arc4Tuple(ARC4Contract):

def __init__(self) -> None:
self.contact_info = GlobalState(
contact_info_tuple((arc4.String(""), arc4.String(""), arc4.UInt64(0)))
)

@abimethod()
def add_contact_info(self, contact: contact_info_tuple) -> UInt64:
"""An arc4.Tuple is a heterogeneous collection of arc4 types."""
name, email, phone = contact.native

assert name.native == "Alice"
assert email.native == "[email protected]"
assert phone == arc4.UInt64(555_555_555)

self.contact_info.value = contact

return phone.native

@abimethod()
def return_contact(self) -> arc4.Tuple[arc4.String, arc4.String, arc4.UInt64]:
"""An arc4.Tuple can be returned when more than one return value is needed."""

return self.contact_info.value


# example: ARC4_TUPLE
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import logging

import algokit_utils
from algosdk.v2client.algod import AlgodClient
from algosdk.v2client.indexer import IndexerClient

from smart_contracts.artifacts.arc4_types.arc4_types_client import (
Arc4TypesClient,
)

logger = logging.getLogger(__name__)


# define deployment behaviour based on supplied app spec
def deploy(
algod_client: AlgodClient,
indexer_client: IndexerClient,
app_spec: algokit_utils.ApplicationSpecification,
deployer: algokit_utils.Account,
) -> None:

app_client = Arc4TypesClient(
algod_client,
creator=deployer,
indexer_client=indexer_client,
)
app_client.deploy(
on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
on_update=algokit_utils.OnUpdate.AppendApp,
)
Loading