As we outlined in one of our previous articles, tokens in DeFi are just small apps written as smart contracts and deployed to a smart contract compatible blockchain. The token itself is pretty much just two components, a list of balances and some rules for how the list is updated. The rules are defined in the programming code of the smart contract and the list is managed as an object in the contract as well.
When you look at an ERC20 implementation, like the one designed by open zeppelin, you will see this line:
mapping(address => uint256) private _balances;
That code creates a mapping named “_balances”. You can just think of a mapping as a table or list. The first column in the table contains the addresses that “own” some tokens and the second column represents the number of tokens that each address owns, or represents their “balance”. The balance column is represented as an unsigned 256 bit integer, or uint256. Unsigned means it cannot be negative, 256 bit means it can be really really big1, and integer means whole number.
If you were to create that data structure in a spreadsheet, it would look like this:
When you send tokens to another address, you call the “transfer()” function in the token code.
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
That function takes two arguments, an address named “to” and a uint256 named “amount”. The “to” is going to be the destination address of the tokens and the “amount” is how much you want to send. The “transfer()” function then calls the “_transfer()” function, note the leading underscore. The difference between the two is that “_transfer()” takes a third argument for the “from” address and actually does all of the work. “transfer()” with no leading underscore just passes along the message sender to “_transfer()” as the “from” address. Because your address is the one calling the function, you are the sender and will have the uint256 in the “_balances” mapping next to your address reduced by “amount” and the “to” will have the uint256 in the ”_balances” mapping next to their address increased by “amount”.
In more complicated scenarios, like interacting with other applications, your address may not actually be the entity initiating the transfer. A smart contract may need to update those balances on your behalf. For security purposes, tokens will not let any sender update the balances of any address. So there is another function, named “transferFrom()”, which is similar to “transfer()” except that it accepts an additional argument for a “from” address. This function works by checking a different list named “_allowances” before it can call the “_transfer()” function.
mapping(address => mapping(address => uint256)) private _allowances;
This “_allowances” list is a nested mapping, and would look something like this in a spreadsheet:
The first column is the address that is the “owner” of the tokens, the second column is the address that is approved as a “spender”, and the third column shows the “allowance” that the spender can spend. You can have multiple entries for each owner for the allowances they have given to different spenders. In the example above, all 3 addresses have approved the same spender (0x1334) to transfer some of their tokens to another address. When a spender transfers tokens on behalf of the owner, the allowance is reduced by the amount spent, the owner's balance is reduced by the amount spent, and the receiver’s balance is increased by the amount spent. If the amount to spend is greater than the allowance, then the function would fail before the “_transfer()” function can be called.
MetaMask has done a great writeup on how their wallet handles approving allowance changes and some best practices. https://support.metamask.io/hc/en-us/articles/6174898326683-What-is-a-token-approval-
Much like giving someone access to your bank account, there is risk in setting unlimited allowances for spending addresses. A good general rule is to never approve more than needs to be spent for one particular interaction. This can result in approving a lot of allowance increases if you regularly use DeFi applications and will increase transaction costs because each of these allowance updates are written to the blockchain. The tradeoff between security, cost, and convenience is ultimately the choice of the user.
Disclaimer
We, Digital Opportunities Group, LLC, are not providing investment or other advice. Nothing that we post on Substack should be construed as personalized investment advice or a recommendation that you buy, sell, or hold any security or other investment or that you pursue any investment style or strategy.
Case studies may be included for informational purposes only and are provided as a general overview of our general investment process. We have compiled our research in good faith and use reasonable efforts to include accurate and up-to-date information. In no event should we be responsible or liable for the correctness of any such research or for any damage or lost opportunities resulting from use of our data.
We are not responsible for the content of any third-party websites and we do not endorse the products, services, or investment recommendations described or offered in third-party social media posts and websites.
Nothing we post on Substack should be construed as, and may not be used in connection with, an offer to sell, or a solicitation of an offer to buy or hold, an interest in any security or investment product.
How big is really really big? The maximum number a uint256 can be is:
115792089237316195423570985008687907853269984665640564039457584007913129639935