#How do I convert between algopy and arc4 types (e.g., UInt64)?
1 messages · Page 1 of 1 (latest)
Can you share the code you've got that's not working and the error message?
It appears that just wrapping a ctor around them (e.g., calling arc4.UInt64(my_int_val)) works for the integers.
But I have this: ```class Stake(arc4.Struct, kw_only=True):
'''
owner_address (arc4.Address): the address of the staker
stake_amount (UInt64): the number of microalgos staked
stake_period_in_days (UInt64): the number of days to stake the amount
stake_apy (UInt64): the apy in basis points (e.g., 1/100ths of a %age point)
'''
owner_address : arc4.Address
stake_amount : arc4.UInt64
stake_period_in_days : arc4.UInt64
stake_apy : arc4.UInt64
nft_asset_id : arc4.UInt64
initial_block_no : arc4.UInt64
last_paid_at_ts : arc4.UInt64
accrued_rewards : arc4.UInt64
is_funded : bool
seems like an implicity type conversion here would be useful syntactic sugar, though I recognize it would add potential ambiguity into the teal synthesis.
Also seems like I can't get an algopy.UInt64 back by wrapping the arc4.UInt64 in the ctor
Right, this is one of those situations where the Python is sematically and syntactically correct, but is still a bit verbose
So the conversion to the arc4 type is easy.. converting back from it seems less sobvious?
or do I need to do an op.btoi on it?
Maybe--what are you trying to do with the UInt64 that you're passing in?
Some of them are being used for arithmetic. Some of them are just identifiers. I'm using the asset ID for an nft as a key for a BoxMap -- so that one is fine just as an arc4.UInt64. The ones that I'm uisng as accumulators or otherwise inputs to arithmetic are more problematic, because I need to be able to do bidirectional conversions with them.
(the link you sent includes nothing about turning the arc4.UInt64 into a regular algopy.UInt64)
oh, wait!
I can call arc4.UInt64.native to get the algopy.UInt64 back?
Ok. so for the ints that makes sense. For account/address -- is arc4.Address the arc4 equivalent of algopy.Account?
Or should I be using something else?
As a general statement, consider using more specfic types for Asset, Application, etc.
https://algorandfoundation.github.io/puya/lg-arc4.html#reference-types
Right, there's always the .native escape hatch
You mean, versus using arc4.Address?
Address is different--that's not a UInt64
I know.
But for asset IDs, e.g.
Ah, for asset IDs use asset rather than UInt64?
but there is no arc4.Asset type?
You can use algopy.Asset
and that will be legal in an arc4.struct?
itxn.AssetConfig returns a UInt64, not an algopy.Asset
On a return that's fine
Part of the reason that it can be helpful to define a method arg as Asset instead of just UInt64 is because that asset will need to be provided as a foreign asset reference on the app call
So it is a "special" UInt64, not just any UInt64
Ok, so I define it as an Asset in method signature, but it's still a UInt64 for purposes of keying the BoxMap, and for purposes of return value?
That still leaves me with Account...I'mm not allowed to use algopy.Account in the struct, but it rejects the attempt to convert it to arc4.Address. What's the best way to manage that?
Just treat it as "bytes"?
Show me how you're doing this?
So you saw the struct definition, with owner_address set to arc4.Address -- I'd previously had it set to algopy.Account -- because I set it to Txn.sender
I tried setting arc4.Address to Txn.sender.bytes, but that also fails.
Have you referred to this BoxMap example from the docs?
from algopy import BoxMap, Contract, Account, Txn, String
class MyContract(Contract):
def __init__(self) -> None:
self.my_map = BoxMap(Account, String, key_prefix=b"a_")
def approval_program(self) -> bool:
# Check if the box exists
if Txn.sender in self.my_map:
# Reassign the value
self.my_map[Txn.sender] = String(" World")
else:
# Assign a new value
self.my_map[Txn.sender] = String("Hello")
# Read a value
return self.my_map[Txn.sender] == String("Hello World")```
Account should work here
Yeah, but I'm not using the Account as the BoxMap key -- I'm using the Asset. The Acccount I'm storing as an attribute on the arc4.struct
I was using the account as the boxmap key, and then I realized that I wanted the same wallet to be able to have multiple stakes, so I changed it to look up by NFT asset ID, and then confirm that Stake.owner_address matched the wallet interacting with it.
why not key to a struct of [Account, Asset] ?
That's a good idea, though it still doesn't solve the question of how to store an Account inside of a struct
Is converting the Account to an arc4.Address the right approach?
I don't think that is necessary here--Account should result in the 32 bytes of the account (non-human readable)
Right, but I want to be able to look it up? Or is that fundamentally unnecessary, because I can query the chain for the asset owner?
Look it up how?
You can assemble the Account+Asset bytes offchain and query the box by that name
Just as an example, if a bit extreme, I have an app that takes about a dozen inputs, hashes them, and then uses the resulting bytes as the box key. I then have an effectively-infinite number of box keys I can use, and I can do the hash off-chain anytime I need to fetch a box value from algod
I guess if it's a composite key, it wouldn't need to be a property on the struct -- the user would pass in the asset id, we'd concatenate it with Txn.sender to create the composite key, and look up the details, so they would have no need to be stored there. So for this specific use case, I can avoid it altogether.
But...for future use...if I ever had a reason to store an Account in a box, what is the correct arc4 data structure to use?
One possible argument (easily worked around with off-chain caching of the tuple, but for argument's sake) would be:
let's say I had an off-chain process that iterated over NFTs and paid a dividend to the account of each in sequence...I'd like the recipient account to be easily accessible on-chain so we could just pass in the nft id, after iterating over that list in isolation?
That shows using the Account as the key
But to store an Account in a struct, it's required to be an arc4 type
Invalid ARC4 Struct declaration, the following fields are not ARC4 encoded types: owner_address
that's the build error I get when I have an algopy.Account as a data member of an arc4.struct
show me the struct in total so I can try to recreate it?
owner_address : Account
stake_amount : arc4.UInt64
stake_period_in_days : arc4.UInt64
stake_apy : arc4.UInt64
nft_asset_id : UInt64
initial_block_no : arc4.UInt64
last_paid_at_ts : arc4.UInt64
accrued_rewards : arc4.UInt64
is_funded : bool
I believe I've got what you need working like so
from algopy import ARC4Contract, arc4, Account, BoxMap, Txn
from algopy.arc4 import abimethod
class Stake(arc4.Struct):
owner_address : arc4.Address
stake_amount : arc4.UInt64
stake_period_in_days : arc4.UInt64
stake_apy : arc4.UInt64
nft_asset_id : arc4.UInt64
initial_block_no : arc4.UInt64
last_paid_at_ts : arc4.UInt64
accrued_rewards : arc4.UInt64
is_funded : arc4.Bool
class Vrf(ARC4Contract):
def __init__(self) -> None:
self.box_map = BoxMap(Account, Stake, key_prefix="")
@abimethod()
def struct_in_box(self, struct: Stake) -> None:
self.box_map[Txn.sender] = struct.copy()
sure, but...what it still doesn't show is this: if I have an Account, that I want to assign to an instance of Stake.owner_address, how do I convert it?
That I still can't figure out.
Sure, I can use the Txn.sender as the BoxMap key.
I've got that working
What I'm trying to figure out how to do is just how to assign an Account to a value within the struct (as an arc4.Address, presumably) such that it will work.
And I can't do it directly, and I can't wrap it in a constructor (e.g., arc4.Address(my_account_obj), and I can't do that on the .bytes suffix -- like arc4.Address(my_account_obj.bytes)
My question is really very simple: what arc4 data structure can i use to represent an algopy.Account, and how do I convert back and forth between the two?
from algopy import ARC4Contract, arc4, Account, BoxMap, Txn
from algopy.arc4 import abimethod, Address
class Stake(arc4.Struct):
owner_address : arc4.Address
stake_amount : arc4.UInt64
stake_period_in_days : arc4.UInt64
stake_apy : arc4.UInt64
nft_asset_id : arc4.UInt64
initial_block_no : arc4.UInt64
last_paid_at_ts : arc4.UInt64
accrued_rewards : arc4.UInt64
is_funded : arc4.Bool
class Vrf(ARC4Contract):
def __init__(self) -> None:
self.box_map = BoxMap(Account, Stake, key_prefix="")
@abimethod()
def struct_in_box(self, struct: Stake) -> None:
new_struct = struct.copy()
new_struct.owner_address = Address.from_bytes(Txn.sender.bytes)
self.box_map[Txn.sender] = new_struct.copy()```
Ah, ok. So .from_bytes is the way. And then to go the other way...is there a .native?
Perfect. Thank you. I might end up dropping the owner_address property from this struct as an optimization, but it's useful to have it during development, and I'm sure it's a use-case that will arise again in the future, so it's quite helpful to have a solid grasp of how to go back and forth.
Here comes Xartyx to set me straight 😅
arc4.Address(some_account)
should work? (On mobile so I can’t check for myself)
Yah
Even terser:
@abimethod()
def struct_in_box(self, struct: Stake) -> None:
new_struct = struct.copy()
new_struct.owner_address = Address(Txn.sender)
self.box_map[Txn.sender] = new_struct.copy()```
from_bytes works too of course, but pretty sure we added Account as one of the overloads in arc4.Address
😅 I'm now trying to figure out why I thought that I couldn't do that.
Let's see...if I can get it to build without errors...I may have misread an earlier set of error messages to believe I couldn't do that
Well...new error: mutable reference to ARC4-encoded value must be copied using .copy() when being assigned to another variable stake = self.stake_map[nft_asset_id] -- @main totem I noticed you used .copy() in your sample code. I guess if I'm just reading from the box values, that's fine. If I'm modifying them...I either need to assign to the members of the original reference, or assign the modified copy back to the BoxMap after I'm done?
There was a discussion about this somewhere recently--let me find
The need for .copy() is actually coming from semantic compatibility. Objects in python are passed around by reference, when an object (eg. an array or struct) is mutated in one place, all references to that object now point to the mutated version. All this is managed via the heap. The AVM has a very limited 'heap' (the 256 scratch slots), and puya is not currently using it at all. arc4 structs and arrays are instead stored on the stack. When you create multiple 'references' to the same arc4 value in puya, we add a copy of it to the stack meaning if you updated one reference, the other would not see the updated value. So, in order to have algorand python run with the same semantics - we force a .copy() each time you create a new reference to the same object to match what will actually happen on the AVM.
Having said that, it's obviously not a nice API to have to deal with and as soon as we have a technical solution for simulating reference types on the AVM, we would look to retire the need for copy.
Ok, that makes lots of sense. Thanks for sharing that context. It builds! Now to test! I'm sure I'll find new problems 😂
Woo!
You can also avoid the .copy() requirement if the struct is indicated as frozen e.g.
class MyStruct(arc4.Struct, frozen=True):
foo: arc4.UInt64
bar: arc4.Address
Depending on your usage this might be more convenient
I assume "frozen" == "immutable"? If so, that's super useful to know, possibly not strictly applicable to the use-case discussed above 🙂
