[R4R] BEP3 simulations (#423)

* implement randomized genesis, params

* implement operations: MsgCreateAtomicSwap

* implement claim, refund future ops

* remove dynamic block locks

* refactor BondedAddresses

* add consistent supported assets
This commit is contained in:
Denali Marsh 2020-04-11 20:54:45 -07:00 committed by GitHub
parent 1bdf580a61
commit 3da4657102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 354 additions and 10 deletions

View File

@ -34,6 +34,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations"
"github.com/cosmos/cosmos-sdk/x/supply" "github.com/cosmos/cosmos-sdk/x/supply"
bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations"
) )
// Simulation parameter constants // Simulation parameter constants
@ -56,6 +57,7 @@ const (
OpWeightMsgUndelegate = "op_weight_msg_undelegate" OpWeightMsgUndelegate = "op_weight_msg_undelegate"
OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate"
OpWeightMsgUnjail = "op_weight_msg_unjail" OpWeightMsgUnjail = "op_weight_msg_unjail"
OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap"
) )
// TestMain runs setup and teardown code before all tests. // TestMain runs setup and teardown code before all tests.
@ -264,6 +266,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper
}(nil), }(nil),
slashingsimops.SimulateMsgUnjail(app.slashingKeeper), slashingsimops.SimulateMsgUnjail(app.slashingKeeper),
}, },
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgCreateAtomicSwap, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
bep3simops.SimulateMsgCreateAtomicSwap(app.accountKeeper, app.bep3Keeper),
},
} }
} }

View File

@ -2,21 +2,167 @@ package simulation
import ( import (
"fmt" "fmt"
"math/rand"
"strings"
"time"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/kava-labs/kava/x/bep3/types" "github.com/kava-labs/kava/x/bep3/types"
) )
// Simulation parameter constants
const (
BnbDeputyAddress = "bnb_deputy_address"
MinBlockLock = "min_block_lock"
MaxBlockLock = "max_block_lock"
SupportedAssets = "supported_assets"
)
var (
MaxSupplyLimit = sdk.NewInt(10000000000000000)
Accs []simulation.Account
ConsistentDenoms = [3]string{"bnb", "xrp", "btc"}
)
// GenBnbDeputyAddress randomized BnbDeputyAddress
func GenBnbDeputyAddress(r *rand.Rand) sdk.AccAddress {
return simulation.RandomAcc(r, Accs).Address
}
// GenMinBlockLock randomized MinBlockLock
func GenMinBlockLock(r *rand.Rand) int64 {
min := int(types.AbsoluteMinimumBlockLock)
max := int(types.AbsoluteMaximumBlockLock)
return int64(r.Intn(max-min) + min)
}
// GenMaxBlockLock randomized MaxBlockLock
func GenMaxBlockLock(r *rand.Rand, minBlockLock int64) int64 {
min := int(minBlockLock)
max := int(types.AbsoluteMaximumBlockLock)
return int64(r.Intn(max-min) + min)
}
// GenSupportedAssets gets randomized SupportedAssets
func GenSupportedAssets(r *rand.Rand) types.AssetParams {
var assets types.AssetParams
for i := 0; i < (r.Intn(10) + 1); i++ {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3)))
asset := genSupportedAsset(r, denom)
assets = append(assets, asset)
}
// Add bnb, btc, or xrp as a supported asset for interactions with other modules
stableAsset := genSupportedAsset(r, ConsistentDenoms[r.Intn(3)])
assets = append(assets, stableAsset)
return assets
}
func genSupportedAsset(r *rand.Rand, denom string) types.AssetParam {
coinID, _ := simulation.RandPositiveInt(r, sdk.NewInt(100000))
limit, _ := simulation.RandPositiveInt(r, MaxSupplyLimit)
return types.AssetParam{
Denom: denom,
CoinID: int(coinID.Int64()),
Limit: limit,
Active: true,
}
}
// RandomizedGenState generates a random GenesisState // RandomizedGenState generates a random GenesisState
func RandomizedGenState(simState *module.SimulationState) { func RandomizedGenState(simState *module.SimulationState) {
Accs = simState.Accounts
// TODO implement this fully bep3Genesis := loadRandomBep3GenState(simState)
// - randomly generating the genesis params fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, bep3Genesis))
// - overwriting with genesis provided to simulation simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(bep3Genesis)
genesisState := types.DefaultGenesisState()
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesisState)) authGenesis, totalCoins := loadAuthGenState(simState, bep3Genesis)
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState) simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis)
// Update supply to match amount of coins in auth
var supplyGenesis supply.GenesisState
simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis)
for _, deputyCoin := range totalCoins {
supplyGenesis.Supply = supplyGenesis.Supply.Add(deputyCoin)
}
simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis)
}
func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState {
bnbDeputyAddress := simulation.RandomAcc(simState.Rand, simState.Accounts).Address
// min/max block lock are hardcoded to 50/100 for expected -NumBlocks=100
minBlockLock := int64(types.AbsoluteMinimumBlockLock)
maxBlockLock := minBlockLock * 2
var supportedAssets types.AssetParams
simState.AppParams.GetOrGenerate(
simState.Cdc, SupportedAssets, &supportedAssets, simState.Rand,
func(r *rand.Rand) { supportedAssets = GenSupportedAssets(r) },
)
bep3Genesis := types.GenesisState{
Params: types.Params{
BnbDeputyAddress: bnbDeputyAddress,
MinBlockLock: minBlockLock,
MaxBlockLock: maxBlockLock,
SupportedAssets: supportedAssets,
},
}
return bep3Genesis
}
func loadAuthGenState(simState *module.SimulationState, bep3Genesis types.GenesisState) (
auth.GenesisState, []sdk.Coins) {
var authGenesis auth.GenesisState
simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis)
deputy, found := getAccount(authGenesis.Accounts, bep3Genesis.Params.BnbDeputyAddress)
if !found {
panic("deputy address not found in available accounts")
}
// Load total limit of each supported asset to deputy's account
var totalCoins []sdk.Coins
for _, asset := range bep3Genesis.Params.SupportedAssets {
assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.Limit))
if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin)); err != nil {
panic(err)
}
totalCoins = append(totalCoins, assetCoin)
}
authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, deputy)
return authGenesis, totalCoins
}
// Return an account from a list of accounts that matches an address.
func getAccount(accounts []authexported.GenesisAccount, addr sdk.AccAddress) (authexported.GenesisAccount, bool) {
for _, a := range accounts {
if a.GetAddress().Equals(addr) {
return a, true
}
}
return nil, false
}
// In a list of accounts, replace the first account found with the same address. If not found, append the account.
func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount {
newAccounts := accounts
for i, a := range accounts {
if a.GetAddress().Equals(acc.GetAddress()) {
newAccounts[i] = acc
return newAccounts
}
}
return append(newAccounts, acc)
} }

View File

@ -0,0 +1,153 @@
package operations
import (
"fmt"
"math"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/x/bep3"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
)
var (
noOpMsg = simulation.NoOpMsg(bep3.ModuleName)
)
// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values
func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulation.Operation {
handler := bep3.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
simulation.OperationMsg, []simulation.FutureOperation, error) {
sender := k.GetBnbDeputyAddress(ctx)
recipient := simulation.RandomAcc(r, accs).Address
recipientOtherChain := simulation.RandStringOfLength(r, 43)
senderOtherChain := simulation.RandStringOfLength(r, 43)
// Generate cryptographically strong pseudo-random number
randomNumber, err := types.GenerateSecureRandomNumber()
if err != nil {
return noOpMsg, nil, err
}
// Must use current blocktime instead of 'now' since initial blocktime was randomly generated
timestamp := ctx.BlockTime().Unix()
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
// Randomly select an asset from supported assets
assets, found := k.GetAssets(ctx)
if !found {
return noOpMsg, nil, fmt.Errorf("no supported assets found")
}
asset := assets[r.Intn(len(assets))]
// Check that the sender has coins of this type
availableAmount := ak.GetAccount(ctx, sender).GetCoins().AmountOf(asset.Denom)
if !availableAmount.IsPositive() {
return noOpMsg, nil, fmt.Errorf("available amount must be positive")
}
// Get a random amount of the available coins
amount, err := simulation.RandPositiveInt(r, availableAmount)
if err != nil {
return noOpMsg, nil, err
}
// If we don't adjust the conversion factor, we'll be out of funds soon
adjustedAmount := amount.Int64() / int64(math.Pow10(8))
coin := sdk.NewInt64Coin(asset.Denom, adjustedAmount)
coins := sdk.NewCoins(coin)
expectedIncome := coin.String()
// We're assuming that sims are run with -NumBlocks=100
heightSpan := int64(55)
crossChain := true
msg := types.NewMsgCreateAtomicSwap(
sender, recipient, recipientOtherChain, senderOtherChain, randomNumberHash,
timestamp, coins, expectedIncome, heightSpan, crossChain)
if err := msg.ValidateBasic(); err != nil {
return noOpMsg, nil, fmt.Errorf("expected MsgCreateAtomicSwap to pass ValidateBasic: %s", err)
}
// Submit msg
ok := submitMsg(ctx, handler, msg)
// If created, construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation
var futureOp simulation.FutureOperation
if ok {
swapID := types.CalculateSwapID(msg.RandomNumberHash, msg.From, msg.SenderOtherChain)
acc := simulation.RandomAcc(r, accs)
evenOdd := r.Intn(2) + 1
if evenOdd%2 == 0 {
// Claim future operation
executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2)
futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.Bytes(), executionBlock, handler)
} else {
// Refund future operation
executionBlock := ctx.BlockHeight() + msg.HeightSpan
futureOp = loadRefundFutureOp(acc.Address, swapID, executionBlock, handler)
}
}
return simulation.NewOperationMsg(msg, ok, ""), []simulation.FutureOperation{futureOp}, nil
}
}
func loadClaimFutureOp(sender sdk.AccAddress, swapID []byte, randomNumber []byte, height int64, handler sdk.Handler) simulation.FutureOperation {
claimOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
simulation.OperationMsg, []simulation.FutureOperation, error) {
// Build the refund msg and validate basic
claimMsg := types.NewMsgClaimAtomicSwap(sender, swapID, randomNumber)
if err := claimMsg.ValidateBasic(); err != nil {
return noOpMsg, nil, fmt.Errorf("expected MsgClaimAtomicSwap to pass ValidateBasic: %s", err)
}
// Test msg submission at target block height
ok := handler(ctx.WithBlockHeight(height), claimMsg).IsOK()
return simulation.NewOperationMsg(claimMsg, ok, ""), nil, nil
}
return simulation.FutureOperation{
BlockHeight: int(height),
Op: claimOp,
}
}
func loadRefundFutureOp(sender sdk.AccAddress, swapID []byte, height int64, handler sdk.Handler) simulation.FutureOperation {
refundOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
simulation.OperationMsg, []simulation.FutureOperation, error) {
// Build the refund msg and validate basic
refundMsg := types.NewMsgRefundAtomicSwap(sender, swapID)
if err := refundMsg.ValidateBasic(); err != nil {
return noOpMsg, nil, fmt.Errorf("expected MsgRefundAtomicSwap to pass ValidateBasic: %s", err)
}
// Test msg submission at target block height
ok := handler(ctx.WithBlockHeight(height), refundMsg).IsOK()
return simulation.NewOperationMsg(refundMsg, ok, ""), nil, nil
}
return simulation.FutureOperation{
BlockHeight: int(height),
Op: refundOp,
}
}
func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) {
ctx, write := ctx.CacheContext()
ok = handler(ctx, msg).IsOK()
if ok {
write()
}
return ok
}

View File

@ -1,14 +1,46 @@
package simulation package simulation
import ( import (
"fmt"
"math/rand" "math/rand"
"github.com/cosmos/cosmos-sdk/x/simulation" "github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/x/bep3/types"
)
const (
keyBnbDeputyAddress = "BnbDeputyAddress"
keyMinBlockLock = "MinBlockLock"
keyMaxBlockLock = "MaxBlockLock"
keySupportedAssets = "SupportedAssets"
) )
// ParamChanges defines the parameters that can be modified by param change proposals // ParamChanges defines the parameters that can be modified by param change proposals
// on the simulation
func ParamChanges(r *rand.Rand) []simulation.ParamChange { func ParamChanges(r *rand.Rand) []simulation.ParamChange {
// TODO implement this // We generate MinBlockLock first because the result is required by GenMaxBlockLock()
return []simulation.ParamChange{} minBlockLockVal := GenMinBlockLock(r)
return []simulation.ParamChange{
simulation.NewSimParamChange(types.ModuleName, keyBnbDeputyAddress, "",
func(r *rand.Rand) string {
return fmt.Sprintf("\"%s\"", GenBnbDeputyAddress(r))
},
),
simulation.NewSimParamChange(types.ModuleName, keyMinBlockLock, "",
func(r *rand.Rand) string {
return fmt.Sprintf("\"%d\"", minBlockLockVal)
},
),
simulation.NewSimParamChange(types.ModuleName, keyMaxBlockLock, "",
func(r *rand.Rand) string {
return fmt.Sprintf("\"%d\"", GenMaxBlockLock(r, minBlockLockVal))
},
),
simulation.NewSimParamChange(types.ModuleName, keySupportedAssets, "",
func(r *rand.Rand) string {
return fmt.Sprintf("\"%v\"", GenSupportedAssets(r))
},
),
}
} }