Task: Build an Algorand Smart Contract

Task: Build an Algorand Smart Contract

In this task, create an Algorand Smart Contract in TEAL that solves any problem you choose. Reply to this post with a description of the problem you are trying to solve, the commented TEAL code that solves the problem, and any further explanation (e.g. caveats, pitfalls) that the user should be aware of if they use the code. The contract can be as short or as long as you want. The point of this task is to get you familiar with TEAL opcodes and provide a platform for discussing use cases and best practices.

Need a suggestion for a problem to solve? How about a recurring payment? Write a Smart Contract that allows someone to remove no more than x Algos from your account every y blocks.

Task instructions

  1. Write your TEAL program.
  2. Make sure it compiles and that you successfully test a transaction against it on TestNet.
  3. Reply to this post with a description of the problem you are solving, the TEAL code you wrote to solve it (with inline comments), and any further explanation of pitfalls or caveats that you think are relevant.
  4. Earn the Algorand TEAL Badge by participating in this task.**
  5. (optional) To receive an on-chain version of the TEAL Badge, include an Algorand address in your post that has opted-in to the asset. Asset details here.

**Participation means that you post a reply with the specified information, that your script compiles successfully, and it is accurately explained. If your contract has pitfalls or issues that you did not callout initially, the badge can still be earned through engaging in discussion related to feedback about your submission.

Prerequisites

  1. Checkout this Medium post for getting started with Algorand Smart Contracts
  2. Review the Algorand Smart Contract documentation to learn more about how ASC1s work in goal.
  3. For testing your Smart Contract, use goal or any of the SDKs.

Hint

Take a look at the templates that Algorand has created for inspiration.

Why This Task Matters

With Algorand Smart Contracts you can create contract accounts for escrow-like payments or you can delegate signature authority to authorize withdrawals from an account. Both of these scenarios are backed by an Algorand Smart Contract written in TEAL.

Algorand will provide the community with templates that represent common use cases, but you can also create your own TEAL code to apply to various other use cases. When delegating a signature, this can be very powerful but also very dangerous. Learning what you must check for to protect an account is very important.

Have Questions?

Post them here!

TEAL Badge:

1 Like

Liz, Quick question. What is the significance of txn lease?. I see this code in many TEAL templates.

txn Lease
byte base64 TMPL_LEASE

If you add a Lease to a transaction tx1, no other transaction from that address can use the same Lease within tx1's validity lifetime (firstRound to lastRound). This is true even after tx1 is confirmed, the Lease still will block other transactions until tx1.lastRound.

That’s just my explanation of it, for a more precise summary, here’s the excerpt from the node code:

	// Lease enforces mutual exclusion of transactions.  If this field is
	// nonzero, then once the transaction is confirmed, it acquires the
	// lease identified by the (Sender, Lease) pair of the transaction until
	// the LastValid round passes.  While this transaction possesses the
	// lease, no other transaction specifying this lease can be confirmed.
	Lease [32]byte `codec:"lx"`
1 Like

I was able to modify the HTLC.teal and create a new smart contract and made it work. While HTLC releases the balance to the sender after a certain time period, this contract locks in amount with 2 secret codes. One with sender and one with receiver. For transaction to go through the sender should share the secret code to receiver. If both agrees to cancel the contract and return the money to sender then receiver shares his secret code with sender. If both do not share the secret code with each other, then amount is locked in the contract until it is resolved by sender and receiver or go to court.

Teal Contract

//1st transaction close to receiver when presented with secret key from sender
txn CloseRemainderTo
// address of receiver
addr VIFSMPHK567D3AE6PKOFTWD7RX35KKIN6LKVZS2SQOHM2Q5WPGU5ZJJTJA

// We do not need receiver
txn Receiver
global ZeroAddress

&&
// pass secret code as argument 0 and should match sha256
arg 0
sha256
byte base64 gbrodrcFE8nezGCO7VSZd6ga+hwra0CArsJWM555Lg8=

&&
// 2nd transaction close to sender when presented with secret key from receiver
txn CloseRemainderTo
addr QA75IQ76F6H2T55G65BY7BPLF5QNWSLT5XGI62COZSYB4ZQ3MSKI3EQ25A

// We do not need receiver
txn Receiver
global ZeroAddress

&&
// pass secret code as argument 0 and should match sha256
arg 0
sha256
byte base64 tsRYY4deNEh8o8FV7RRe/hKnRYHie+/sWqZhuO6Mpt0=

&&
// This OR statement is the key where it allows either transaction to go through if it passes SHA256 key
||
// Make sure the txn fee is not high
txn Fee
int 1000000
<=
&&

I opted-in to receive on chain TEAL badge for this address: QA75IQ76F6H2T55G65BY7BPLF5QNWSLT5XGI62COZSYB4ZQ3MSKI3EQ25A

1 Like

I would like to modify HTLC (Hash Time Lock Contract) using Assets not simple Algo payment.
(https://github.com/algorand/go-algorand/blob/master/tools/teal/templates/htlc.teal.tmpl)

    txn Fee
    int TMPL_FEE
    <=
    txn TypeEnum
    int 1
    ==
    &&
    txn Receiver
    global ZeroAddress
    ==
    &&
    txn AssetAmount
    int 0
    ==
    &&
    txn AssetCloseTo
    addr TMPL_RCV
    ==
    arg_0
    TMPL_HASHFN
    byte base64 TMPL_HASHIMG
    ==
    &&
    txn AssetCloseTo
    addr TMPL_OWN
    ==
    txn FirstValid
    int TMPL_TIMEOUT
    >
    &&
    ||
    &&

Is this correct?

1 Like

Faucet contract account:

  • gives back max. 0.1 Algo
  • can be used again only after 5 minutes

// Make faucet contract account
// V1, 16-Dec-2019

// txn.Amount <= 0.1 Algo ?	// Faucet gives max. 0.1 Algo
txn Amount
int 100000
<=

// txn.LastValid - txn.FirstValid >= floor(300/4.4) ?  // Repeated use: after at least 68 round, i.e. 5 minutes
txn LastValid 
txn FirstValid
-
int 68
>=

// combine
&&

// txn.Lease is given and valid? 
txn Lease
byte base64 XlTiv+2SHsY9dttPpSXC+mj4m0rihuavdMKhYjsXDdU=	// 32 bytes
==

// combine
&&

//-----

// owner may empty contract account, using a hash preimage
arg_0
sha256
byte base64 PF62+e9yBNw+5oJhaRZ1t7pLtUYA8Bm4sdeJukxzoJs=	// 32 bytes, hash of preimage
==

// combine
end:
||

Usage:


$ ./goal clerk compile teal_badge.teal
teal_badge.teal: 7IAAGJ2AXTPUCYQLZJS232WEKMCC5NR2EPCLB5ND3DWGW5YT2A7CGJPMZU
$ ./goal clerk send -a 2000000 -f Unnamed-2 -t 7IAAGJ2AXTPUCYQLZJS232WEKMCC5NR2EPCLB5ND3DWGW5YT2A7CGJPMZU -w w2 -d data
$ ./goal clerk send -a 1 --from-program teal_badge.teal -t Unnamed-2 -w w2 -x XlTiv+2SHsY9dttPpSXC+mj4m0rihuavdMKhYjsXDdU= --lastvalid 3699200 --argb64 AA== -o tx1.txn -d data
$ ./goal clerk rawsend -f tx1.txn -d data

The “only” problem is that lease param doesn’t block
a new tx to the faucet, before lastvalid.

How can it be corrected?

1 Like

Maugli, This looks basically good. You should add a limit to the Fee, otherwise a malicious transaction could spend the entire balance on Fee wasting the account while taking their 0.1 Algo.
Are you saying that you had trouble submitting the transaction? Looks like it needs goal clerk send ... --lease XlTiv+2SHsY9dttPpSXC+mj4m0rihuavdMKhYjsXDdU=

1 Like

Vytek,
“AssetTransfer” is a separate type of transaction, different than “Payment” for moving Algos. See the TypeEnum table https://developer.algorand.org/docs/teal-opcodes
Also, the assembler in goal clerk compile now accepts symbolic constants for TypeEnum, so you can say int AssetTrransfer and get something that will == for txn TypeEnum
And an extra complication of using assets in contract accounts is that they have to authorize the transaction that sets up the account to allow receiving the asset. See the limit-order-swap example https://github.com/algorand/go-algorand/blob/master/tools/teal/templates/limit-order-b.teal.tmpl

visybl,
That looks like it should work. I don’t think I would accept such a contract though if I were the initiator. I want the timeout so I can get my money back if the other side flakes out. Now the other side can flake out and leave me with no way to get back the money I put into escrow.

1 Like

Brian, good point. I can argue the same from receiver side. What if sender does not share the secret code, receives the product and wait for expiry period to get back the balance? instead, this gives equal stake for both parties. In this case, both does not want the balance to be stuck on the contract so they either strike a deal or take it court…

Dear Brian,
I used -x, equivalent to --lease.
I tried --lease again, but is seem to have no effect whatsoever on testnet:

$ ./goal clerk send -f Unnamed-2 -t Unnamed-3 -w w2 --lease XlTiv+2SHsY9dttPpSXC+mj4m0rihuavdMKhYjsXDdU= --lastvalid 3715950 -a 1 -d data
Please enter the password for wallet 'w2': 
Sent 1 MicroAlgos from account JW6L2ZCQT3UIQH5AFM3CVW3C7M3QFYHXA3EU4WHREOATRWMXP6MBFNKILM to address OSWEIKXSTSVH7KV65N24USUNANXRKGACZEG3ZF2U6UCPCSAY4WRBWAKYYU, transaction ID: YJBQZTPOUKMTPBH2RM32PTGJDEFZBH5YHZBOJFS7CKYCMO5EKRJA. Fee set to 1000
Transaction YJBQZTPOUKMTPBH2RM32PTGJDEFZBH5YHZBOJFS7CKYCMO5EKRJA still pending as of round 3715930
Transaction YJBQZTPOUKMTPBH2RM32PTGJDEFZBH5YHZBOJFS7CKYCMO5EKRJA committed in round 3715932
$ ./goal clerk send -f Unnamed-2 -t Unnamed-3 -w w2 --lease XlTiv+2SHsY9dttPpSXC+mj4m0rihuavdMKhYjsXDdU= --lastvalid 3715950 -a 1 -d data
Please enter the password for wallet 'w2': 
Sent 1 MicroAlgos from account JW6L2ZCQT3UIQH5AFM3CVW3C7M3QFYHXA3EU4WHREOATRWMXP6MBFNKILM to address OSWEIKXSTSVH7KV65N24USUNANXRKGACZEG3ZF2U6UCPCSAY4WRBWAKYYU, transaction ID: R26JLS6V4B2FFCIBFT4W5SCT6XPEYH6XZ4GXG6WQZ7BFCNA7T6OA. Fee set to 1000
Transaction R26JLS6V4B2FFCIBFT4W5SCT6XPEYH6XZ4GXG6WQZ7BFCNA7T6OA still pending as of round 3715933
Transaction R26JLS6V4B2FFCIBFT4W5SCT6XPEYH6XZ4GXG6WQZ7BFCNA7T6OA committed in round 3715935
1 Like

Hash Time Lock Contract (HTLC) for Algorand Standard Assets (ASA)

Functionality

This contract implements a hashed timelock contract (HTLC) for holding Algorand Standard Assets (ASA). It is intended for use in cross-chain atomic swaps of an ASA for an asset on a foreign chain using the same preimage.

The contract will approve transactions from itself under three scenarios:

  1. If txn is an “opt-in” transaction for TMPL_ASA_ID.
  2. If txn.FirstValid is greater than TMPL_TIMEOUT, then funds may be closed out to TMPL_OWN.
  3. If an argument arg_0 is passed to the script such that TMPL_HASHFN(arg_0) is equal to TMPL_HASHIMG, then funds may be closed out to TMPL_RCV.

Code overview

( (Scenario 1) OR  ( (        Scenario 2           ) OR  (      Scenario 3     ) ) AND Fee)
( (  Opt-in  ) bnz ( ( Time Lock: ( ASA || Algos ) ) bnz ( Hash Lock: ASA Only ) ) && Fee )

TEAL Template Code

Template Details and Walkthrough

Next Steps

Please make use of the template and provide feedback. Pull Requests are welcome :slight_smile:

Footnotes

All code is offered “AS-IS” and “WITHOUT WARRANTY” into the PUBLIC DOMAIN.

The only caveat I am aware of is overfunding the escrow account with Algos, as anyone may send an “opt-in” transaction which could consume all Algos upto the minimum balance requirement. Either party may send Algos to the escrow along with their HTLC transaction to pay for fees just-in-time, so the contract cannot be halted.

Eligible account to receive TEAL Badge:

FT43JWVPZQ4U3M5BL5DFENHG3LXWXD7Q34YZP6SWWQCEB7OBREMDM3RKC4
2 Likes

Hi Ryan, Thanks for sharing this. This is what I was looking for. Couple of questions.

  1. TMPL_TIMEOUT was not used inside the program. Assume it will replace int 200000 for txn FfirstValid

  2. What is the significance of checking the length of input argument? it will fail SHA256 if it is not 32 bytes right? Also when using Java SDK this check does not pass even when it is 32 bytes. When I remove it, then it works if SHA256 match or fail if it does not match.
    arg 0
    len
    int 32
    ==
    &&

  1. Thanks @visybl for finding that missing TMPL entry. I also found TMPL_FEE near the end and updated the template accordingly.
    2a. I’m not sure about the validation checks Algorand implemented for SHA256, so I included the 32 byte check to be sure. If the implementation performs this check, then yes, the explicit check should removed from the TEAL code.
    2b. As to why the Java SDK fails on the 32 byte check, I’m quite surprised. What is the stack output upon failure? Thus far I’ve been doing all my testing with goal rather than an SDK. I’ll try other SDKs shortly and compare results.
1 Like

Thanks Ryan. SDK Java issue may be with my setup. But let me know when you test using SDK. For now I will avoid the len check block. Let me know if you find that the len check block is really needed for a reason.

If you can post your Java code.

Hi Jason, Thanks for your help. Find the example below.

ASC teal code

arg 0
len
int 32
==
&&
arg 0
sha256
byte base64 nnhszg2V8PYIlYr+96R2oh9sb7Pc6YHSrAVLXx6cuSE=
==
&&

I am simply checking if the sha256 matches and then allowing the transaction. Find the below dry run output created by saving the transaction created in Java.

tx[0] cost=17 trace:
1 intcblock =>
4 bytecblock =>
39 arg_0 => 6d65726368616e74
40 len => 8 0x8
41 intc_0 => 32 0x20
42 == => 0 0x0
43 arg_0 => 6d65726368616e74
44 sha256 => 9e786cce0d95f0f608958afef7a476a21f6c6fb3dce981d2ac054b5f1e9cb921
45 bytec_0 => 9e786cce0d95f0f608958afef7a476a21f6c6fb3dce981d2ac054b5f1e9cb921
46 == => 1 0x1
47 && => 0 0x0

REJECT

In the above, SHA 256 matches but the length check fails with a length of 8 instead of 32.

Here’s the signed txn output:

{
“tx”: {
“type”: “Payment”,
“sender”: {
“bytes”: [-48, 52, 106, 51, -24, -48, -64, -106, -39, 62, -40, -34, 38, -90, -118, 102, -11, 125, 58, -74, 22, -116, -24, -124, 58, -101, -88, -82, 115, 100, -111, -85]
},
“fee”: 1000,
“firstValid”: 3804100,
“lastValid”: 3804200,
“genesisID”: “testnet-v1.0”,
“genesisHash”: {
“bytes”: [72, 99, -75, 24, -92, -77, -56, 78, -56, 16, -14, 45, 79, 16, -127, -53, 15, 113, -16, 89, -89, -84, 32, -34, -58, 47, 127, 112, -27, 9, 58, 34]
},
“group”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“amount”: 410000,
“receiver”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“closeRemainderTo”: {
“bytes”: [-128, 63, -44, 67, -2, 47, -113, -87, -9, -90, -9, 67, -113, -123, -21, 47, 96, -37, 73, 115, -19, -52, -113, 104, 78, -52, -80, 30, 102, 27, 100, -108]
},
“votePK”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“selectionPK”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“voteFirst”: 0,
“voteLast”: 0,
“voteKeyDilution”: 0,
“assetParams”: {
“assetTotal”: 0,
“assetDefaultFrozen”: false,
“assetUnitName”: “”,
“assetName”: “”,
“url”: “”,
“assetManager”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“assetReserve”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“assetFreeze”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“assetClawback”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
},
“assetIndex”: 0,
“xferAsset”: 0,
“assetAmount”: 0,
“assetSender”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“assetReceiver”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“assetCloseTo”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“freezeTarget”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“assetFreezeID”: 0,
“freezeState”: false
},
“sig”: {
“bytes”: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
“mSig”: {
“version”: 0,
“threshold”: 0,
“subsigs”:
},
“lSig”: {
“logic”: [1, 32, 1, 32, 38, 1, 32, -98, 120, 108, -50, 13, -107, -16, -10, 8, -107, -118, -2, -9, -92, 118, -94, 31, 108, 111, -77, -36, -23, -127, -46, -84, 5, 75, 95, 30, -100, -71, 33, 45, 21, 34, 18, 45, 1, 40, 18, 16],
“args”: [
[109, 101, 114, 99, 104, 97, 110, 116]
]
},
“transactionID”: “CDQQUYM42HQGVLGYAHUCQ3HBXW4DYA4PB3WKCZ3EM6TTHD344HJQ”
}

Ok a couple of things. Are you calling this from Java or Goal or some other SDK. Your arg0 is not 32 bytes long. When you pass parameters in Java they will look something like this:

ArrayList<byte[]> pargs = new ArrayList<byte[]>();
byte[] arg1 = "hunter2".getBytes();
// byte[] arg2 = {4, 5, 6};
pargs.add(arg1);
System.out.println("program: " + result.program.toString());
//lsig = new LogicsigSignature(program, args);
LogicsigSignature lsig = new LogicsigSignature(result.program, pargs);

Notice i am passing a passcode but before I sha256 it I want to check the link I expect it to be. In this case 7.
When you pass parameters using goal you have to pass them as base64 encoded strings, so that same parameter would be passed like:

first get base64 encoded string
$ echo -n hunter2 | base64

aHVudGVyMg==

Then I could call the transaction like:
$ goal clerk send -a 1000 -c J376TKTX4CY5LAXJQKVDHZZBTXTBOYQ7TE562SPGEGCKVOQQDPDKPPMQX4 --to C3MKH24QL3GHSD5CDQ47ZNQZMNZRX4MUTV6LVPAXMWAXMIISYSOWPGH674 --from-program sha.teal --argb64 “aHVudGVyMg==” -d ~/node/data -o ff.stx

You code appears to have a && operator that is not needed. Try:

arg 0
len
int 7
==
arg 0
sha256
byte base64 9S+9MrKzuG/4jvbEkGKChfSCrxXdyylUH5S89Saj9sc=
==
&&

I cover parameter passing in this article:

I hope this helps

ok. Understand now. I am using Java sdk. The length of the argument is 7 in your example and 8 in my example. If I put len int 8 then it works. Agree with &&. That was a typo error. For some reason I was thinking that input arg0 is always 32 (user error…!)

Modified code.

arg 0
len
int 8
==
arg 0
sha256
byte base64 nnhszg2V8PYIlYr+96R2oh9sb7Pc6YHSrAVLXx6cuSE=
==
&&

Result when triggered from Java.

tx[0] cost=17 trace:
1 intcblock =>
4 bytecblock =>
39 arg_0 => 6d65726368616e74
40 len => 8 0x8
41 intc_0 => 8 0x8
42 == => 1 0x1
43 arg_0 => 6d65726368616e74
44 sha256 => 9e786cce0d95f0f608958afef7a476a21f6c6fb3dce981d2ac054b5f1e9cb921
45 bytec_0 => 9e786cce0d95f0f608958afef7a476a21f6c6fb3dce981d2ac054b5f1e9cb921
46 == => 1 0x1
47 && => 1 0x1

pass -

@visybl I see @JasonW provided the info about argument passing and by now we can see the SHA256 implementation does not explicitly check for a 32 byte input. That’s not a bug, so not suggesting it to be changed. Just want to recommend when performing a BIP-199 compatible HTLC in a cross chain swap, ensure both protocols enforce a 32 byte check on the preimage length. My HTLC for ASA template above should retain the explicit check within the TEAL program.

1 Like