diff --git a/staking/types/delegation_test.go b/staking/types/delegation_test.go new file mode 100644 index 0000000000..d6111f4e7e --- /dev/null +++ b/staking/types/delegation_test.go @@ -0,0 +1,82 @@ +package types + +import ( + "math/big" + "testing" + + common "github.com/ethereum/go-ethereum/common" + common2 "github.com/harmony-one/harmony/internal/common" +) + +var ( + testAddr, _ = common2.Bech32ToAddress("one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k") + delegatorAddr = common.Address(testAddr) + delegationAmt = big.NewInt(100000) + // create a new delegation: + delegation = NewDelegation(delegatorAddr, delegationAmt) +) + +func TestUndelegate(t *testing.T) { + epoch1 := big.NewInt(10) + amount1 := big.NewInt(1000) + delegation.Undelegate(epoch1, amount1) + + // check the undelegation's Amount + if delegation.Undelegations[0].Amount.Cmp(amount1) != 0 { + t.Errorf("undelegate failed, amount does not match") + } + // check the undelegation's Epoch + if delegation.Undelegations[0].Epoch.Cmp(epoch1) != 0 { + t.Errorf("undelegate failed, epoch does not match") + } + + epoch2 := big.NewInt(12) + amount2 := big.NewInt(2000) + delegation.Undelegate(epoch2, amount2) + + // check the number of undelegations + if len(delegation.Undelegations) != 2 { + t.Errorf("total number of undelegations should have been two") + } +} + +func TestTotalInUndelegation(t *testing.T) { + var totalAmount *big.Int = delegation.TotalInUndelegation() + + // check the total amount of undelegation + if totalAmount.Cmp(big.NewInt(3000)) != 0 { + t.Errorf("total undelegation amount is not correct") + } +} + +func TestDeleteEntry(t *testing.T) { + // add the third delegation + // Undelegations[]: 1000, 2000, 3000 + epoch3 := big.NewInt(15) + amount3 := big.NewInt(3000) + delegation.Undelegate(epoch3, amount3) + + // delete the second undelegation entry + // Undelegations[]: 1000, 3000 + deleteEpoch := big.NewInt(12) + delegation.DeleteEntry(deleteEpoch) + + // check if the Undelegtaions[1] == 3000 + if delegation.Undelegations[1].Amount.Cmp(big.NewInt(3000)) != 0 { + t.Errorf("deleting an undelegation entry fails, amount is not correct") + } +} + +func TestRemoveUnlockUndelegations(t *testing.T) { + lastEpochInCommitte := big.NewInt(16) + curEpoch := big.NewInt(24) + + epoch4 := big.NewInt(21) + amount4 := big.NewInt(4000) + delegation.Undelegate(epoch4, amount4) + + result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommitte) + if result.Cmp(big.NewInt(8000)) != 0 { + t.Errorf("removing an unlocked undelegation fails") + } +} diff --git a/staking/types/transaction_test.go b/staking/types/transaction_test.go index 4fc4447931..72400763fb 100644 --- a/staking/types/transaction_test.go +++ b/staking/types/transaction_test.go @@ -1,7 +1,8 @@ package types import ( - "fmt" + "bytes" + "errors" "math/big" "testing" @@ -19,6 +20,20 @@ var ( testBLSPubKey2 = "40379eed79ed82bebfb4310894fd33b6a3f8413a78dc4d43b98d0adc9ef69f3285df05eaab9f2ce5f7227f8cb920e809" ) +func createDelegate() (*StakingTransaction, error) { + dAddr, _ := common2.Bech32ToAddress(testAccount) + vAddr, _ := common2.Bech32ToAddress(testAccount) + stakePayloadMaker := func() (Directive, interface{}) { + return DirectiveDelegate, Delegate{ + DelegatorAddress: dAddr, + ValidatorAddress: vAddr, + Amount: big.NewInt(100), + } + } + gasPrice := big.NewInt(1) + return NewStakingTransaction(0, 21000, gasPrice, stakePayloadMaker) +} + func CreateTestNewTransaction() (*StakingTransaction, error) { dAddr, _ := common2.Bech32ToAddress(testAccount) @@ -96,9 +111,86 @@ func TestTransactionCopy(t *testing.T) { if cv1.CommissionRates.Rate.Equal(cv2.CommissionRates.Rate) { t.Errorf("CommissionRate should not be equal") } +} + +func TestHash(t *testing.T) { + stakingTx, err := CreateTestNewTransaction() + if err != nil { + t.Errorf("cannot create new staking transaction, %v\n", err) + } + hash := stakingTx.Hash() + if hash.String() == "" { + t.Errorf("cannot get hash of staking transaction, %v\n", err) + } + if stakingTx.Hash().String() != hash.String() { + t.Errorf("cannot set hash of staking transaction\n") + } +} + +func TestGasCost(t *testing.T) { + stakingTx, err := CreateTestNewTransaction() + if err != nil { + t.Errorf("cannot create validator staking transaction, %v\n", err) + } + if stakingTx.Gas() != 600000 { + t.Errorf("gas set incorrectly \n") + } + if stakingTx.GasPrice().Int64() != big.NewInt(1).Int64() { + t.Errorf("gas price set incorrectly \n") + } + cost, err := stakingTx.Cost() + if err != nil || cost.Int64() != 600100 { + t.Errorf("staking transaction cost is incorrect %v\n", err) + } + delegateTx, err := createDelegate() + if err != nil { + t.Errorf("cannot create delegate staking transaction, %v\n", err) + } + cost, err = delegateTx.Cost() + if err != nil || cost.Int64() != 21100 { + t.Errorf("staking transaction cost is incorrect %v\n", err) + } + dAddr, _ := common2.Bech32ToAddress(testAccount) + vAddr, _ := common2.Bech32ToAddress(testAccount) + delegateTx1, err := NewStakingTransaction(0, 21000, big.NewInt(1), func() (Directive, interface{}) { + return DirectiveCreateValidator, Delegate{ + DelegatorAddress: dAddr, + ValidatorAddress: vAddr, + Amount: big.NewInt(100), + } + }) + if _, err = delegateTx1.Cost(); err == nil { + t.Error("expected", errStakingTransactionTypeCastErr, "got", nil) + } +} + +func TestNonce(t *testing.T) { + stakingTx, err := CreateTestNewTransaction() + if err != nil { + t.Errorf("cannot create validator staking transaction, %v\n", err) + } + if stakingTx.Nonce() != 0 { + t.Error("incorrect nonce \n") + } +} - fmt.Println("cv1", cv1) - fmt.Println("cv2", cv2) - fmt.Println("cv1", cv1.Description) - fmt.Println("cv2", cv2.Description) +func TestData(t *testing.T) { + stakingTx, err := CreateTestNewTransaction() + if err != nil { + t.Errorf("cannot create validator staking transaction, %v\n", err) + } + encoded, err := stakingTx.RLPEncodeStakeMsg() + if err != nil { + t.Errorf("could not rlp encode staking tx %v\n", err) + } + if bytes.Compare(stakingTx.Data(), encoded) != 0 { + t.Error("RLPEncode and Data does not match \n") + } + if _, err = RLPDecodeStakeMsg(encoded, DirectiveCreateValidator); err != nil { + t.Errorf("could not rlp decode staking tx %v\n", err) + } + e := errors.New("rlp: expected input string or byte for common.Address, decoding into (types.Delegate).ValidatorAddress") + if _, err = RLPDecodeStakeMsg(encoded, DirectiveDelegate); err == nil { + t.Error("expected", e, "got", nil) + } } diff --git a/staking/types/validator_test.go b/staking/types/validator_test.go index 6a5c576e66..f8e579c9ba 100644 --- a/staking/types/validator_test.go +++ b/staking/types/validator_test.go @@ -1,18 +1,441 @@ package types import ( + "fmt" "math/big" + "strings" + "testing" - "github.com/ethereum/go-ethereum/common" + common "github.com/ethereum/go-ethereum/common" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/harmony-one/harmony/crypto/hash" + common2 "github.com/harmony-one/harmony/internal/common" + "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/numeric" + "github.com/harmony-one/harmony/shard" + "github.com/pkg/errors" ) -func CreateNewValidator() Validator { +var ( + testAddr1, _ = common2.Bech32ToAddress("one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy") + validatorAddr = common.Address(testAddr1) + desc = Description{ + Name: "john", + Identity: "john", + Website: "harmony.one.wen", + SecurityContact: "wenSecurity", + Details: "wenDetails", + } + blsPubKey = "ba41f49d70d40434110e32b269dc9b52879ca5fb2aee01c49311c45e008a4b6494c3bd9e6ef7954e39d25d023243b898" + blsPriKey = "0a8c69c12020a762e7087f52bacbe835b8a91728f7310a191e026001f753a00e" + slotPubKeys = setSlotPubKeys() + slotKeySigs = setSlotKeySigs() + + validator = createNewValidator() + wrapper = createNewValidatorWrapper(validator) + + delegationAmt1 = big.NewInt(1e18) + delegation1 = NewDelegation(delegatorAddr, delegationAmt1) + + longNameDesc = Description{ + Name: "WayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayneWayne1", + Identity: "wen", + Website: "harmony.one.wen", + SecurityContact: "wenSecurity", + Details: "wenDetails", + } + longIdentityDesc = Description{ + Name: "Wayne", + Identity: "wenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwenwe1", + Website: "harmony.one.wen", + SecurityContact: "wenSecurity", + Details: "wenDetails", + } + longWebsiteDesc = Description{ + Name: "Wayne", + Identity: "wen", + Website: "harmony.one.wenharmony.one.wenharmony.one.wenharmony.one.wenharmony.one.wenharmony.one.wenharmony.one.wenharmony.one.wenharmony.one.wenharmo1", + SecurityContact: "wenSecurity", + Details: "wenDetails", + } + longSecurityContactDesc = Description{ + Name: "Wayne", + Identity: "wen", + Website: "harmony.one.wen", + SecurityContact: "wenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuritywenSecuri", + Details: "wenDetails", + } + longDetailsDesc = Description{ + Name: "Wayne", + Identity: "wen", + Website: "harmony.one.wen", + SecurityContact: "wenSecurity", + Details: "wenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswwenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetailswenDetails", + } +) + +// Using public keys to create slot for validator +func setSlotPubKeys() []shard.BlsPublicKey { + p := &bls.PublicKey{} + p.DeserializeHexStr(blsPubKey) + pub := shard.BlsPublicKey{} + pub.FromLibBLSPublicKey(p) + return []shard.BlsPublicKey{pub} +} + +// Using private keys to create sign slot for message.CreateValidator +func setSlotKeySigs() []shard.BLSSignature { + messageBytes := []byte(BlsVerificationStr) + privateKey := &bls.SecretKey{} + privateKey.DeserializeHexStr(blsPriKey) + msgHash := hash.Keccak256(messageBytes) + signature := privateKey.SignHash(msgHash[:]) + var sig shard.BLSSignature + copy(sig[:], signature.Serialize()) + return []shard.BLSSignature{sig} +} + +// create a new validator +func createNewValidator() Validator { cr := CommissionRates{Rate: numeric.OneDec(), MaxRate: numeric.OneDec(), MaxChangeRate: numeric.ZeroDec()} c := Commission{cr, big.NewInt(300)} - d := Description{Name: "SuperHero", Identity: "YouWillNotKnow", Website: "under_construction", Details: "N/A"} - v := Validator{Address: common.Address{}, SlotPubKeys: nil, - LastEpochInCommittee: big.NewInt(20), MinSelfDelegation: big.NewInt(7), - Active: false, Commission: c, Description: d} + d := Description{Name: "Wayne", Identity: "wen", Website: "harmony.one.wen", Details: "best"} + v := Validator{ + Address: validatorAddr, + SlotPubKeys: slotPubKeys, + LastEpochInCommittee: big.NewInt(20), + MinSelfDelegation: big.NewInt(1e18), + MaxTotalDelegation: big.NewInt(3e18), + Active: false, + Commission: c, + Description: d, + CreationHeight: big.NewInt(12306), + } return v } + +// create a new validator wrapper +func createNewValidatorWrapper(v Validator) ValidatorWrapper { + return ValidatorWrapper{ + Validator: v, + } +} + +// Test MarshalValidator +func TestMarshalValidator(t *testing.T) { + _, err := MarshalValidator(validator) + if err != nil { + t.Errorf("MarshalValidator failed") + } +} + +// Test UnmarshalValidator +func TestMarshalUnmarshalValidator(t *testing.T) { + tmp, _ := MarshalValidator(validator) + _, err := UnmarshalValidator(tmp) + if err != nil { + t.Errorf("UnmarshalValidator failed!") + } +} + +// Test Print Slot Public Keys +func TestPrintSlotPubKeys(t *testing.T) { + printSlotPubKeys(validator.SlotPubKeys) +} + +func TestTotalDelegation(t *testing.T) { + // add a delegation to validator + // delegation.Amount = 10000 + wrapper.Delegations = append(wrapper.Delegations, delegation1) + totalNum := wrapper.TotalDelegation() + + // check if the numebr is 10000 + if totalNum.Cmp(big.NewInt(1e18)) != 0 { + t.Errorf("TotalDelegation number is not right") + } +} + +// check the validator wrapper's sanity +func TestValidatorSanityCheck(t *testing.T) { + err := validator.SanityCheck() + if err != nil { + t.Error("expected", nil, "got", err) + } + + v := Validator{ + Address: validatorAddr, + } + if err := v.SanityCheck(); err != errNeedAtLeastOneSlotKey { + t.Error("expected", errNeedAtLeastOneSlotKey, "got", err) + } + + v.SlotPubKeys = setSlotPubKeys() + if err := v.SanityCheck(); err != errNilMinSelfDelegation { + t.Error("expected", errNilMinSelfDelegation, "got", err) + } + v.MinSelfDelegation = big.NewInt(1e18) + if err := v.SanityCheck(); err != errNilMaxTotalDelegation { + t.Error("expected", errNilMaxTotalDelegation, "got", err) + } + v.MinSelfDelegation = big.NewInt(1e17) + v.MaxTotalDelegation = big.NewInt(3e18) + e := errors.Wrapf( + errMinSelfDelegationTooSmall, + "delegation-given %s", v.MinSelfDelegation.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + + v.MinSelfDelegation = big.NewInt(3e18) + v.MaxTotalDelegation = big.NewInt(1e18) + e = errors.Wrapf( + errInvalidMaxTotalDelegation, + "max-total-delegation %s min-self-delegation %s", + v.MaxTotalDelegation.String(), + v.MinSelfDelegation.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.MinSelfDelegation = big.NewInt(1e18) + v.MaxTotalDelegation = big.NewInt(3e18) + minusOneDec, _ := numeric.NewDecFromStr("-1") + plusTwoDec, _ := numeric.NewDecFromStr("2") + cr := CommissionRates{Rate: minusOneDec, MaxRate: numeric.OneDec(), MaxChangeRate: numeric.ZeroDec()} + c := Commission{cr, big.NewInt(300)} + v.Commission = c + e = errors.Wrapf( + errInvalidCommissionRate, "rate:%s", v.Rate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.Rate = plusTwoDec + e = errors.Wrapf( + errInvalidCommissionRate, "rate:%s", v.Rate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.Rate = numeric.MustNewDecFromStr("0.5") + v.Commission.MaxRate = minusOneDec + e = errors.Wrapf( + errInvalidCommissionRate, "rate:%s", v.MaxRate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.MaxRate = plusTwoDec + e = errors.Wrapf( + errInvalidCommissionRate, "rate:%s", v.MaxRate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.MaxRate = numeric.MustNewDecFromStr("0.9") + v.Commission.MaxChangeRate = minusOneDec + e = errors.Wrapf( + errInvalidCommissionRate, "rate:%s", v.MaxChangeRate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.MaxChangeRate = plusTwoDec + e = errors.Wrapf( + errInvalidCommissionRate, "rate:%s", v.MaxChangeRate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.MaxChangeRate = numeric.MustNewDecFromStr("0.05") + v.Commission.MaxRate = numeric.MustNewDecFromStr("0.41") + e = errors.Wrapf( + errCommissionRateTooLarge, "rate:%s", v.MaxRate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.MaxRate = numeric.MustNewDecFromStr("0.51") + v.Commission.MaxChangeRate = numeric.MustNewDecFromStr("0.95") + e = errors.Wrapf( + errCommissionRateTooLarge, "rate:%s", v.MaxChangeRate.String(), + ) + if err := v.SanityCheck(); err.Error() != e.Error() { + t.Error("expected", e, "got", err) + } + v.Commission.MaxChangeRate = numeric.MustNewDecFromStr("0.05") + v.SlotPubKeys = append(v.SlotPubKeys, v.SlotPubKeys[0]) + if err := v.SanityCheck(); err != errDuplicateSlotKeys { + t.Error("expected", errDuplicateSlotKeys, "got", err) + } +} + +func TestValidatorWrapperSanityCheck(t *testing.T) { + // no delegation must fail + wrapper := createNewValidatorWrapper(createNewValidator()) + if err := wrapper.SanityCheck(); err == nil { + t.Error("expected", errInvalidSelfDelegation, "got", err) + } + + // valid self delegation must not fail + valDel := NewDelegation(validatorAddr, big.NewInt(1e18)) + wrapper.Delegations = []Delegation{valDel} + if err := wrapper.SanityCheck(); err != nil { + t.Errorf("validator wrapper SanityCheck failed: %s", err) + } + + // invalid self delegation must fail + valDel = NewDelegation(validatorAddr, big.NewInt(1e17)) + wrapper.Delegations = []Delegation{valDel} + if err := wrapper.SanityCheck(); err == nil { + t.Error("expected", errInvalidSelfDelegation, "got", err) + } +} + +func testEnsureLength(t *testing.T) { + // test name length > MaxNameLength + if _, err := longNameDesc.EnsureLength(); err == nil { + t.Error("expected", ctxerror.New("[EnsureLength] Exceed Maximum Length", "have", len(longNameDesc.Name), "maxNameLen", MaxNameLength), "got", err) + } + // test identity length > MaxIdentityLength + if _, err := longIdentityDesc.EnsureLength(); err == nil { + t.Error("expected", ctxerror.New("[EnsureLength] Exceed Maximum Length", "have", len(longIdentityDesc.Identity), "maxIdentityLen", MaxIdentityLength), "got", err) + } + // test website length > MaxWebsiteLength + if _, err := longWebsiteDesc.EnsureLength(); err == nil { + t.Error("expected", ctxerror.New("[EnsureLength] Exceed Maximum Length", "have", len(longWebsiteDesc.Website), "maxWebsiteLen", MaxWebsiteLength), "got", err) + } + // test security contact length > MaxSecurityContactLength + if _, err := longSecurityContactDesc.EnsureLength(); err == nil { + t.Error("expected", ctxerror.New("[EnsureLength] Exceed Maximum Length", "have", len(longSecurityContactDesc.SecurityContact), "maxSecurityContactLen", MaxSecurityContactLength), "got", err) + } + // test details length > MaxDetailsLength + if _, err := longDetailsDesc.EnsureLength(); err == nil { + t.Error("expected", ctxerror.New("[EnsureLength] Exceed Maximum Length", "have", len(longDetailsDesc.Details), "maxDetailsLen", MaxDetailsLength), "got", err) + } +} + +func TestEnsureLength(t *testing.T) { + _, err := validator.Description.EnsureLength() + if err != nil { + t.Error("expected", "nil", "got", err) + } + testEnsureLength(t) +} + +func TestUpdateDescription(t *testing.T) { + // create two description + d1 := Description{ + Name: "Wayne", + Identity: "wen", + Website: "harmony.one.wen", + SecurityContact: "wenSecurity", + Details: "wenDetails", + } + d2 := Description{ + Name: "John", + Identity: "jw", + Website: "harmony.one.john", + SecurityContact: "johnSecurity", + Details: "johnDetails", + } + d1, _ = UpdateDescription(d1, d2) + + // check whether update description function work? + if compareTwoDescription(d1, d2) { + t.Errorf("UpdateDescription failed") + } +} + +// compare two descriptions' items +func compareTwoDescription(d1, d2 Description) bool { + return (strings.Compare(d1.Name, d2.Name) != 0 && + strings.Compare(d1.Identity, d2.Identity) != 0 && + strings.Compare(d1.Website, d2.Website) != 0 && + strings.Compare(d1.SecurityContact, d2.SecurityContact) != 0 && + strings.Compare(d1.Details, d2.Details) != 0) +} + +// test get validator's address +func TestGetAddress(t *testing.T) { + if validator.GetAddress() != validator.Address { + t.Errorf("validator GetAddress failed") + } +} + +// test get validator's name +func TestGetName(t *testing.T) { + if strings.Compare(validator.GetName(), validator.Name) != 0 { + t.Errorf("validator GetName failed") + } +} + +// test get validator's commission Rate +func TestGetCommissionRate(t *testing.T) { + if validator.GetCommissionRate() != validator.Commission.Rate { + t.Errorf("validator GetCommissionRate failed") + } +} + +// test get validator's min self delegation +func TestGetMinSelfDelegation(t *testing.T) { + if validator.GetMinSelfDelegation().Cmp(validator.MinSelfDelegation) != 0 { + t.Errorf("validator GetMinSelfDelegation failed") + } +} + +func TestVerifyBLSKeys(t *testing.T) { + // test verify bls for valid single key/sig pair + val := CreateValidator{ + ValidatorAddress: validatorAddr, + Description: desc, + SlotPubKeys: slotPubKeys, + SlotKeySigs: slotKeySigs, + Amount: big.NewInt(1e18), + } + err := VerifyBLSKeys(val.SlotPubKeys, val.SlotKeySigs) + if err != nil { + t.Errorf("VerifyBLSKeys failed") + } + + // test verify bls for not matching single key/sig pair + + // test verify bls for not length matching multiple key/sig pairs + + // test verify bls for not order matching multiple key/sig pairs + + // test verify bls for empty key/sig pairs +} + +func TestCreateValidatorFromNewMsg(t *testing.T) { + v := CreateValidator{ + ValidatorAddress: validatorAddr, + Description: desc, + Amount: big.NewInt(1e18), + } + blockNum := big.NewInt(1000) + _, err := CreateValidatorFromNewMsg(&v, blockNum) + if err != nil { + t.Errorf("CreateValidatorFromNewMsg failed") + } +} + +func TestUpdateValidatorFromEditMsg(t *testing.T) { + ev := EditValidator{ + ValidatorAddress: validatorAddr, + Description: desc, + MinSelfDelegation: big.NewInt(2e18), + MaxTotalDelegation: big.NewInt(6e18), + } + UpdateValidatorFromEditMsg(&validator, &ev) + + if validator.MinSelfDelegation.Cmp(big.NewInt(2e18)) != 0 { + t.Errorf("UpdateValidatorFromEditMsg failed") + } +} + +func TestString(t *testing.T) { + // print out the string + fmt.Println(validator.String()) +}