From d2e117d371ba2bef4a45b60a999f5d6e658e84c9 Mon Sep 17 00:00:00 2001 From: leonz789 Date: Thu, 29 Aug 2024 20:31:30 +0800 Subject: [PATCH] test: add tests, fix related bugs --- go.mod | 1 + go.sum | 2 + x/oracle/keeper/native_token.go | 205 ++++++++++++-- x/oracle/keeper/native_token_test.go | 396 +++++++++++++++++++++++++++ 4 files changed, 575 insertions(+), 29 deletions(-) create mode 100644 x/oracle/keeper/native_token_test.go diff --git a/go.mod b/go.mod index daaeac22d..81beb849f 100644 --- a/go.mod +++ b/go.mod @@ -155,6 +155,7 @@ require ( github.com/holiman/uint256 v1.2.3 // indirect github.com/huandu/skiplist v1.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect + github.com/imroc/biu v0.0.0-20170329141542-0376ce6761c0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index 432118e65..880f3c79e 100644 --- a/go.sum +++ b/go.sum @@ -1147,6 +1147,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/imroc/biu v0.0.0-20170329141542-0376ce6761c0 h1:pkyNAS9IQiZgseFrdhZC4cloBo2k2O2Son/k+3NquwY= +github.com/imroc/biu v0.0.0-20170329141542-0376ce6761c0/go.mod h1:wscexmyH+oDXfQr1q8PAZUXfKnxCUcNm62D/M5Ec8Lw= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= diff --git a/x/oracle/keeper/native_token.go b/x/oracle/keeper/native_token.go index 4efb46996..7e7905d7c 100644 --- a/x/oracle/keeper/native_token.go +++ b/x/oracle/keeper/native_token.go @@ -18,6 +18,39 @@ import ( var stakerList types.StakerList +func (k Keeper) GetStakerInfo(ctx sdk.Context, assetID, stakerAddr string) types.StakerInfo { + store := ctx.KVStore(k.storeKey) + stakerInfo := types.StakerInfo{} + value := store.Get(types.NativeTokenStakerKey(assetID, stakerAddr)) + if value == nil { + return stakerInfo + } + k.cdc.MustUnmarshal(value, &stakerInfo) + return stakerInfo +} + +func (k Keeper) GetStakerDelegations(ctx sdk.Context, assetID, stakerAddr string) types.StakerDelegationInfo { + store := ctx.KVStore(k.storeKey) + value := store.Get(types.NativeTokenStakerDelegationKey(assetID, stakerAddr)) + stakerDelegation := types.StakerDelegationInfo{} + if value == nil { + return stakerDelegation + } + k.cdc.MustUnmarshal(value, &stakerDelegation) + return stakerDelegation +} + +func (k Keeper) GetOperatorInfo(ctx sdk.Context, assetID, operator string) types.OperatorInfo { + store := ctx.KVStore(k.storeKey) + value := store.Get(types.NativeTokenOperatorKey(assetID, operator)) + operatorInfo := types.OperatorInfo{} + if value == nil { + return operatorInfo + } + k.cdc.MustUnmarshal(value, &operatorInfo) + return operatorInfo +} + // TODO, NOTE: price changes will effect reward/slash calculation, every time one staker's price changed, it's reward/slash amount(LST) should be cleaned or recalculated immediately // TODO: validatorIndex // amount: represents for originalToken @@ -37,9 +70,44 @@ func (k Keeper) UpdateNativeTokenByDepositOrWithdraw(ctx sdk.Context, assetID, s // calculate amount of virtual LST from nativetoken with price amountInt := convertAmountOriginalIntToAmountFloat(amount, stakerInfo.PriceList[latestIndex].Price).RoundInt() stakerInfo.TotalDeposit = stakerInfo.TotalDeposit.Add(amountInt) + + keyStakerList := types.NativeTokenStakerListKey(assetID) + valueStakerList := store.Get(keyStakerList) + stakerList := &types.StakerList{} + if valueStakerList != nil { + k.cdc.MustUnmarshal(valueStakerList, stakerList) + } + exists := false + for idx, stakerExists := range stakerList.StakerAddrs { + if stakerExists == stakerAddr { + if !stakerInfo.TotalDeposit.IsPositive() { + stakerList.StakerAddrs = append(stakerList.StakerAddrs[:idx], stakerList.StakerAddrs[idx+1:]...) + valueStakerList = k.cdc.MustMarshal(stakerList) + store.Set(keyStakerList, valueStakerList) + } + exists = true + stakerInfo.StakerIndex = int64(idx) + break + } + } + // update totalDeposit of staker, and price won't change on either deposit or withdraw - bz := k.cdc.MustMarshal(stakerInfo) - store.Set(key, bz) + if !stakerInfo.TotalDeposit.IsPositive() { + store.Delete(key) + } else { + bz := k.cdc.MustMarshal(stakerInfo) + store.Set(key, bz) + } + + if !exists { + if !stakerInfo.TotalDeposit.IsPositive() { + // this should not happened, if a staker execute the 'withdraw' action, he must have already been in the stakerList + return amountInt + } + stakerList.StakerAddrs = append(stakerList.StakerAddrs, stakerAddr) + valueStakerList = k.cdc.MustMarshal(stakerList) + store.Set(keyStakerList, valueStakerList) + } return amountInt } @@ -49,15 +117,16 @@ func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operator store := ctx.KVStore(k.storeKey) keyOperator := types.NativeTokenOperatorKey(assetID, operatorAddr) operatorInfo := &types.OperatorInfo{} - value := store.Get(keyOperator) - if value == nil { + valueOperator := store.Get(keyOperator) + if valueOperator == nil { operatorInfo = types.NewOperatorInfo(operatorAddr) } else { - k.cdc.MustUnmarshal(value, operatorInfo) + k.cdc.MustUnmarshal(valueOperator, operatorInfo) } stakerInfo := &types.StakerInfo{} keyStaker := types.NativeTokenStakerKey(assetID, stakerAddr) - if value = store.Get(keyStaker); value == nil { + value := store.Get(keyStaker) + if value == nil { panic("staker must exist before delegation") } k.cdc.MustUnmarshal(value, stakerInfo) @@ -69,15 +138,29 @@ func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operator operatorAmountFloat = operatorAmountFloat.Add(amountFloat) // update operator's price for native token base on new delegation - operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{ - Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat), - Block: uint64(ctx.BlockHeight()), - }) - + if valueOperator == nil { + // undelegation should not happen on nil operatorInfo, this is delegate case + operatorInfo.PriceList = []*types.PriceInfo{ + { + Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat), + Block: uint64(ctx.BlockHeight()), + }, + } + } else if operatorAmountFloat.IsPositive() { + // if amount <=0 thies operatorInfo will be rmoved, no need to append any price + operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{ + Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat), + Block: uint64(ctx.BlockHeight()), + }) + } // update operator's total amount for native token, for this 'amount' we don't disginguish different tokens from different stakers. That difference reflects in 'operator price' operatorInfo.TotalAmount = operatorAmountFloat.RoundInt() - bz := k.cdc.MustMarshal(operatorInfo) - store.Set(keyOperator, bz) + if operatorInfo.TotalAmount.IsPositive() { + bz := k.cdc.MustMarshal(operatorInfo) + store.Set(keyOperator, bz) + } else { + store.Delete(keyOperator) + } amountInt := amountFloat.RoundInt() // update staker's related operator list keyDelegation := types.NativeTokenStakerDelegationKey(assetID, stakerAddr) @@ -94,7 +177,7 @@ func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operator for idx, delegationInfo := range stakerDelegation.Delegations { if delegationInfo.OperatorAddr == operatorAddr { if delegationInfo.Amount = delegationInfo.Amount.Add(amountInt); !delegationInfo.Amount.IsPositive() { - stakerDelegation.Delegations = append(stakerDelegation.Delegations[0:idx], stakerDelegation.Delegations[idx:]...) + stakerDelegation.Delegations = append(stakerDelegation.Delegations[:idx], stakerDelegation.Delegations[idx+1:]...) } value = k.cdc.MustMarshal(stakerDelegation) store.Set(keyDelegation, value) @@ -150,6 +233,9 @@ func (k Keeper) GetStakerList(ctx sdk.Context, assetID string) types.StakerList } func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error { + if len(rawData) < 32 { + return errors.New("length of indicate maps for stakers shoule be exactly 32 bytes") + } sl := k.getStakerList(ctx, assetID) if len(sl.StakerAddrs) == 0 { return errors.New("staker list is empty") @@ -221,38 +307,54 @@ func (k Keeper) getStakerList(ctx sdk.Context, assetID string) types.StakerList func parseBalanceChange(rawData []byte, sl types.StakerList) (map[string]int, error) { indexs := rawData[:32] changes := rawData[32:] - // lenChanges := len(changes) index := -1 - byteIndex := -1 - bitOffset := 5 + byteIndex := 0 + bitOffset := 0 + lengthBits := 5 stakerChanges := make(map[string]int) for _, b := range indexs { for i := 7; i >= 0; i-- { - // staker's index start from 1 index++ if (b>>i)&1 == 1 { - // effect balance f stakerAddr[index] has changed - lenValue := int(changes[byteIndex] >> 4) + lenValue := changes[byteIndex] << bitOffset + bitsLeft := 8 - bitOffset + lenValue >>= (8 - lengthBits) + if bitsLeft < lengthBits { + byteIndex++ + lenValue |= changes[byteIndex] >> (8 - lengthBits + bitsLeft) + bitOffset = lengthBits - bitsLeft + } else { + if bitOffset += lengthBits; bitOffset == 8 { + bitOffset = 0 + } + if bitsLeft == lengthBits { + byteIndex++ + } + } + + symbol := lenValue & 1 + lenValue >>= 1 if lenValue <= 0 { return stakerChanges, errors.New("length of change value must be at least 1 bit") } - symbol := (changes[byteIndex] >> 3) & 1 + bitsExtracted := 0 stakerChange := 0 - for j := 0; j < lenValue; j++ { - byteIndex++ - byteValue := changes[byteIndex] << bitOffset - // byteValue <<= bitOffset + for bitsExtracted < int(lenValue) { //0<8, offset: bitsLeft := 8 - bitOffset - if bitsExtracted+bitsLeft > lenValue { - bitsLeft = lenValue - bitsExtracted - bitOffset = bitsLeft + byteValue := changes[byteIndex] << bitOffset + if (int(lenValue) - bitsExtracted) < bitsLeft { + bitsLeft = int(lenValue) - bitsExtracted + bitOffset += bitsLeft } else { + byteIndex++ bitOffset = 0 } - byteValue = (byteValue >> (8 - bitsLeft)) & ((1 << bitsLeft) - 1) + byteValue >>= (8 - bitsLeft) stakerChange = (stakerChange << bitsLeft) | int(byteValue) + bitsExtracted += bitsLeft } + stakerChange++ if symbol == 1 { stakerChange *= -1 } @@ -263,6 +365,51 @@ func parseBalanceChange(rawData []byte, sl types.StakerList) (map[string]int, er return stakerChanges, nil } +// func parseBalanceChange(rawData []byte, sl types.StakerList) (map[string]int, error) { +// indexs := rawData[:32] +// changes := rawData[32:] +// // lenChanges := len(changes) +// index := -1 +// byteIndex := -1 +// bitOffset := 5 +// stakerChanges := make(map[string]int) +// for _, b := range indexs { +// for i := 7; i >= 0; i-- { +// // staker's index start from 1 +// index++ +// if (b>>i)&1 == 1 { +// // effect balance f stakerAddr[index] has changed +// lenValue := int(changes[byteIndex] >> 4) +// if lenValue <= 0 { +// return stakerChanges, errors.New("length of change value must be at least 1 bit") +// } +// symbol := (changes[byteIndex] >> 3) & 1 +// bitsExtracted := 0 +// stakerChange := 0 +// for j := 0; j < lenValue; j++ { +// byteIndex++ +// byteValue := changes[byteIndex] << bitOffset +// // byteValue <<= bitOffset +// bitsLeft := 8 - bitOffset +// if bitsExtracted+bitsLeft > lenValue { +// bitsLeft = lenValue - bitsExtracted +// bitOffset = bitsLeft +// } else { +// bitOffset = 0 +// } +// byteValue = (byteValue >> (8 - bitsLeft)) & ((1 << bitsLeft) - 1) +// stakerChange = (stakerChange << bitsLeft) | int(byteValue) +// } +// if symbol == 1 { +// stakerChange *= -1 +// } +// stakerChanges[sl.StakerAddrs[index]] = stakerChange +// } +// } +// } +// return stakerChanges, nil +// } + func getLatestOperatorPriceFloat(operatorInfo *types.OperatorInfo) sdkmath.LegacyDec { latestIndex := len(operatorInfo.PriceList) - 1 return operatorInfo.PriceList[latestIndex].Price diff --git a/x/oracle/keeper/native_token_test.go b/x/oracle/keeper/native_token_test.go new file mode 100644 index 000000000..c1d393f51 --- /dev/null +++ b/x/oracle/keeper/native_token_test.go @@ -0,0 +1,396 @@ +package keeper_test + +import ( + "encoding/binary" + "strings" + + sdkmath "cosmossdk.io/math" + assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types" + "github.com/ExocoreNetwork/exocore/x/oracle/types" + "github.com/ethereum/go-ethereum/common" + "github.com/imroc/biu" +) + +// workflow: +// 1. Deposit. into staker_A +// 1. stakerInfo {totalDeposit, price} - new +// 2. stakerList - new +// +// 2. Delegate. into operator_A +// 1. stakerDelegation_AA {amount, operator} - new +// 2. operatorInfo_A {totalAmount, price} - new +// +// 3. Msg. minus staker_A's amountOriginal +// 1. stakerInfo_A {price-change} - update +// 2. operatorInfo_A {price-change} - update +// +// 4. Deposit more. into staker_A +// 1. stakerInfo {totalDeposit-change} -update +// +// 5. Msg. add staker_A's amountOriginal +// 1. stakerInfo_A {price-change} - update +// 2. operatorInfo_A {price-change} - update +// +// 6. delegate into operator_A +// 1. stakerDelegation_AA {amount-change} - update +// 2. operatorInfo_A {totalAmount-change} - update +// +// 7. Undelegate from operator_A +// 1. stakerDelegation_AA {amount-chagne} - update +// 2. operatorInfo_A {price-change, totalAmount-change} - update +// +// 8. UndelegateAll from operator_A +// 1. stakerDelegation_AA item-removed +// 2. operatorInfo_A totalAmount->0-> operatorInfo removed +// +// 9. withdrawAll from staker_A +// 1. stakerInfo removed +// 2. stakerList removed + +func (ks *KeeperSuite) TestNativeTokenLifeCycleOneStaker() { + operator := ks.Operators[0] + operatorStr := operator.String() + stakerStr := common.Address(operator.Bytes()).String() + assetID := assetstypes.NativeETHAssetID + // 1. deposit amount 100 + amount100 := sdkmath.NewIntFromUint64(100) + ks.k.UpdateNativeTokenByDepositOrWithdraw(ks.ctx, assetID, stakerStr, amount100) + // - 1.1 check stakerInfo + stakerInfo := ks.k.GetStakerInfo(ks.ctx, assetID, stakerStr) + ks.Equal(stakerInfo.TotalDeposit, amount100) + // - 1.2 check stakerList + stakerList := ks.k.GetStakerList(ks.ctx, assetID) + ks.Equal(stakerList.StakerAddrs[0], stakerStr) + // 2. delegateTo operator with amount 80 + amount80 := sdkmath.NewIntFromUint64(80) + ks.k.UpdateNativeTokenByDelegation(ks.ctx, assetID, operatorStr, stakerStr, amount80) + // - 2.1 check stakerDelegatioin + stakerDelegation := ks.k.GetStakerDelegations(ks.ctx, assetID, stakerStr) + ks.Equal(len(stakerDelegation.Delegations), 1) + ks.Equal(stakerDelegation.Delegations[0].OperatorAddr, operatorStr) + ks.Equal(stakerDelegation.Delegations[0].Amount, amount80) + // - 2.2 check operatorInfo + operatorInfo := ks.k.GetOperatorInfo(ks.ctx, assetID, operatorStr) + ks.Equal(operatorInfo, types.OperatorInfo{ + OperatorAddr: operatorStr, + TotalAmount: amount80, + PriceList: []*types.PriceInfo{ + { + Price: sdkmath.LegacyNewDec(1), + Block: 2, + RoundID: 0, + }, + }, + }) + // 3. Msg. minus staker's amountOriginal + stakerChanges := [][]int{ + {0, -50}, + } + rawData := convertBalanceChangeToBytes(stakerChanges) + ks.k.UpdateNativeTokenByBalanceChange(ks.ctx, assetID, rawData, 9) + // - 3.1 check stakerInfo + stakerInfo = ks.k.GetStakerInfo(ks.ctx, assetID, stakerStr) + ks.Equal(stakerInfo.PriceList[len(stakerInfo.PriceList)-1].Price, sdkmath.LegacyNewDecWithPrec(5, 1)) + // - 3.2 check operatorInfo + operatorInfo = ks.k.GetOperatorInfo(ks.ctx, assetID, operatorStr) + ks.Equal(operatorInfo.PriceList[len(operatorInfo.PriceList)-1].Price, sdkmath.LegacyNewDecWithPrec(5, 1)) + ks.Equal(operatorInfo.PriceList[len(operatorInfo.PriceList)-1].RoundID, uint64(9)) + + // 4. deposit more. 100 + ks.k.UpdateNativeTokenByDepositOrWithdraw(ks.ctx, assetID, stakerStr, amount100) + // - 4.1 check stakerInfo + stakerInfo = ks.k.GetStakerInfo(ks.ctx, assetID, stakerStr) + amount300 := sdkmath.NewInt(300) + ks.Equal(stakerInfo.TotalDeposit, amount300) + // 5. Msg. add staker's amountOriginal + stakerChanges = [][]int{ + {0, 30}, + } + rawData = convertBalanceChangeToBytes(stakerChanges) + ks.k.UpdateNativeTokenByBalanceChange(ks.ctx, assetID, rawData, 11) + // - 5.1 check stakerInfo + stakerInfo = ks.k.GetStakerInfo(ks.ctx, assetID, stakerStr) + ks.Equal(types.PriceInfo{ + Price: sdkmath.LegacyNewDecWithPrec(6, 1), + Block: 2, + RoundID: 11, + }, *stakerInfo.PriceList[2]) + ks.Equal(amount300, stakerInfo.TotalDeposit) + // - 5.2 check operatorInfo + operatorInfo = ks.k.GetOperatorInfo(ks.ctx, assetID, operatorStr) + ks.Equal(sdkmath.LegacyNewDecWithPrec(6, 1), operatorInfo.PriceList[len(operatorInfo.PriceList)-1].Price) + + // 6. delegate more. 60->100 + amount60 := sdkmath.NewInt(60) + ks.k.UpdateNativeTokenByDelegation(ks.ctx, assetID, operatorStr, stakerStr, amount60) + // - 6.1 check delegation-record + stakerDelegation = ks.k.GetStakerDelegations(ks.ctx, assetID, stakerStr) + amount180 := sdkmath.NewInt(180) + ks.Equal(amount180, stakerDelegation.Delegations[0].Amount) + // - 6.2 check operatorInfo + operatorInfo = ks.k.GetOperatorInfo(ks.ctx, assetID, operatorStr) + ks.Equal(amount180, operatorInfo.TotalAmount) + + // 7. undelegate. 72->120 + amount72N := sdkmath.NewInt(-72) + ks.k.UpdateNativeTokenByDelegation(ks.ctx, assetID, operatorStr, stakerStr, amount72N) + // - 7.1 check delegation-record + stakerDelegation = ks.k.GetStakerDelegations(ks.ctx, assetID, stakerStr) + ks.Equal(amount60, stakerDelegation.Delegations[0].Amount) + // - 7.2 check operatorInfo + operatorInfo = ks.k.GetOperatorInfo(ks.ctx, assetID, operatorStr) + ks.Equal(amount60, operatorInfo.TotalAmount) + + // 8. undelegate all + amount36N := sdkmath.NewInt(-36) + ks.k.UpdateNativeTokenByDelegation(ks.ctx, assetID, operatorStr, stakerStr, amount36N) + // - 8.1 check delegation-record + stakerDelegation = ks.k.GetStakerDelegations(ks.ctx, assetID, stakerStr) + ks.Equal(0, len(stakerDelegation.Delegations)) + // - 8.2 check operatorInfo + operatorInfo = ks.k.GetOperatorInfo(ks.ctx, assetID, operatorStr) + ks.Equal(types.OperatorInfo{}, operatorInfo) + + // 9. withdraw all + amount180N := sdkmath.NewInt(-180) + ks.k.UpdateNativeTokenByDepositOrWithdraw(ks.ctx, assetID, stakerStr, amount180N) + // - 9.1 check stakerInfo + stakerInfo = ks.k.GetStakerInfo(ks.ctx, assetID, stakerStr) + ks.Equal(types.StakerInfo{}, stakerInfo) + // - 9.2 check stakerList + stakerList = ks.k.GetStakerList(ks.ctx, assetID) + ks.Equal(0, len(stakerList.StakerAddrs)) +} + +func convertBalanceChangeToBytes(stakerChanges [][]int) []byte { + if len(stakerChanges) == 0 { + return nil + } + str := "" + index := 0 + changeBytesList := make([][]byte, 0, len(stakerChanges)) + bitsList := make([]int, 0, len(stakerChanges)) + for _, stakerChange := range stakerChanges { + str += strings.Repeat("0", stakerChange[0]-index) + "1" + index = stakerChange[0] + 1 + + // change amount -> bytes + change := stakerChange[1] + var changeBytes []byte + symbol := 1 + if change < 0 { + symbol = -1 + change *= -1 + } + change-- + bits := 0 + if change == 0 { + bits = 1 + changeBytes = []byte{byte(0)} + } else { + tmpChange := change + for tmpChange > 0 { + bits++ + tmpChange /= 2 + } + if change < 256 { + // 1 byte + changeBytes = []byte{byte(change)} + changeBytes[0] <<= (8 - bits) + } else { + // 2 byte + changeBytes = make([]byte, 2) + binary.BigEndian.PutUint16(changeBytes, uint16(change)) + moveLength := 16 - bits + changeBytes[0] <<= moveLength + tmp := changeBytes[1] >> (8 - moveLength) + changeBytes[0] |= tmp + changeBytes[1] <<= moveLength + } + } + + // use lower 4 bits to represent the length of valid change value in bits format + bitsLengthBytes := []byte{byte(bits)} + bitsLengthBytes[0] <<= 4 + if symbol < 0 { + bitsLengthBytes[0] |= 8 + } + + tmp := changeBytes[0] >> 5 + bitsLengthBytes[0] |= tmp + if bits <= 3 { + changeBytes = nil + } else { + changeBytes[0] <<= 3 + } + + if len(changeBytes) == 2 { + tmp = changeBytes[1] >> 5 + changeBytes[0] |= tmp + if bits <= 11 { + changeBytes = changeBytes[:1] + } else { + changeBytes[1] <<= 3 + } + } + bitsLengthBytes = append(bitsLengthBytes, changeBytes...) + changeBytesList = append(changeBytesList, bitsLengthBytes) + bitsList = append(bitsList, bits) + } + + l := len(bitsList) + changeResult := changeBytesList[l-1] + bitsList[len(bitsList)-1] = bitsList[len(bitsList)-1] + 5 + for i := l - 2; i >= 0; i-- { + prev := changeBytesList[i] + + byteLength := 8 * len(prev) + bitsLength := bitsList[i] + 5 + // delta must <8 + delta := byteLength - bitsLength + if delta == 0 { + changeResult = append(prev, changeResult...) + bitsList[i] = bitsLength + bitsList[i+1] + } else { + // delta : (0,8) + tmp := changeResult[0] >> (8 - delta) + prev[len(prev)-1] |= tmp + if len(changeResult) > 1 { + for j := 1; j < len(changeResult); j++ { + changeResult[j-1] <<= delta + tmp := changeResult[j] >> (8 - delta) + changeResult[j-1] |= tmp + } + } + changeResult[len(changeResult)-1] <<= delta + left := bitsList[i+1] % 8 + if bitsList[i+1] > 0 && left == 0 { + left = 8 + } + if left <= delta { + changeResult = changeResult[:len(changeResult)-1] + } + changeResult = append(prev, changeResult...) + bitsList[i] = bitsLength + bitsList[i+1] + } + } + str += strings.Repeat("0", 256-index) + bytesIndex := biu.BinaryStringToBytes(str) + + result := append(bytesIndex, changeResult...) + return result +} + +// func convertBalanceChangeToBytes(stakerChanges [][]int) []byte { +// if len(stakerChanges) == 0 { +// return nil +// } +// str := "" +// index := 0 +// changeBytesList := make([][]byte, 0, len(stakerChanges)) +// bitsList := make([]int, 0, len(stakerChanges)) +// for _, stakerChange := range stakerChanges { +// str += strings.Repeat("0", stakerChange[0]-index) + "1" +// index = stakerChange[0] + 1 +// +// // change amount -> bytes +// change := stakerChange[1] +// var changeBytes []byte +// symbol := 1 +// if change < 0 { +// symbol = -1 +// change *= -1 +// change-- +// } +// bits := 0 +// if change == 0 { +// bits = 1 +// changeBytes = []byte{byte(0)} +// } else { +// for change > 0 { +// bits++ +// change /= 2 +// } +// if change < 256 { +// // 1 byte +// changeBytes = []byte{byte(change)} +// changeBytes[0] <<= (8 - bits) +// } else { +// // 2 byte +// changeBytes = make([]byte, 0, 2) +// binary.BigEndian.PutUint16(changeBytes, uint16(change)) +// moveLength := 16 - bits +// changeBytes[0] <<= moveLength +// tmp := changeBytes[1] >> (8 - moveLength) +// changeBytes[0] |= tmp +// changeBytes[1] <<= moveLength +// } +// } +// +// // use lower 4 bits to represent the length of valid change value in bits format +// bitsLengthBytes := []byte{byte(bits)} +// bitsLengthBytes[0] <<= 4 +// if symbol < 0 { +// bitsLengthBytes[0] |= 8 +// } +// +// tmp := changeBytes[0] >> 5 +// bitsLengthBytes[0] |= tmp +// if bits <= 3 { +// changeBytes = nil +// } else { +// changeBytes[0] <<= 3 +// } +// +// if len(changeBytes) == 2 { +// tmp = changeBytes[1] >> 5 +// changeBytes[0] |= tmp +// if bits <= 11 { +// changeBytes = changeBytes[:1] +// } else { +// changeBytes[1] <<= 3 +// } +// } +// bitsLengthBytes = append(bitsLengthBytes, changeBytes...) +// changeBytesList = append(changeBytesList, bitsLengthBytes) +// bitsList = append(bitsList, bits) +// } +// +// l := len(bitsList) +// changeResult := changeBytesList[l-1] +// bitsList[len(bitsList)-1] = bitsList[len(bitsList)-1] + 5 +// for i := l - 2; i >= 0; i-- { +// prev := changeBytesList[i] +// +// byteLength := 8 * len(prev) +// bitsLength := bitsList[i] + 5 +// // delta must <8 +// delta := byteLength - bitsLength +// if delta == 0 { +// changeResult = append(prev, changeResult...) +// bitsList[i] = bitsLength + bitsList[i+1] +// } else { +// // delta : (0,8) +// tmp := changeResult[0] >> (8 - delta) +// prev[len(prev)-1] |= tmp +// if len(changeResult) > 1 { +// for j := 1; j < len(changeResult); j++ { +// changeResult[j-1] <<= delta +// tmp := changeResult[j] >> (8 - delta) +// changeResult[j-1] |= tmp +// } +// } +// changeResult[len(changeResult)-1] <<= delta +// if bitsList[i+1]%8 <= delta { +// changeResult = changeResult[:len(changeResult)-1] +// } +// changeResult = append(prev, changeResult...) +// bitsList[i] = bitsLength + bitsList[i+1] +// } +// } +// str += strings.Repeat("0", 256-index) +// bytesIndex := biu.BinaryStringToBytes(str) +// +// result := append(bytesIndex, changeResult...) +// return result +// }