In the last article we have talked about the background and basic design of the Authpaper 2019 token sales. This article will go into the code and discuss the core of the contract, default function.
The contract is based on Ethereum blockchain, so the language is Solidity. The full smart contract code can be found here: https://github.com/SolonAuthpaper/AUPC_sales_contract
Default function is important to any contract receiving ETH, because it is called whenever someone pays ETH to the contract.
If your contract does not intend to receive ETH, you should also define default function which just revert the operation. (I learnt this the hard way……)
With the revert() function, all users paying ETH to the contract will fail and the ETH never leaves the user. In many smart contracts (including our own AUPC contract), the default function is not defined. If an user accidentally send ETH to the contract, the contract will keep the ETH. If there is no function inside the contract to withdraw ETH (which is very normal, the contract creator does not expect ETH going to the contract, how come he/she would write a withdraw function?), the ETH cannot be transferred to others. The ETH is essentially locked forever.
Default function of the sales contract
The flow of the default function is obvious. First make sure the user paying ETH to the contract is not an empty address. If the contract owner sends in ETH, just keep the ETH without further operations.
After input checking, the contract will check if the token sales is over. If it is over, send back ETH and send out alert to burn the remaining tokens.
If it is not over, check if the upline information of the payer is kept in the smart contract and distribute the AUPC accordingly. If there is no upline information, make a call to the server for this information.
Let’s us go into the details.
You may see the word “external” and “payable” near the function, meaning this function is called externally only and this function involves sending out ETH. You may use “public” instead of “external” if the function is called both externally and internally. We select “external” as we know the function will not be called internally (it is the start of the whole token purchase process) and “external” costs less gas than “public”.
In the first few lines of codes, there are some require() function calls. require() is a special function which check the condition in the first input and revert the whole process if the condition is false.
For example, the line require(msg.value >= minPurchase, “Smaller than minimum amount”) means the contract will check if the amount of ETH paying to the contract is more than the minimum purchase value.
require() is very important to make sure the function is executed correctly. With different inputs, some operations may return unexpected numbers (buffer overflow or underflow, or just the user does not send in enough ETH to buy token). require() allows you to check if the numbers or operations are done in the expected way. If not, revert the whole process so that no intermediate or invalid result appears. Remember, a bug in the smart contract may bring loss of ETH, so we need to be careful. Another use of require() is making sure a function is run successfully, e.g.
This line of code will call the function purchaseAUPC. If the function returns false, it means there is something wrong. The whole function will be reverted and no transaction is done.
Optionally, one may add the second input to the require() function, which defines the error message to send out when the condition is false and the function is reverted. However, the message is encoded, you can only see the message in human readable way in Remix IDE.
Numbers in smart contract
Go back to our require(msg.value >= minPurchase, “Smaller than minimum amount”) example. In the contract, minPurchase is set to be 100 finney, or 0.1 ETH. Why we do not just use 0.1? What is finney?
In smart contract there is no float number, only unsigned integers. Then how ETH performs float number operations (like sending 0.1 ETH)? It is done by making 1 very large. In smart contract, the default unit is wei (1 * 10^(-18) ETH). If you would like to send 1 ether to others, in smart contract you are sending 1,000,000,000,000,000,000. The extra number of digits allows ETH to have arithmetic operations like a float number.
In other words, smart contract keeps and operates ETH transactions with 18 decimal accuracy. It is much more than enough in normal operations.
To make life simpler, there are some units in smart contract, so you do not need to count the zeros when working with numbers. 1 ether means 1 ETH, 1 finney means 0.001 ETH. Our token price is 1 finney. In our contract, all ETH related numbers are in finney.
The list of unit can be found here: http://ethdocs.org/en/latest/ether.html
In the require(msg.value >= minPurchase, “Smaller than minimum amount”) example, msg.values shows the amount of ETH sent to the contract (in unit wei). msg is a special global variable that contains some properties which allow access to the blockchain. All values in msg are read-only.
There are two values in msg worth your attentions.
msg.sender shows sender address of the message and current (external) function caller. This value may change when the function is called by other external party, which can be another contract or user.
Suppose there is a contract A (address 0x72d……), which will call a function in contract B (address 0xabc……). And now an user (address 0xbdc……) pays ETH to contract A. In contract A, the msg.sender will be 0xbdc……, no matter it is external, public, or internal functions. What is not obvious is when contract A calls contract B, the msg.sender in contract B will be 0x72d……, not 0xbdc…… the original caller. This confuses many people and makes an error in the contract.
msg.value returns the ETH amount sent in by the external party. If the user only intends to run the contract, he/she may call the function by paying the gas fee only, without sending in ETH to the contract. In this case msg.value will be 0.
You may take a look this page for the whole list of unit and global variables in Solidity: https://solidity.readthedocs.io/en/v0.5.3/units-and-global-variables.html
msg.sender.transfer(msg.value) and revert
In the default function, when token sales has ended, the contract will send back the ETH it received back to the sender. The code we used is msg.sender.transfer(msg.value), which means transferring ETH with amount of msg.value from the smart contract to address msg.sender. What it does is that the contract receives the ETH, and the same time sends back the same ETH to the user. transfer() function can only be called when the function is payable.
As mentioned before, revert() can also do the return ETH operation. It actually does it a better way: The ETH does not even leave the user wallet.
However, revert() will revert everything done in the function, like the function is never called. But we want to send out an alert to burn the unsold tokens, not just paying ETH back. So we take the current approach.
This line of code will send out an event to the blockchain. All events can be viewed publicly. For example, you can read all events fired from this sales contract in etherscan: https://etherscan.io/address/0x76c944a5fc6d98477e374e5a605f5c3b11b27148#events
Firing an event is important for contract owners and public to know what is happening inside the contract, especially when the contract is deployed. As logging is not available in smart contract, event is very important when something goes wrong.
To define an event, one need to define it with other variables in the contract.
You can define all kinds of events, and emit events as you wish. The only limitation is emitting an event takes transaction fee, or gas.
To emit an event, it is like calling a function with emit at head:
emit distributeETH(dad, owner, ethRate);
In etherscan, this event will be shown like:
One may argue why we do not burn the token right away when the token sales is ended, but firing an event. It is because when someone pays ETH to the contract after the contract sales (like 1 second after), there may be unsettled orders of the AUPC (like purchases made 2 seconds before deadline). In that case we need to wait all AUPC are sent out before burning the remaining AUPC. This is why we do not burn the tokens right away.
In the default function, there are codes reading information from the AUPC token contract and making request to the server. This involves interactions between multiple contracts and we will discuss it in the next article.