Observations on zero premine protocols

Zero premine protocols have shown up with force, letting loose upon the world fledgling un-audited code with no premine. Meanwhile, the devs continue building the thing while flying through metaphorical blockchain thunderstorms. Think test piloting but with software, a rapidly growing, hyperventilating user base, and a deluge of cash (lest we forget the prime defi metric: Total Value Locked (TVL)).

FYI (or YFI??): YAM TVL was $563,111,070 as of 10pm August 12th. I don't even know the zenith of TVL for the YAM protocol in its short lived reign.

YAM Finance

We're now two days post YAM kickoff and a bug has been discovered in the rebasing contract. There is an all out effort to mobilize the nascent farming community to pass a proposal to secure our beloved yam protocol. As I sit here writing this, there's a collective 2,132,840 (worth $48,021,103) UNIV2 tokens stacked in YAM's UNIV2 staking pool.

Rebasing Woes

We're flying the plane while building, so lets paraphrase from this post: https://medium.com/@yamfinance/save-yam-245598d81cec

My familiarity with the contract code base is quite literally me writing this as I walk through the github repo.

There is a bug in the rebase function of the YAMToken.sol contract:

Link to start of rebase function: https://github.com/yam-finance/yam-protocol/blob/d10780268273fb4d97fae8a549ab22e4bb35e426/contracts/token/YAM.sol#L313

Link to actual line of bug: https://github.com/yam-finance/yam-protocol/blob/d10780268273fb4d97fae8a549ab22e4bb35e426/contracts/token/YAM.sol#L340

totalSupply = initSupply.mul(yamsScalingFactor);

As the author alluded to in the earlier post, should be:

totalSupply = initSupply.mul(yamsScalingFactor).div(BASE);

the initSupply is multiplied by the yamsScalingFactor but was not divided by the BASE constant. In Maker parlance, BASE == WAD (10 ** 18).

When the YAM token was initialized, yamsScalingFactor was set to BASE (remember: 10 ** 18). Line 340 takes initSupply and multiplies it by the this 18 digit uint, but fails to then divide by it.

In terms of arbitrary precision, here is the yamsScalingFactor value:

And the BASE constant value:

initSupply was multiplied by this large uint but not divided. It is a precision error.

Pretend we initialized with an initSupply of 1337, then it's easier to see the impact (and I'll even use a precision calculator for appropriate measure):

With the precision error bug:
1337000000000000000000 (bc -l <<< ' 1337 * 10 ^ 18 ')
Without the precision error bug:
1337.00000000000000000000 (bc -l <<< ' 1337 * 10 ^ 18 / 10 ^ 18')

Other noteworthy things

Only the Rebaser contract can call the yam.rebase() function. Also worth a mention is that only an Externally Owned Address (EOA) can call the rebaser.rebase() function, as enforced by the require(msg.sender == tx.origin) on line 309.

The initial rebase occurred at 7AM UTC August 12th and resulted in roughly $500k worth of yCRV being added to the YAM treasury. Any governance issue to rectify the hyper-inflating treasury supply must happen in the 12 hour window before the next rebase.

Mobilizing the community

To continue with the aviation analogies, we're mid-flight at about 10,000 ft (we never got to cruising altitude) and there's a fire in one engine. A manageable situation, but we need coordination... and we got coordination.

At this point, I delegated and positioned myself for both possible outcomes.... and went to bed.

While asleep, the community successfully garnered the required number of YAM votes to pass the proposal. I don't have the numbers at the moment, but may update this post with them later.

Landing failure

The delegated votes were secured, and then began the attempt to rescue the governance functionality of the protocol.

13-08-2020 7:12:03 UTC Transaction where the proposal was submitted to the GovernorAlpha contract: https://ethtx.info/mainnet/0x1d64875b24732bc2e8880cd0870ea8e301ddde683ce81fea418e9ab4feea90bb

My understanding here is that although the community had delegated enough YAM to meet the proposal threshold, there is a timelock feature of the current unix timestamp + 12.5 hours that is tee'd up from when the proposal is queued. And that timelock pushed the ability to propose beyond the second rebase. Once the second rebase happened, there were not enough votes to maintain the 1% threshold set from the imprecise initSupply variable.

Then, someone canceled the proposal. Although to be honest, it was already defeated.

13-08-2020 08:21:01 UTC Transaction where the proposal was canceled by an Externally Owned Address: https://ethtx.info/mainnet/0x1b98c761592b54498b9fbd41e6e4755cf6e5a1a6f13658d409081532d89d6bf0

From the Compound docs:

Cancel a proposal that has not yet been executed. The Guardian is the only one who may execute this unless the proposer does not maintain the delegates required to create a proposal. If the proposer does not have more delegates than the proposal threshold, anyone can cancel the proposal.

Source: https://compound.finance/docs/governance#cancel

The guardian address of GovernorAlpha contract is set to address(0) so cancellation by guardian is not a distinct possibility. In our situation here, the proposer did not have more delegates than the proposal threshold, and as a result anyone can cancel the proposal. And that cancel function was called for reasons unknown to me by an externally owned address: 0x24394A4758DBdCf6fcbC14dc35af64Ac0D9a450A.

At this point the cargo plane carrying all our beloved YAMs collided swiftly into the tarmac, and while it didn't go up into a ball of flames, all of our yams got smashed. Perhaps the plane is salvageable, but it's too early to tell, we can only assess the damage.


As @yamfinance explains on their twitter:
"The concern is the same as before: the yCRV accumulated in the reserves during rebase is at risk of being stuck if governance cannot pass proposals." See: https://twitter.com/YamFinance/status/1293814954164981760

In summary, and perhaps I'll do do a deeper analysis of the code, due to our failed landing the rebaser contract will continue minting tons of YAM into the protocol reserve. The YAM treasury will continue to hyper-inflate, permanently hindering any further governance action.

Late last night YAM was trading around ~$12/yam and now on Coingecko it's trading at $0.95/yam. If you're still in the UNI-V2 pool, please grab a parachute and exit immediately.

Main takeaway that is important to me: the community mobilized itself when it mattered and it did so in a 12 hour window, yet the protocol code prevented a timely rescue. We're but mere test pilots and we carry onward researching, implementing, and testing new ways to bootstrap and operate meaningful governance systems.

Show Comments