algorand / pyteal

Algorand Smart Contracts in Python
https://pyteal.readthedocs.io
MIT License
285 stars 131 forks source link

Allow assignment to constant blocks #48

Closed barnjamin closed 3 years ago

barnjamin commented 3 years ago

For a project I'm working on I'd like to know the exact byte location of given variables in the assembled teal for some static analysis.

Currently the intc/bytec blocks are created in the compilation processes and the variables are deduplicated which prevents the analysis I'd like to do.

Being able to call something like IntConst(0, Int(500)) or ByteConst(0, Byte("DEADBEEF")) that create the intc/bytecblock lines would allow me to define the constants I'm interested in and know their precise location. The ByteConst would have to convert to binary.

I think this will introduce another issue though because if those lines exist, the assembler will skip adding any other constants thinking that step has already been done.

The open PR here https://github.com/algorand/pyteal/pull/41 appears to make some changes to the bits that would be relevant so I haven't dug into the pyteal lib yet to see how I might go about doing this.

jasonpaulos commented 3 years ago

Hi @barnjamin, could you explain what problem you're trying to solve by knowing the exact location of variables?

Also, I don't see how explicitly defining an intcblock/bytecblock would give you an exact location, since the placement of those statements would be very dependent on the expressions that appear before them.

barnjamin commented 3 years ago

Hi @jasonpaulos, my goal was to have a delegate signature check the receiver of an ASA to see that it is a contract account that was created with a specific template.

To do that I've got the sha256(teal assembly - variable bytes) compiled into that delegate sig and also check that the asset receiver address matches the sha512_256("Program"+ teal assembly).

To remove the template variable bytes I had to do some hacky stuff: https://github.com/barnjamin/rareaf/blob/main/contracts/platform-token.py#L79

What I'm trying to do is described more in detail here: https://github.com/barnjamin/rareaf/blob/main/contracts/platform.md

1) I'm not sure that this makes any sense to do 2) I'm not sure that this is even a good way to do this

As for exact placement, I think if you were to call those at the beginning of the teal script it should put them first?

I'd love to hear your thoughts on the idea but I'm sure this is not the arena for it. Still trying to understand the space so I'd appreciate any notes you have.

barnjamin commented 3 years ago

You could probably do something similar in terms of getting them added to the const block in the right order by just declaring a ScratchVar at the top a but the deduplication step will make it impossible (?) to do this.

jasonpaulos commented 3 years ago

@barnjamin thanks for explaining your problem more. This problem definitely makes sense, and I have some suggestions that may be useful.

Assuming you want a single fixed-length byte slice template variable for your contract, you could do the following:

  1. Use Tmpl.Bytes to create a placeholder for the template variable in your PyTeal program. Use that variable to implement the rest of your contract and then compile it to TEAL assembly. Note that since you're using a template variable, this won't produce a program that will successfully assemble with goal into a .teal.tok file since there will be lines like byte TMPL_VALUE. I suggest using the .teal.tmpl file type to denote a TEAL program with some template values in it.
  2. Write a script that reads in the generated .teal.tmpl from the previous step. This script can search for the template variable (it'll start with TMPL_), replace all occurrences of it with a concrete placeholder value, then assemble the program using goal into a .teal.tok file. You can then search this file for the placeholder value to find the offset of the template variable in the final compiled contract.
  3. Once you have the compiled .teal.tok file and the offset where your template variable is, you can populate the template from within another PyTeal contract to calculate it's hash.

Note that the hard part is step 2, specifically choosing a unique placeholder value. If you choose a byte string that appears somewhere else in your program, the assembler will deduplicate it and you'll end up accidentally modifying that other value too. Additionally, if the placeholder value is a sequence of bytes that happens to appear anywhere else in the .teal.tok assembled program, your offset for the variable in the .teal.tok program may be wrong.

The above steps can be extended to support multiple template variables too. It can also support int values if you use Btoi and Itob to convert them to/from bytes (Itob always produces 8 bytes, so this doesn't violate the assumption of fixed-length byte slices). It's actually preferable to use byte slices to store ints instead of trying to encode/decode int values directly from the assembled .teal.tok format because they are stored as varints, which would be very difficult to deal with in PyTeal.

barnjamin commented 3 years ago

@jasonpaulos thanks for the tips! I'll definitely use the TMPL_* vars in the teal template program and use a find/replace to populate them with real values in the final app. And the Btoi for ints instead of using ints directly makes more sense than comparing the value to determine number of bytes which felt bad to do.

The problem you mentioned about placeholders being unique is part of the problem I was hoping to address by setting values directly in the const blocks. If the compiler respects the values already set in the const block and doesn't de-duplicate them, I should be able to add TMPL* vars directly to the const block in the .teal file and reference them later with just intc X. Then swap the TMPL* values in the const block with the actual values and compile as normal.

That change would have to be implemented in the assembler and then possibly supported here so I'll just close this one and suggest it in the Teal 4.

Thanks again!