Skip to content

Latest commit

 

History

History
106 lines (73 loc) · 3 KB

004.md

File metadata and controls

106 lines (73 loc) · 3 KB

Bitter Khaki Iguana

Medium

OverwriteParamsAtVersion reorders heightToVersionMap

Summary

OverwriteParamsAtVersion reorders heightToVersionMap

Root Cause

During upgrades the OverwriteParamsAtVersion updates the params for version in heightToVersionMap. It sorts heightToVersionMap by the Version. Afterwards it calls SetHeightToVersionMap. However the heightToVersionMap must be sorted by the start_height as it used by GetParamsForBtcHeight -> GetVersionForHeight -> sort.Search which expects a sorted slice (by height).

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/btcstaking/types/params.pb.go#L250

type HeightToVersionMap struct {
	// Pairs must be sorted by `start_height` in ascending order, without duplicates
	Pairs []*HeightVersionPair `protobuf:"bytes,1,rep,name=pairs,proto3" json:"pairs,omitempty"`
}

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/btcstaking/keeper/params.go#L99

func (k Keeper) OverwriteParamsAtVersion(ctx context.Context, v uint32, p types.Params) error {
    if err := p.Validate(); err != nil {
        return fmt.Errorf("cannot overwrite params at version %d: %w", v, err)
    }

    paramsStore := k.paramsStore(ctx)
    sp := types.StoredParams{
        Params:  p,
        Version: v,
    }

    heightToVersionMap := k.GetHeightToVersionMap(ctx)
    if heightToVersionMap == nil {
        heightToVersionMap = types.NewHeightToVersionMap()
    }

    // makes sure it is ordered by the version
@>    sort.Slice(heightToVersionMap.Pairs, func(i, j int) bool {
        return heightToVersionMap.Pairs[i].Version < heightToVersionMap.Pairs[j].Version
    })

    if v >= uint32(len(heightToVersionMap.Pairs)) {
        if err := heightToVersionMap.AddNewPair(uint64(p.BtcActivationHeight), v); err != nil {
            return err
        }
    } else {
        heightToVersionMap.Pairs[v] = types.NewHeightVersionPair(uint64(p.BtcActivationHeight), v)
    }

    paramsStore.Set(uint32ToBytes(v), k.cdc.MustMarshal(&sp))
@>    return k.SetHeightToVersionMap(ctx, heightToVersionMap)
}

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/btcstaking/types/params.go#L327

func (m *HeightToVersionMap) GetVersionForHeight(height uint64) (uint32, error) {
    if m.IsEmpty() {
        return 0, fmt.Errorf("height to version map is empty")
    }

    // Binary search to find the applicable version of the parameters
@>   idx := sort.Search(len(m.Pairs), func(i int) bool {
        return m.Pairs[i].StartHeight > height
    }) - 1

    if idx < 0 {
        return 0, fmt.Errorf("no parameters found for block height %d", height)
    }

    return m.Pairs[idx].Version, nil
}

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

Wrong order heightToVersionMap is saved. CreateBTCDelegation -> getTimeInfoAndParams -> GetParamsForBtcHeight will use wrong params.

PoC

No response

Mitigation

No response