Kava 3 Chain Advisory

Kevin Davis
4 min readJun 27, 2020

--

TLDR: There is a minor bug that affects changing the BEP3 asset supply, which means that the current value of 40000 BNB cannot be currently updated. No user funds are at risk due to this minor bug.

We’ve coded ourselves into a knot

The kava-3 blockchain uses parameters to govern the state of the blockchain. By changing parameters via governance, Kava can manage how the chain functions and respond to changing conditions in a time-sensitive manner. One of those parameters, the asset supply limit for BEP3 assets, has a minor bug which causes changes in the parameter value not to take effect, which means that the current supply limit, 40000BNB, cannot be updated.

How can it be fixed?

The fix required is a few lines of code which enforce that when the parameter value for supply limit is updated, the state of the application is updated at that time. Importantly, this is a breaking change so we can’t just release a new version of v0.8 to fix this.

How the fix will be implemented

Asynchronous network update

A version of the software has been written that fixes the minor bug. In the bug fix, a time is specified when that code will take effect. Validators update their nodes before the specified time and the network will update smoothly without service interruption. Any nodes left on the previous version of the software will be forked off at that time. This is essentially the process used by the Ethereum core developers during what they call ‘network upgrades’.

Technical addendum

This section gives a breakdown of where the minor bug is in the kava code. The code in question is in the bep3 module on kava. In the Params for the module, the assets which are supported are specified as follows:

type AssetParam struct {
Denom string `json:"denom" yaml:"denom"` // name of the asset
CoinID int `json:"coin_id" yaml:"coin_id"` // internationally recognized coin ID
Limit sdk.Int `json:"limit" yaml:"limit"` // asset supply limit
Active bool `json:"active" yaml:"active"` // denotes if asset is active or paused
}

Each asset specifies a limit — which is the number of units of that asset that can exist before further incoming swaps are disallowed. When the chain starts, the following code sets the initial limit for each asset:

// Initialize supported assets
for _, asset := range gs.Params.SupportedAssets {
zeroCoin := sdk.NewCoin(asset.Denom, sdk.NewInt(0))
supply := NewAssetSupply(asset.Denom, zeroCoin, zeroCoin,
zeroCoin, sdk.NewCoin(asset.Denom, asset.Limit))
keeper.SetAssetSupply(ctx, supply, []byte(asset.Denom))
}

The function SetAssetSupply sets the limit for each asset at the prefix 0x02 in the application database.

// SetAssetSupply updates an asset's current active supplyfunc (k Keeper) SetAssetSupply(ctx sdk.Context, supply types.AssetSupply, denom []byte) {
store := prefix.NewStore(
ctx.KVStore(k.key),
types.AssetSupplyKeyPrefix, //0x02
)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(supply)
store.Set(denom, bz)
}

When the parameter value for AssetParam.Limit is updated, there is no corresponding function that updates the value in the store (by calling SetAssetSupply ). Thus, updating the parameters does not propagate to the application state.

When a new swap is created, the function IncrementIncomingAssetSupply is called, which checks that the swap is not over the asset limit stored at prefix 0x02 . Because this value has not been updated, the swap fails this check:

// IncrementIncomingAssetSupply increments an asset's incoming supply
func (k Keeper) IncrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
// Result of (current + incoming + amount) must be under asset's
//limit
totalSupply := supply.CurrentSupply.Add(supply.IncomingSupply)

if supply.SupplyLimit.IsLT(totalSupply.Add(coin)) {
return sdkerrors.Wrapf(
types.ErrExceedsSupplyLimit,
"increase %s, asset supply %s, limit %s",
coin, totalSupply, supply.SupplyLimit,
)
}
...

The fix is to add a function to the bep3 begin blocker that updates the value of AssetSupply when the value is updated in the parameters:

// BeginBlocker on every block expires outdated atomic swaps, removes closed
// swap from long term storage (default storage time of 1 week), and updates
// asset supply limits that may have changed via governance.
func BeginBlocker(ctx sdk.Context, k Keeper) {
k.UpdateExpiredAtomicSwaps(ctx)
k.DeleteClosedAtomicSwapsFromLongtermStorage(ctx)
k.UpdateAssetSupplies(ctx)
}

The UpdateAssetSupply function reconciles differences between the parameters and what’s in the store, setting the store to match the parameters.

// UpdateAssetSupplies applies updates to the asset limit from parameters to the asset supplies
func (k Keeper) UpdateAssetSupplies(ctx sdk.Context) error {
params := k.GetParams(ctx)
for _, supportedAsset := range params.SupportedAssets {
asset, found := k.GetAssetSupply(
ctx, []byte(supportedAsset.Denom),
)
if !found {
continue
}
if asset.SupplyLimit.Amount != supportedAsset.Limit {
asset.SupplyLimit = sdk.NewCoin(
supportedAsset.Denom, supportedAsset.Limit,
)
k.SetAssetSupply(ctx, asset, []byte(supportedAsset.Denom))
}
}
return nil
}

To implement this as an asynchronous network fork, we add an activationTime argument to the function which specifies when UpdateAssetSupplies should run. For example, we specify that UpdateAssetSupply only run if ctx.BlockTime().After(activationTime) and set activation time to be one week after the bugfix release is published. Note that ctx.BlockTime() is the value of the block time agreed by the validator set, and is not subject to wall clock drift.

Stay in touch!

Disclaimer: This content is provided for informational purposes only, and should not be relied upon as legal, business, investment, or tax advice. You should consult your own advisers as to those matters. References to any securities or digital assets are for illustrative purposes only, and do not constitute an investment recommendation or offer to provide investment advisory services. Furthermore, this content is not directed at nor intended for use by any investors or prospective investors, and may not under any circumstances be relied upon when making investment decisions.

--

--

Kevin Davis

I work on interledger and other cool tech in the blockchain and interoperability space for @kava-labs.