Moolya ICO: Burn the burnReturn
It started with a mystery.
Various crypto influencers reviewed the Moolya ICO in exchange for free MOOLYA tokens. Typical publicity-for-pay arrangement. But while expecting to receive 50,000 tokens, their Ethereum wallets received an incoming transaction of 833,333 MOOLYA. At the ICO price of $0.06, that’s worth $50,000 USD!
The beauty of the blockchain is that you can confirm every transaction. TxReceipt Status: Success. Plenty of block confirmations. Whoever screwed up those sends, it was now permanently etched into the Ethereum blockchain. Too f’n bad, they just made those influencers MOOLYA whales.
But when checking back an hour later their balance strangely showed up as 50,000 MOOLYA. The amount it was supposed to be all along. Wait, wha…? They didn’t send their MOOLYA riches out to anyone (nor could they have; the tokens are locked until the ICO ends, rendering them nontransferable).
And yet we know the 833,333 token transfer did happen. The transaction is right there in the freakin’ blockchain!
Where did those tokens go?!
After a little digging they realized there was a new transaction on their Ethereum address. It offered a half explanation: there was an additional transfer of 50,000 MOOLYA coins.
Two transactions in. No transactions out. Mystery.
When they first told me about this I figured they were just misunderstanding the transaction records. But nope, both sides of the impossible contradiction were clearly true: Yes, there were two transactions. Yes, the total balance was only 50,000.
The MOOLYA ERC-20 contract code is publicly available on etherscan.io so I started reading through it.
The vast majority of every ERC-20 contract code is actually just a copy-and-paste from the base ERC-20 template. That made it pretty easy to skim through. It was all totally generic, boilerplate stuff that just sets up the basic token mechanics.
But then I finally found something interesting in the custom-coded section at the end:
It’s simple enough so let’s dig into this line-by-line. The function takes as inputs an Ethereum address
_addr and an integer
_value. And even though this is a
public function, it has an access modifier
onlyOwner that does what it sounds like: only the contract owner has permission to execute this function.
require calls on lines 370 and 371 are just checking to make sure that the address provided isn’t a blank address (it would be nonsensical to do a token operation on nothingness) and that the address has at least
_value number of MOOLYA tokens in its balance.
Those were just sanity checks. Now we get to the interesting part!
Line 372 subtracts
_value number of MOOLYA tokens from the provided address’ balance. The contract owner is literally taking
_addr's tokens away.
And where do those tokens go? Well in line 373 those tokens are added to
msg.sender's balance. Someone else gets
_addr's MOOLYA! Who is the
msg.sender? It’s the address that called
burnReturn. And the only person who can call that function is the contract owner.
burnReturn is not a “burn” function — that’s the terminology for when tokens are destroyed. No, instead it gives the contract owner the power to take anyone’s MOOLYA tokens away at any time and deposit it back into his/her own account.
This function makes the contract owner this ERC-20’s god. A single point of total control over who owns how many tokens.
But wait, there’s more! As Dr. Josh Cotton pointed out (props on spotting it!), there’s also a problematic
This is another
onlyOwner function and by now you can probably read what it does yourself. On line 365 it increases the
msg.sender's token balance by the
_value specified. We know that means it goes to the contract owner. These new tokens are then added to the
totalSupply, making MOOLYA trivially inflationary. They can make more coins for themselves any time they like.
Good grief. MOOLYA is not decentralized. At all.
Sifting through the transaction logs everything finally made sense:
burnReturn was called to take back the 833,333 MOOLYA they sent out on mistake.
Clicking “Decode Input Data” translates the function params into friendlier values:
So the influencers were accidentally paid out 833,333 MOOLYA, the team realized their mistake and
burnReturned those tokens back to themselves, then sent out the proper 50,000 MOOYLA. Mystery solved.
In this case the
burnReturn saved the Moolya team some serious bacon.
Sadly, their transaction history seems to be riddled with other token distribution mistakes:
In their first attempt to send 5,555,556 tokens they forgot that Ethereum contracts are denominated in wei (1 ether = 1x10¹⁸ wei). These two addresses were expecting $333,333 USD worth of MOOLYA and instead only got dust. That is until the second transaction where the Moolya team did the wei conversion correctly.
Silly mistake. But at least they learned from it. Or not.
Same problem but, uh, wait, the fix-up transfer didn’t go to the same address! 0xe96 got dust while two minutes later 0x091 got the amount that they clearly intended to originally send to 0xe96. Man, nice to be 0x091… when it whales it pours!
It’s starting to become clear what the
burnReturn is for. It may or may not be for nefarious token-stealing purposes down the road. But it’s clearly necessary to fix up the Moolya team’s many screwups.
I’d like to return to the MOOLYA smart contract to point out a few more concerns.
The first is the blank line on line 375. This is a pretty ticky-tacky complaint, but why is that blank line there? Programmers have certain formatting rules (really more like a tidiness aesthetic) that often become dogmatic (the “Silicon Valley” episode where using spaces instead of tabs is grounds for ending a relationship is really a thing). That blank line should not be there. That bothers me.
What bothers me more is what should be in that blank line but isn’t: an Event. Think of an Event as a way to announce important things happening in your smart contract. For example
Transfer is a crucial Event for an ERC-20 as it announces when one user has moved funds to another user. Events even get their own section in block explorers:
If you look through MOOLYA’s Events, you’ll see a stream of
Transfers but you know what you don’t see? There are no
BurnReturn Events. Why is that? Every other token transfer operation triggers a
Transfer Event but when the contract owner moves your tokens no such matching Event is fired? Not surprisingly there’s no
TokensMinted Event, either.
We know that you can’t hide your tracks on the blockchain, but you can make them a bit harder to find. This is sketchy.
That blank line on 375 should have a
Transfer Event (and maybe it even did at one point in time). It’s trivial. In fact, here’s what should be there:
emit Transfer(_addr, msg.sender, _value);
There, was that so hard?
Elsewhere in the custom-coded section there is an unforgivable formatting violation:
Do you see it?
allocate's function body is not indented! In solidity this doesn’t affect the code but in languages like python and ruby this would be disastrous. This is simply not done.
This is a sign of mind-boggling sloppiness. The indentation issue in particular is proof that there was no code review by another developer (programmers are wise enough to not just simply trust themselves that they built something right; real project teams always require a separate set of eyeballs on each developer’s code).
The custom-coded part of MOOLYA is only 40 lines of code. That’s it! The rest is copy-and-paste. It’s deeply disconcerting to find this many issues in just 40 lines.
So is this a scam?
I don’t know. But it doesn’t even matter what their intentions are. The fact is that the contract has an unacceptable back door. Can it be fixed? Well, the contract owner could renounce the ownership of the contract once the ICO is complete, thereby rendering
mintable totally unusable from that point forward.
But should you take it on faith that they’ll do the right thing after you’ve invested your money? And what does the code sloppiness and the incompetent token transfer mishaps say about the team running this project?
The Moolya team seems to be entirely Indian while the ICO info all points to Estonia. Maybe they outsourced their ICO and just picked a bad partner to work with? I dunno. But for an ICO that is looking to raise $3–$25 million USD (and has already raised ~$7 million USD at time of writing), I would expect all of that value to be entrusted to something stronger than these shaky 40 lines of code.
All I can say at this point is:
DYOR: Do Your Own Research
Don’t trust, verify.