Implementing a FA1.2 token in pure Michelson (Part 2)
Take your Michelson skills to the next level with a more complex project
Part 1 is available here.
In Part 1, we had an overview of the TZIP-7 proposal, we set up our project in the Jupyter notebooks and we checked the structure of the parameter and the storage for our FA1.2 token.
In Part 2, we explore the code for the %transfer
entrypoint. It is an important piece of the contract (and the longest one) as it allows or restricts transfers.
Now let’s dive into the code 👨💻
The transfer entrypoint
In general, what I like to do at the very beginning, is unwrapping the structure laid down by the parameter. Because the parameter is made of nested union values, there will be some logic switches to implement the entrypoints of the contract.
This is how it looks like:
Notice the
DUMP
instruction at the bottom? This is not Michelson! It is used in the notebooks to print the state of the stack.
The first thing we do is to separate the parameter and the storage with the UNPAIR
instruction. The parameter is made of nested union values with the following structure:
parameter
______________|______________
| |
or or
_______|_______ _______|_______
| | | |
transfer approve getAllowance or
_______|_______
| |
getBalance getTotalSupply
In order to implement this structure in Michelson, we will use the IF_LEFT
instruction. A union value can only have a value in one of its sides. If there is a value on the left side, the first block following IF_LEFT
will be executed. If there is a value on the right side, the second block after IF_LEFT
will be executed. This is how entrypoints are simulated.
Let’s start with the most complex entrypoint, %transfer
. The transfer entrypoint receives a nested pair, i.e a pair that contains an address
on the left and another pair on the right, with an address
on the left and a nat
value on the right. We want to unpair these nested pairs and get the ledger so we can first verify that the spender exists in the ledger (indeed, it makes no sense to continue if the address we are supposed to deduct tokens from doesn’t exist in the ledger).
The UNPAPAIR
macro indicates that we have a pair with a nested pair on the right and we want to unpair both pairs at the same time. From that moment, we will have access to the address with the@from
annotation which is the sender of the tokens. We can then duplicate it and use it to verify if the sender has an account in the ledger.
After that, we want to check if the sender’s account has enough balance in order to process the transfer.
As usual, we copy the @from
value (because we will use it again later) and get its balance in the ledger. The GET
instruction returns an optional
value that is None
if the key/value pair couldn’t be found or (Some value)
with the value associated with the key. Although it is pretty unlikely that we will have a None
value here, let’s not take any risk and make the contract fail if it happens. The contract has to fail every time an error happens in order to prevent any unwanted change in the storage.
Now, we have the sender’s balance, so we can check if it is sufficient. We duplicate the amount of tokens to be sent, bring it to the top of the stack and compare it with the balance. If the amount of tokens is greater than the balance (IFCMPGT
), the contract fails and has to return the NotEnoughBalance
error code as per the standard. Otherwise, we can continue to the last check before the transfer.
At this point, we know that the account from which the tokens will be debited exists and has enough balance. We must make sure that the actual sender of the transaction has been allowed to debit the requested amount of tokens. Only two kinds of users can do it: the owner of the account or an address that has been approved by the owner of the account.
We start by ordering the elements of the stack to verify if the sender of the transaction is also the owner of the account. We use here SENDER
instead of SOURCE
to allow smart contracts to hold a balance in the ledger. If the owner’s address and the sender’s address are the same, we use empty curly braces {}
to tell the compiler to continue with whatever code follows the condition. If they are not the same, we check if the sender’s address has been approved by the owner of the account.
We search for the sender’s address in the allowances
map, if we cannot find it, we make the contract fail and return the NotEnoughAllowance
error code as per the standard. If we find it, we have to make a last verification and check if the current allowance covers the amount of tokens for the transfer. If it doesn’t, the contract fails, otherwise, we can proceed with the transfer.
The first part of the code processing the transfer is simple: we get the sender’s balance and deduct the amount of tokens to transfer. Remember that the subtraction of 2 nats in Michelson yields an int value, so you have to use ABS
to turn it back into a nat before updating the ledger big map.
Next, you may face 2 different situations: the recipient of the tokens may already have an account in the ledger, so we have to increase its balance or it may not have one, in which case we have to create a new key/value pair and set the balance. We use the MEM
instruction to check if the key (i.e the recipient’s address) exists in the ledger.
If we found an account, we’ll get its balance. In the unlikely event of getting None
from the GET
instruction (we know the account exists so there must be a balance, even if it may be 0
), we make the contract fail, otherwise, we get the balance, add to it the amount of tokens to be transferred and push it back paired with the allowances
map into the recipient’s account.
If there is no account, we simply create one by pushing an empty map
for the allowances, pairing it with the amount of received tokens and pushing it into the ledger (with UPDATE
).
We are now in the final push of the transfer
entrypoint. It is utterly important to update the sender’s allowance in order to avoid unwanted spendings.
We check first if the sender is the owner of the account, because there is then no allowance update to do and we will do some stack cleanup to remove the elements we don’t need to return the entrypoint.
If the sender is not the owner of the account, we fetch the owner’s account details (the balance and the allowances
map), we unpair them and search for the sender’s address. We already verified earlier that the sender is allowed to transfer tokens, so no need to do it again. Once we get the allowance, we deduct the amount of tokens we’ve just transferred and put it back in the allowances map (don’t forget to wrap the result in an optional value with SOME
before you UPDATE
). After that, we can pair the map with the account’s balance (take care of putting the elements in the right order, the balance on the left side of the pair, the allowances on the right side) and push the updated account details back to the ledger! At this point, the stack should be made of the ledger
big map and the totalSupply
number, so we can use PAIR
to pair them together and finally return our new storage 🥳
Conclusion
That’s it! You’ve successfully implemented the transfer
entrypoint of a FA1.2 token contract! The Michelson code does a bunch of verifications before actually updating the token balances and allowances, but these verifications are absolutely necessary to ensure the safety of your contract and of your users’ balances.
Remember also that any unexpected value or behaviour should lead the contract to fail in order to preserve the integrity of the accounts in the ledger.
This is the end of Part 2. In Part 3, we will explore the approve
entrypoint and the view
entrypoints, stay tuned!
Also, Read
- The Best Crypto Trading Bot
- Crypto Copy Trading Platforms
- The Best Crypto Tax Software
- Best Crypto Trading Platforms
- Best Crypto Lending Platforms
- Best Blockchain Analysis Tools
- Crypto arbitrage guide: How to make money as a beginner
- Best Crypto Charting Tool
- Ledger vs Trezor
- What are the best books to learn about Bitcoin?
- 3Commas Review
- AAX Exchange Review | Referral Code, Trading Fee, Pros and Cons
- Deribit Review | Options, Fees, APIs and Testnet
- FTX Crypto Exchange Review
- NGRAVE ZERO review
- Bybit Exchange Review
- 3Commas vs Cryptohopper
- The Best Bitcoin Hardware wallet
- Best monero wallet
- ledger nano s vs x
- Bitsgap vs 3Commas vs Quadency
- Ledger Nano S vs Trezor one vs Trezor T vs Ledger Nano X
- BlockFi vs Celsius vs Hodlnaut
- Bitsgap review — A Crypto Trading Bot That Makes Easy Money
- Quadency Review- A Crypto Trading Bot Made For Professionals
- PrimeXBT Review | Leverage Trading, Fee and Covesting
- Ellipal Titan Review
- SecuX Stone Review
- BlockFi Review | Earn up to 8.6% interests on your Crypto