2020-04-23 16:35:58 +00:00
package simulation
import (
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
appparams "github.com/kava-labs/kava/app/params"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
)
var (
2020-08-17 15:06:59 +00:00
noOpMsg = simulation . NoOpMsg ( types . ModuleName )
2020-06-05 18:24:08 +00:00
randomNumber = [ ] byte { 114 , 21 , 74 , 180 , 81 , 92 , 21 , 91 , 173 , 164 , 143 , 111 , 120 , 58 , 241 , 58 , 40 , 22 , 59 , 133 , 102 , 233 , 55 , 149 , 12 , 199 , 231 , 63 , 122 , 23 , 88 , 9 }
2020-04-23 16:35:58 +00:00
)
// Simulation operation weights constants
const (
OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_swap"
)
// WeightedOperations returns all the operations from the module with their respective weights
func WeightedOperations (
2020-05-06 17:56:43 +00:00
appParams simulation . AppParams , cdc * codec . Codec , ak types . AccountKeeper , k keeper . Keeper ,
2020-04-23 16:35:58 +00:00
) simulation . WeightedOperations {
var weightCreateAtomicSwap int
appParams . GetOrGenerate ( cdc , OpWeightMsgCreateAtomicSwap , & weightCreateAtomicSwap , nil ,
func ( _ * rand . Rand ) {
weightCreateAtomicSwap = appparams . DefaultWeightMsgCreateAtomicSwap
} ,
)
return simulation . WeightedOperations {
simulation . NewWeightedOperation (
weightCreateAtomicSwap ,
SimulateMsgCreateAtomicSwap ( ak , k ) ,
) ,
}
}
// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values
2020-05-06 17:56:43 +00:00
func SimulateMsgCreateAtomicSwap ( ak types . AccountKeeper , k keeper . Keeper ) simulation . Operation {
2020-04-23 16:35:58 +00:00
return func (
r * rand . Rand , app * baseapp . BaseApp , ctx sdk . Context , accs [ ] simulation . Account , chainID string ,
) ( simulation . OperationMsg , [ ] simulation . FutureOperation , error ) {
2020-08-17 15:06:59 +00:00
// Get asset supplies and shuffle them
assets , found := k . GetAssets ( ctx )
if ! found {
2020-04-23 16:35:58 +00:00
return noOpMsg , nil , nil
}
2020-08-17 15:06:59 +00:00
r . Shuffle ( len ( assets ) , func ( i , j int ) {
assets [ i ] , assets [ j ] = assets [ j ] , assets [ i ]
2020-05-06 19:30:27 +00:00
} )
2020-08-17 15:06:59 +00:00
senderOutgoing , selectedAsset , found := findValidAccountAssetPair ( accs , assets , func ( simAcc simulation . Account , asset types . AssetParam ) bool {
supply , found := k . GetAssetSupply ( ctx , asset . Denom )
if ! found {
return false
}
if supply . CurrentSupply . Amount . IsPositive ( ) {
authAcc := ak . GetAccount ( ctx , simAcc . Address )
// deputy cannot be sender of outgoing swap
if authAcc . GetAddress ( ) . Equals ( asset . DeputyAddress ) {
return false
}
// Search for an account that holds coins received by an atomic swap
minAmountPlusFee := asset . MinSwapAmount . Add ( asset . FixedFee )
2020-06-04 23:03:14 +00:00
if authAcc . SpendableCoins ( ctx . BlockTime ( ) ) . AmountOf ( asset . Denom ) . GT ( minAmountPlusFee ) {
2020-05-06 19:30:27 +00:00
return true
}
}
return false
} )
var sender simulation . Account
var recipient simulation . Account
2020-08-17 15:06:59 +00:00
var asset types . AssetParam
2020-05-06 19:30:27 +00:00
// If an outgoing swap can be created, it's chosen 50% of the time.
if found && r . Intn ( 100 ) < 50 {
2020-08-17 15:06:59 +00:00
deputy , found := simulation . FindAccount ( accs , selectedAsset . DeputyAddress )
if ! found {
return noOpMsg , nil , nil
}
sender = senderOutgoing
recipient = deputy
asset = selectedAsset
2020-05-06 19:30:27 +00:00
} else {
2020-08-17 15:06:59 +00:00
// if an outgoing swap cannot be created or was not selected, simulate an incoming swap
assets , _ := k . GetAssets ( ctx )
asset = assets [ r . Intn ( len ( assets ) ) ]
var eligibleAccs [ ] simulation . Account
for _ , simAcc := range accs {
// don't allow recipient of incoming swap to be the deputy
if simAcc . Address . Equals ( asset . DeputyAddress ) {
continue
}
eligibleAccs = append ( eligibleAccs , simAcc )
}
recipient , _ = simulation . RandomAcc ( r , eligibleAccs )
deputy , found := simulation . FindAccount ( accs , asset . DeputyAddress )
if ! found {
return noOpMsg , nil , nil
2020-05-06 19:30:27 +00:00
}
2020-08-17 15:06:59 +00:00
sender = deputy
2020-05-06 19:30:27 +00:00
}
2020-04-23 16:35:58 +00:00
recipientOtherChain := simulation . RandStringOfLength ( r , 43 )
senderOtherChain := simulation . RandStringOfLength ( r , 43 )
2020-06-05 18:24:08 +00:00
// Use same random number for determinism
2020-04-23 16:35:58 +00:00
timestamp := ctx . BlockTime ( ) . Unix ( )
2020-06-05 01:27:54 +00:00
randomNumberHash := types . CalculateRandomHash ( randomNumber , timestamp )
2020-04-23 16:35:58 +00:00
2020-05-06 19:30:27 +00:00
// Check that the sender has coins for fee
senderAcc := ak . GetAccount ( ctx , sender . Address )
2020-04-23 16:35:58 +00:00
fees , err := simulation . RandomFees ( r , ctx , senderAcc . SpendableCoins ( ctx . BlockTime ( ) ) )
if err != nil {
return simulation . NoOpMsg ( types . ModuleName ) , nil , err
}
2020-05-06 19:30:27 +00:00
2020-05-07 00:29:59 +00:00
// Get maximum valid amount
2020-08-17 15:06:59 +00:00
maximumAmount := senderAcc . SpendableCoins ( ctx . BlockTime ( ) ) . Sub ( fees ) . AmountOf ( asset . Denom )
2020-08-27 02:05:27 +00:00
assetSupply , foundAssetSupply := k . GetAssetSupply ( ctx , asset . Denom )
if ! foundAssetSupply {
return noOpMsg , nil , fmt . Errorf ( "no asset supply found for %s" , asset . Denom )
}
2020-05-07 00:29:59 +00:00
// The maximum amount for outgoing swaps is limited by the asset's current supply
2020-08-17 15:06:59 +00:00
if recipient . Address . Equals ( asset . DeputyAddress ) {
if maximumAmount . GT ( assetSupply . CurrentSupply . Amount . Sub ( assetSupply . OutgoingSupply . Amount ) ) {
maximumAmount = assetSupply . CurrentSupply . Amount . Sub ( assetSupply . OutgoingSupply . Amount )
2020-05-07 00:29:59 +00:00
}
2020-08-27 02:05:27 +00:00
} else {
// the maximum amount for incoming swaps in limited by the asset's incoming supply + current supply (rate-limited if applicable) + swap amount being less than the supply limit
2023-04-05 23:21:59 +00:00
var currentRemainingSupply sdkmath . Int
2020-08-27 02:05:27 +00:00
if asset . SupplyLimit . TimeLimited {
currentRemainingSupply = asset . SupplyLimit . Limit . Sub ( assetSupply . IncomingSupply . Amount ) . Sub ( assetSupply . TimeLimitedCurrentSupply . Amount )
} else {
currentRemainingSupply = asset . SupplyLimit . Limit . Sub ( assetSupply . IncomingSupply . Amount ) . Sub ( assetSupply . CurrentSupply . Amount )
}
if currentRemainingSupply . LT ( maximumAmount ) {
maximumAmount = currentRemainingSupply
}
2020-05-07 00:29:59 +00:00
}
2020-06-04 23:03:14 +00:00
// The maximum amount for all swaps is limited by the total max limit
2020-08-17 15:06:59 +00:00
if maximumAmount . GT ( asset . MaxSwapAmount ) {
maximumAmount = asset . MaxSwapAmount
2020-06-04 23:03:14 +00:00
}
2020-04-23 16:35:58 +00:00
// Get an amount of coins between 0.1 and 2% of total coins
2023-04-05 23:21:59 +00:00
amount := maximumAmount . Quo ( sdkmath . NewInt ( int64 ( simulation . RandIntBetween ( r , 50 , 1000 ) ) ) )
2020-08-17 15:06:59 +00:00
minAmountPlusFee := asset . MinSwapAmount . Add ( asset . FixedFee )
2021-07-15 15:46:56 +00:00
if amount . LTE ( minAmountPlusFee ) {
2020-08-27 02:05:27 +00:00
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (account funds exhausted for asset %s)" , asset . Denom ) , "" , false , nil ) , nil , nil
2020-04-23 16:35:58 +00:00
}
2020-08-17 15:06:59 +00:00
coins := sdk . NewCoins ( sdk . NewCoin ( asset . Denom , amount ) )
2020-04-23 16:35:58 +00:00
// We're assuming that sims are run with -NumBlocks=100
2020-08-17 15:06:59 +00:00
heightSpan := uint64 ( simulation . RandIntBetween ( r , int ( asset . MinBlockLock ) , int ( asset . MaxBlockLock ) ) )
2020-04-23 16:35:58 +00:00
msg := types . NewMsgCreateAtomicSwap (
2020-05-06 19:30:27 +00:00
sender . Address , recipient . Address , recipientOtherChain , senderOtherChain ,
2020-05-12 20:15:38 +00:00
randomNumberHash , timestamp , coins , heightSpan ,
2020-04-23 16:35:58 +00:00
)
tx := helpers . GenTx (
[ ] sdk . Msg { msg } ,
fees ,
helpers . DefaultGenTxGas ,
chainID ,
[ ] uint64 { senderAcc . GetAccountNumber ( ) } ,
[ ] uint64 { senderAcc . GetSequence ( ) } ,
sender . PrivKey ,
)
_ , result , err := app . Deliver ( tx )
if err != nil {
2020-09-01 15:33:12 +00:00
return simulation . NewOperationMsg ( msg , false , fmt . Sprintf ( "%+v" , err ) ) , nil , err
2020-04-23 16:35:58 +00:00
}
2020-05-06 19:30:27 +00:00
// Construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation
2020-04-23 16:35:58 +00:00
var futureOp simulation . FutureOperation
swapID := types . CalculateSwapID ( msg . RandomNumberHash , msg . From , msg . SenderOtherChain )
if r . Intn ( 100 ) < 50 {
2020-08-17 15:06:59 +00:00
// Claim future operation - choose between next block and the block before height span
executionBlock := uint64 (
int ( ctx . BlockHeight ( ) + 1 ) + r . Intn ( int ( heightSpan - 1 ) ) )
2020-04-23 16:35:58 +00:00
futureOp = simulation . FutureOperation {
BlockHeight : int ( executionBlock ) ,
2020-06-05 01:27:54 +00:00
Op : operationClaimAtomicSwap ( ak , k , swapID , randomNumber ) ,
2020-04-23 16:35:58 +00:00
}
} else {
// Refund future operation
2020-05-12 20:15:38 +00:00
executionBlock := uint64 ( ctx . BlockHeight ( ) ) + msg . HeightSpan
2020-04-23 16:35:58 +00:00
futureOp = simulation . FutureOperation {
BlockHeight : int ( executionBlock ) ,
Op : operationRefundAtomicSwap ( ak , k , swapID ) ,
}
}
return simulation . NewOperationMsg ( msg , true , result . Log ) , [ ] simulation . FutureOperation { futureOp } , nil
}
}
2020-05-06 17:56:43 +00:00
func operationClaimAtomicSwap ( ak types . AccountKeeper , k keeper . Keeper , swapID [ ] byte , randomNumber [ ] byte ) simulation . Operation {
2020-04-23 16:35:58 +00:00
return func (
r * rand . Rand , app * baseapp . BaseApp , ctx sdk . Context , accs [ ] simulation . Account , chainID string ,
) ( simulation . OperationMsg , [ ] simulation . FutureOperation , error ) {
simAccount , _ := simulation . RandomAcc ( r , accs )
acc := ak . GetAccount ( ctx , simAccount . Address )
2020-08-17 15:06:59 +00:00
swap , found := k . GetAtomicSwap ( ctx , swapID )
if ! found {
return simulation . NoOpMsg ( types . ModuleName ) , nil , fmt . Errorf ( "cannot claim: swap with ID %s not found" , swapID )
}
// check that asset supply supports claiming (it could have changed due to a param change proposal)
// use CacheContext so changes don't take effect
cacheCtx , _ := ctx . CacheContext ( )
switch swap . Direction {
case types . Incoming :
err := k . DecrementIncomingAssetSupply ( cacheCtx , swap . Amount [ 0 ] )
if err != nil {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not claim - unable to decrement incoming asset supply %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
err = k . IncrementCurrentAssetSupply ( cacheCtx , swap . Amount [ 0 ] )
if err != nil {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not claim - unable to increment current asset supply %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
case types . Outgoing :
err := k . DecrementOutgoingAssetSupply ( cacheCtx , swap . Amount [ 0 ] )
if err != nil {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not claim - unable to decrement outgoing asset supply %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
err = k . DecrementCurrentAssetSupply ( cacheCtx , swap . Amount [ 0 ] )
if err != nil {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not claim - unable to decrement current asset supply %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
}
2020-04-23 16:35:58 +00:00
2020-08-17 15:06:59 +00:00
asset , err := k . GetAsset ( ctx , swap . Amount [ 0 ] . Denom )
if err != nil {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not claim - asset not found %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
supply , found := k . GetAssetSupply ( ctx , asset . Denom )
if ! found {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not claim - asset supply not found %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
2020-08-27 02:05:27 +00:00
if asset . SupplyLimit . Limit . LT ( supply . CurrentSupply . Amount . Add ( swap . Amount [ 0 ] . Amount ) ) {
2020-08-17 15:06:59 +00:00
return simulation . NoOpMsg ( types . ModuleName ) , nil , nil
}
msg := types . NewMsgClaimAtomicSwap ( acc . GetAddress ( ) , swapID , randomNumber )
2020-04-23 16:35:58 +00:00
fees , err := simulation . RandomFees ( r , ctx , acc . SpendableCoins ( ctx . BlockTime ( ) ) )
if err != nil {
return simulation . NoOpMsg ( types . ModuleName ) , nil , err
}
tx := helpers . GenTx (
[ ] sdk . Msg { msg } ,
fees ,
helpers . DefaultGenTxGas ,
chainID ,
[ ] uint64 { acc . GetAccountNumber ( ) } ,
[ ] uint64 { acc . GetSequence ( ) } ,
simAccount . PrivKey ,
)
_ , result , err := app . Deliver ( tx )
if err != nil {
2020-09-01 15:33:12 +00:00
return simulation . NewOperationMsg ( msg , false , fmt . Sprintf ( "%+v" , err ) ) , nil , err
2020-04-23 16:35:58 +00:00
}
return simulation . NewOperationMsg ( msg , true , result . Log ) , nil , nil
}
}
2020-05-06 17:56:43 +00:00
func operationRefundAtomicSwap ( ak types . AccountKeeper , k keeper . Keeper , swapID [ ] byte ) simulation . Operation {
2020-04-23 16:35:58 +00:00
return func (
r * rand . Rand , app * baseapp . BaseApp , ctx sdk . Context , accs [ ] simulation . Account , chainID string ,
) ( simulation . OperationMsg , [ ] simulation . FutureOperation , error ) {
simAccount , _ := simulation . RandomAcc ( r , accs )
acc := ak . GetAccount ( ctx , simAccount . Address )
2020-08-17 15:06:59 +00:00
swap , found := k . GetAtomicSwap ( ctx , swapID )
if ! found {
return simulation . NoOpMsg ( types . ModuleName ) , nil , fmt . Errorf ( "cannot refund: swap with ID %s not found" , swapID )
}
cacheCtx , _ := ctx . CacheContext ( )
switch swap . Direction {
case types . Incoming :
if err := k . DecrementIncomingAssetSupply ( cacheCtx , swap . Amount [ 0 ] ) ; err != nil {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not refund - unable to decrement incoming asset supply %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
case types . Outgoing :
if err := k . DecrementOutgoingAssetSupply ( cacheCtx , swap . Amount [ 0 ] ) ; err != nil {
return simulation . NewOperationMsgBasic ( types . ModuleName , fmt . Sprintf ( "no-operation (could not refund - unable to decrement outgoing asset supply %s)" , swap . Amount [ 0 ] . Denom ) , "" , false , nil ) , nil , nil
}
}
2020-04-23 16:35:58 +00:00
msg := types . NewMsgRefundAtomicSwap ( acc . GetAddress ( ) , swapID )
fees , err := simulation . RandomFees ( r , ctx , acc . SpendableCoins ( ctx . BlockTime ( ) ) )
if err != nil {
return simulation . NoOpMsg ( types . ModuleName ) , nil , err
}
tx := helpers . GenTx (
[ ] sdk . Msg { msg } ,
fees ,
helpers . DefaultGenTxGas ,
chainID ,
[ ] uint64 { acc . GetAccountNumber ( ) } ,
[ ] uint64 { acc . GetSequence ( ) } ,
simAccount . PrivKey ,
)
_ , result , err := app . Deliver ( tx )
if err != nil {
2020-09-01 15:33:12 +00:00
return simulation . NewOperationMsg ( msg , false , fmt . Sprintf ( "%+v" , err ) ) , nil , err
2020-04-23 16:35:58 +00:00
}
return simulation . NewOperationMsg ( msg , true , result . Log ) , nil , nil
}
}
2020-05-06 19:30:27 +00:00
// findValidAccountAssetSupplyPair finds an account for which the callback func returns true
2020-08-17 15:06:59 +00:00
func findValidAccountAssetPair ( accounts [ ] simulation . Account , assets types . AssetParams ,
2022-05-09 18:37:36 +00:00
cb func ( simulation . Account , types . AssetParam ) bool ,
) ( simulation . Account , types . AssetParam , bool ) {
2020-08-17 15:06:59 +00:00
for _ , asset := range assets {
2020-05-06 19:30:27 +00:00
for _ , acc := range accounts {
2020-08-17 15:06:59 +00:00
if isValid := cb ( acc , asset ) ; isValid {
return acc , asset , true
2020-05-06 19:30:27 +00:00
}
}
}
2020-08-17 15:06:59 +00:00
return simulation . Account { } , types . AssetParam { } , false
2020-05-06 19:30:27 +00:00
}