mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
add modules
This commit is contained in:
parent
d9f60f1dde
commit
30084913dd
@ -32,6 +32,9 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||||
"github.com/cosmos/cosmos-sdk/x/supply"
|
"github.com/cosmos/cosmos-sdk/x/supply"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee"
|
||||||
|
"github.com/0glabs/0g-chain/x/pricefeed"
|
||||||
validatorvesting "github.com/0glabs/0g-chain/x/validator-vesting"
|
validatorvesting "github.com/0glabs/0g-chain/x/validator-vesting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -179,6 +182,7 @@ func TestAppImportExport(t *testing.T) {
|
|||||||
{app.keys[kavadist.StoreKey], newApp.keys[kavadist.StoreKey], [][]byte{}},
|
{app.keys[kavadist.StoreKey], newApp.keys[kavadist.StoreKey], [][]byte{}},
|
||||||
{app.keys[pricefeed.StoreKey], newApp.keys[pricefeed.StoreKey], [][]byte{}},
|
{app.keys[pricefeed.StoreKey], newApp.keys[pricefeed.StoreKey], [][]byte{}},
|
||||||
{app.keys[validatorvesting.StoreKey], newApp.keys[validatorvesting.StoreKey], [][]byte{}},
|
{app.keys[validatorvesting.StoreKey], newApp.keys[validatorvesting.StoreKey], [][]byte{}},
|
||||||
|
{app.keys[committee.StoreKey], newApp.keys[committee.StoreKey], [][]byte{}},
|
||||||
{app.keys[council.StoreKey], newApp.keys[council.StoreKey], [][]byte{}},
|
{app.keys[council.StoreKey], newApp.keys[council.StoreKey], [][]byte{}},
|
||||||
{app.keys[swap.StoreKey], newApp.keys[swap.StoreKey], [][]byte{}},
|
{app.keys[swap.StoreKey], newApp.keys[swap.StoreKey], [][]byte{}},
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ import (
|
|||||||
|
|
||||||
"github.com/0glabs/0g-chain/app"
|
"github.com/0glabs/0g-chain/app"
|
||||||
"github.com/0glabs/0g-chain/chaincfg"
|
"github.com/0glabs/0g-chain/chaincfg"
|
||||||
// bep3types "github.com/0glabs/0g-chain/x/bep3/types"
|
bep3types "github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
// pricefeedtypes "github.com/0glabs/0g-chain/x/pricefeed/types"
|
pricefeedtypes "github.com/0glabs/0g-chain/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@ -35,10 +35,10 @@ func TestAppAnteHandler_AuthorizedMempool(t *testing.T) {
|
|||||||
testPrivKeys, testAddresses := app.GeneratePrivKeyAddressPairs(10)
|
testPrivKeys, testAddresses := app.GeneratePrivKeyAddressPairs(10)
|
||||||
unauthed := testAddresses[0:2]
|
unauthed := testAddresses[0:2]
|
||||||
unauthedKeys := testPrivKeys[0:2]
|
unauthedKeys := testPrivKeys[0:2]
|
||||||
// deputy := testAddresses[2]
|
deputy := testAddresses[2]
|
||||||
// deputyKey := testPrivKeys[2]
|
deputyKey := testPrivKeys[2]
|
||||||
// oracles := testAddresses[3:6]
|
oracles := testAddresses[3:6]
|
||||||
// oraclesKeys := testPrivKeys[3:6]
|
oraclesKeys := testPrivKeys[3:6]
|
||||||
manual := testAddresses[6:]
|
manual := testAddresses[6:]
|
||||||
manualKeys := testPrivKeys[6:]
|
manualKeys := testPrivKeys[6:]
|
||||||
|
|
||||||
@ -69,8 +69,8 @@ func TestAppAnteHandler_AuthorizedMempool(t *testing.T) {
|
|||||||
sdk.NewCoins(sdk.NewInt64Coin(chaincfg.DisplayDenom, 1e9)),
|
sdk.NewCoins(sdk.NewInt64Coin(chaincfg.DisplayDenom, 1e9)),
|
||||||
testAddresses,
|
testAddresses,
|
||||||
),
|
),
|
||||||
// newBep3GenStateMulti(tApp.AppCodec(), deputy),
|
newBep3GenStateMulti(tApp.AppCodec(), deputy),
|
||||||
// newPricefeedGenStateMulti(tApp.AppCodec(), oracles),
|
newPricefeedGenStateMulti(tApp.AppCodec(), oracles),
|
||||||
)
|
)
|
||||||
|
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
@ -85,18 +85,18 @@ func TestAppAnteHandler_AuthorizedMempool(t *testing.T) {
|
|||||||
privKey: unauthedKeys[1],
|
privKey: unauthedKeys[1],
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// name: "oracle",
|
name: "oracle",
|
||||||
// address: oracles[1],
|
address: oracles[1],
|
||||||
// privKey: oraclesKeys[1],
|
privKey: oraclesKeys[1],
|
||||||
// expectPass: true,
|
expectPass: true,
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// name: "deputy",
|
name: "deputy",
|
||||||
// address: deputy,
|
address: deputy,
|
||||||
// privKey: deputyKey,
|
privKey: deputyKey,
|
||||||
// expectPass: true,
|
expectPass: true,
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
name: "manual",
|
name: "manual",
|
||||||
address: manual[1],
|
address: manual[1],
|
||||||
@ -144,53 +144,53 @@ func TestAppAnteHandler_AuthorizedMempool(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func newPricefeedGenStateMulti(cdc codec.JSONCodec, oracles []sdk.AccAddress) app.GenesisState {
|
func newPricefeedGenStateMulti(cdc codec.JSONCodec, oracles []sdk.AccAddress) app.GenesisState {
|
||||||
// pfGenesis := pricefeedtypes.GenesisState{
|
pfGenesis := pricefeedtypes.GenesisState{
|
||||||
// Params: pricefeedtypes.Params{
|
Params: pricefeedtypes.Params{
|
||||||
// Markets: []pricefeedtypes.Market{
|
Markets: []pricefeedtypes.Market{
|
||||||
// {MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: oracles, Active: true},
|
{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: oracles, Active: true},
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// }
|
}
|
||||||
// return app.GenesisState{pricefeedtypes.ModuleName: cdc.MustMarshalJSON(&pfGenesis)}
|
return app.GenesisState{pricefeedtypes.ModuleName: cdc.MustMarshalJSON(&pfGenesis)}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// func newBep3GenStateMulti(cdc codec.JSONCodec, deputyAddress sdk.AccAddress) app.GenesisState {
|
func newBep3GenStateMulti(cdc codec.JSONCodec, deputyAddress sdk.AccAddress) app.GenesisState {
|
||||||
// bep3Genesis := bep3types.GenesisState{
|
bep3Genesis := bep3types.GenesisState{
|
||||||
// Params: bep3types.Params{
|
Params: bep3types.Params{
|
||||||
// AssetParams: bep3types.AssetParams{
|
AssetParams: bep3types.AssetParams{
|
||||||
// bep3types.AssetParam{
|
bep3types.AssetParam{
|
||||||
// Denom: "bnb",
|
Denom: "bnb",
|
||||||
// CoinID: 714,
|
CoinID: 714,
|
||||||
// SupplyLimit: bep3types.SupplyLimit{
|
SupplyLimit: bep3types.SupplyLimit{
|
||||||
// Limit: sdkmath.NewInt(350000000000000),
|
Limit: sdkmath.NewInt(350000000000000),
|
||||||
// TimeLimited: false,
|
TimeLimited: false,
|
||||||
// TimeBasedLimit: sdk.ZeroInt(),
|
TimeBasedLimit: sdk.ZeroInt(),
|
||||||
// TimePeriod: time.Hour,
|
TimePeriod: time.Hour,
|
||||||
// },
|
},
|
||||||
// Active: true,
|
Active: true,
|
||||||
// DeputyAddress: deputyAddress,
|
DeputyAddress: deputyAddress,
|
||||||
// FixedFee: sdkmath.NewInt(1000),
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
// MinSwapAmount: sdk.OneInt(),
|
MinSwapAmount: sdk.OneInt(),
|
||||||
// MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
// MinBlockLock: bep3types.DefaultMinBlockLock,
|
MinBlockLock: bep3types.DefaultMinBlockLock,
|
||||||
// MaxBlockLock: bep3types.DefaultMaxBlockLock,
|
MaxBlockLock: bep3types.DefaultMaxBlockLock,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// Supplies: bep3types.AssetSupplies{
|
Supplies: bep3types.AssetSupplies{
|
||||||
// bep3types.NewAssetSupply(
|
bep3types.NewAssetSupply(
|
||||||
// sdk.NewCoin("bnb", sdk.ZeroInt()),
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
// sdk.NewCoin("bnb", sdk.ZeroInt()),
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
// sdk.NewCoin("bnb", sdk.ZeroInt()),
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
// sdk.NewCoin("bnb", sdk.ZeroInt()),
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
// time.Duration(0),
|
time.Duration(0),
|
||||||
// ),
|
),
|
||||||
// },
|
},
|
||||||
// PreviousBlockTime: bep3types.DefaultPreviousBlockTime,
|
PreviousBlockTime: bep3types.DefaultPreviousBlockTime,
|
||||||
// }
|
}
|
||||||
// return app.GenesisState{bep3types.ModuleName: cdc.MustMarshalJSON(&bep3Genesis)}
|
return app.GenesisState{bep3types.ModuleName: cdc.MustMarshalJSON(&bep3Genesis)}
|
||||||
// }
|
}
|
||||||
|
|
||||||
func TestAppAnteHandler_RejectMsgsInAuthz(t *testing.T) {
|
func TestAppAnteHandler_RejectMsgsInAuthz(t *testing.T) {
|
||||||
testPrivKeys, testAddresses := app.GeneratePrivKeyAddressPairs(10)
|
testPrivKeys, testAddresses := app.GeneratePrivKeyAddressPairs(10)
|
||||||
|
86
app/app.go
86
app/app.go
@ -103,17 +103,29 @@ import (
|
|||||||
"github.com/0glabs/0g-chain/app/ante"
|
"github.com/0glabs/0g-chain/app/ante"
|
||||||
chainparams "github.com/0glabs/0g-chain/app/params"
|
chainparams "github.com/0glabs/0g-chain/app/params"
|
||||||
"github.com/0glabs/0g-chain/chaincfg"
|
"github.com/0glabs/0g-chain/chaincfg"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3"
|
||||||
|
bep3keeper "github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
bep3types "github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
evmutil "github.com/0glabs/0g-chain/x/evmutil"
|
evmutil "github.com/0glabs/0g-chain/x/evmutil"
|
||||||
evmutilkeeper "github.com/0glabs/0g-chain/x/evmutil/keeper"
|
evmutilkeeper "github.com/0glabs/0g-chain/x/evmutil/keeper"
|
||||||
evmutiltypes "github.com/0glabs/0g-chain/x/evmutil/types"
|
evmutiltypes "github.com/0glabs/0g-chain/x/evmutil/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee"
|
||||||
|
committeeclient "github.com/0glabs/0g-chain/x/committee/client"
|
||||||
|
committeekeeper "github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
committeetypes "github.com/0glabs/0g-chain/x/committee/types"
|
||||||
council "github.com/0glabs/0g-chain/x/council/v1"
|
council "github.com/0glabs/0g-chain/x/council/v1"
|
||||||
councilkeeper "github.com/0glabs/0g-chain/x/council/v1/keeper"
|
councilkeeper "github.com/0glabs/0g-chain/x/council/v1/keeper"
|
||||||
counciltypes "github.com/0glabs/0g-chain/x/council/v1/types"
|
counciltypes "github.com/0glabs/0g-chain/x/council/v1/types"
|
||||||
das "github.com/0glabs/0g-chain/x/das/v1"
|
das "github.com/0glabs/0g-chain/x/das/v1"
|
||||||
daskeeper "github.com/0glabs/0g-chain/x/das/v1/keeper"
|
daskeeper "github.com/0glabs/0g-chain/x/das/v1/keeper"
|
||||||
dastypes "github.com/0glabs/0g-chain/x/das/v1/types"
|
dastypes "github.com/0glabs/0g-chain/x/das/v1/types"
|
||||||
|
issuance "github.com/0glabs/0g-chain/x/issuance"
|
||||||
|
issuancekeeper "github.com/0glabs/0g-chain/x/issuance/keeper"
|
||||||
|
issuancetypes "github.com/0glabs/0g-chain/x/issuance/types"
|
||||||
|
pricefeed "github.com/0glabs/0g-chain/x/pricefeed"
|
||||||
|
pricefeedkeeper "github.com/0glabs/0g-chain/x/pricefeed/keeper"
|
||||||
|
pricefeedtypes "github.com/0glabs/0g-chain/x/pricefeed/types"
|
||||||
validatorvesting "github.com/0glabs/0g-chain/x/validator-vesting"
|
validatorvesting "github.com/0glabs/0g-chain/x/validator-vesting"
|
||||||
validatorvestingrest "github.com/0glabs/0g-chain/x/validator-vesting/client/rest"
|
validatorvestingrest "github.com/0glabs/0g-chain/x/validator-vesting/client/rest"
|
||||||
validatorvestingtypes "github.com/0glabs/0g-chain/x/validator-vesting/types"
|
validatorvestingtypes "github.com/0glabs/0g-chain/x/validator-vesting/types"
|
||||||
@ -135,6 +147,7 @@ var (
|
|||||||
upgradeclient.LegacyCancelProposalHandler,
|
upgradeclient.LegacyCancelProposalHandler,
|
||||||
ibcclientclient.UpdateClientProposalHandler,
|
ibcclientclient.UpdateClientProposalHandler,
|
||||||
ibcclientclient.UpgradeProposalHandler,
|
ibcclientclient.UpgradeProposalHandler,
|
||||||
|
committeeclient.ProposalHandler,
|
||||||
}),
|
}),
|
||||||
params.AppModuleBasic{},
|
params.AppModuleBasic{},
|
||||||
crisis.AppModuleBasic{},
|
crisis.AppModuleBasic{},
|
||||||
@ -154,6 +167,10 @@ var (
|
|||||||
evmutil.AppModuleBasic{},
|
evmutil.AppModuleBasic{},
|
||||||
mint.AppModuleBasic{},
|
mint.AppModuleBasic{},
|
||||||
consensus.AppModuleBasic{},
|
consensus.AppModuleBasic{},
|
||||||
|
issuance.AppModuleBasic{},
|
||||||
|
bep3.AppModuleBasic{},
|
||||||
|
pricefeed.AppModuleBasic{},
|
||||||
|
committee.AppModuleBasic{},
|
||||||
council.AppModuleBasic{},
|
council.AppModuleBasic{},
|
||||||
das.AppModuleBasic{},
|
das.AppModuleBasic{},
|
||||||
)
|
)
|
||||||
@ -171,6 +188,8 @@ var (
|
|||||||
evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, // used for secure addition and subtraction of balance using module account
|
evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, // used for secure addition and subtraction of balance using module account
|
||||||
evmutiltypes.ModuleName: {authtypes.Minter, authtypes.Burner},
|
evmutiltypes.ModuleName: {authtypes.Minter, authtypes.Burner},
|
||||||
minttypes.ModuleName: {authtypes.Minter},
|
minttypes.ModuleName: {authtypes.Minter},
|
||||||
|
issuancetypes.ModuleAccountName: {authtypes.Minter, authtypes.Burner},
|
||||||
|
bep3types.ModuleName: {authtypes.Burner, authtypes.Minter},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -234,6 +253,10 @@ type App struct {
|
|||||||
consensusParamsKeeper consensusparamkeeper.Keeper
|
consensusParamsKeeper consensusparamkeeper.Keeper
|
||||||
CouncilKeeper councilkeeper.Keeper
|
CouncilKeeper councilkeeper.Keeper
|
||||||
DasKeeper daskeeper.Keeper
|
DasKeeper daskeeper.Keeper
|
||||||
|
issuanceKeeper issuancekeeper.Keeper
|
||||||
|
bep3Keeper bep3keeper.Keeper
|
||||||
|
pricefeedKeeper pricefeedkeeper.Keeper
|
||||||
|
committeeKeeper committeekeeper.Keeper
|
||||||
|
|
||||||
// make scoped keepers public for test purposes
|
// make scoped keepers public for test purposes
|
||||||
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
|
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
|
||||||
@ -291,6 +314,10 @@ func NewApp(
|
|||||||
crisistypes.StoreKey,
|
crisistypes.StoreKey,
|
||||||
counciltypes.StoreKey,
|
counciltypes.StoreKey,
|
||||||
dastypes.StoreKey,
|
dastypes.StoreKey,
|
||||||
|
issuancetypes.StoreKey,
|
||||||
|
bep3types.StoreKey,
|
||||||
|
pricefeedtypes.StoreKey,
|
||||||
|
committeetypes.StoreKey,
|
||||||
)
|
)
|
||||||
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey, evmtypes.TransientKey, feemarkettypes.TransientKey)
|
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey, evmtypes.TransientKey, feemarkettypes.TransientKey)
|
||||||
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
|
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
|
||||||
@ -323,6 +350,9 @@ func NewApp(
|
|||||||
slashingSubspace := app.paramsKeeper.Subspace(slashingtypes.ModuleName)
|
slashingSubspace := app.paramsKeeper.Subspace(slashingtypes.ModuleName)
|
||||||
govSubspace := app.paramsKeeper.Subspace(govtypes.ModuleName).WithKeyTable(govv1.ParamKeyTable())
|
govSubspace := app.paramsKeeper.Subspace(govtypes.ModuleName).WithKeyTable(govv1.ParamKeyTable())
|
||||||
crisisSubspace := app.paramsKeeper.Subspace(crisistypes.ModuleName)
|
crisisSubspace := app.paramsKeeper.Subspace(crisistypes.ModuleName)
|
||||||
|
issuanceSubspace := app.paramsKeeper.Subspace(issuancetypes.ModuleName)
|
||||||
|
bep3Subspace := app.paramsKeeper.Subspace(bep3types.ModuleName)
|
||||||
|
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeedtypes.ModuleName)
|
||||||
ibcSubspace := app.paramsKeeper.Subspace(ibcexported.ModuleName)
|
ibcSubspace := app.paramsKeeper.Subspace(ibcexported.ModuleName)
|
||||||
ibctransferSubspace := app.paramsKeeper.Subspace(ibctransfertypes.ModuleName)
|
ibctransferSubspace := app.paramsKeeper.Subspace(ibctransfertypes.ModuleName)
|
||||||
packetforwardSubspace := app.paramsKeeper.Subspace(packetforwardtypes.ModuleName).WithKeyTable(packetforwardtypes.ParamKeyTable())
|
packetforwardSubspace := app.paramsKeeper.Subspace(packetforwardtypes.ModuleName).WithKeyTable(packetforwardtypes.ParamKeyTable())
|
||||||
@ -491,6 +521,26 @@ func NewApp(
|
|||||||
ibcRouter := ibcporttypes.NewRouter()
|
ibcRouter := ibcporttypes.NewRouter()
|
||||||
ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack)
|
ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack)
|
||||||
app.ibcKeeper.SetRouter(ibcRouter)
|
app.ibcKeeper.SetRouter(ibcRouter)
|
||||||
|
app.issuanceKeeper = issuancekeeper.NewKeeper(
|
||||||
|
appCodec,
|
||||||
|
keys[issuancetypes.StoreKey],
|
||||||
|
issuanceSubspace,
|
||||||
|
app.accountKeeper,
|
||||||
|
app.bankKeeper,
|
||||||
|
)
|
||||||
|
app.bep3Keeper = bep3keeper.NewKeeper(
|
||||||
|
appCodec,
|
||||||
|
keys[bep3types.StoreKey],
|
||||||
|
app.bankKeeper,
|
||||||
|
app.accountKeeper,
|
||||||
|
bep3Subspace,
|
||||||
|
app.ModuleAccountAddrs(),
|
||||||
|
)
|
||||||
|
app.pricefeedKeeper = pricefeedkeeper.NewKeeper(
|
||||||
|
appCodec,
|
||||||
|
keys[pricefeedtypes.StoreKey],
|
||||||
|
pricefeedSubspace,
|
||||||
|
)
|
||||||
|
|
||||||
app.mintKeeper = mintkeeper.NewKeeper(
|
app.mintKeeper = mintkeeper.NewKeeper(
|
||||||
appCodec,
|
appCodec,
|
||||||
@ -501,7 +551,16 @@ func NewApp(
|
|||||||
authtypes.FeeCollectorName,
|
authtypes.FeeCollectorName,
|
||||||
govAuthAddrStr,
|
govAuthAddrStr,
|
||||||
)
|
)
|
||||||
|
// create committee keeper with router
|
||||||
|
committeeGovRouter := govv1beta1.NewRouter()
|
||||||
|
app.committeeKeeper = committeekeeper.NewKeeper(
|
||||||
|
appCodec,
|
||||||
|
keys[committeetypes.StoreKey],
|
||||||
|
committeeGovRouter,
|
||||||
|
app.paramsKeeper,
|
||||||
|
app.accountKeeper,
|
||||||
|
app.bankKeeper,
|
||||||
|
)
|
||||||
// register the staking hooks
|
// register the staking hooks
|
||||||
app.stakingKeeper.SetHooks(
|
app.stakingKeeper.SetHooks(
|
||||||
stakingtypes.NewMultiStakingHooks(
|
stakingtypes.NewMultiStakingHooks(
|
||||||
@ -516,7 +575,8 @@ func NewApp(
|
|||||||
AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler).
|
AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler).
|
||||||
AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)).
|
AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)).
|
||||||
AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(&app.upgradeKeeper)).
|
AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(&app.upgradeKeeper)).
|
||||||
AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.ibcKeeper.ClientKeeper))
|
AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.ibcKeeper.ClientKeeper)).
|
||||||
|
AddRoute(committeetypes.RouterKey, committee.NewProposalHandler(app.committeeKeeper))
|
||||||
|
|
||||||
govConfig := govtypes.DefaultConfig()
|
govConfig := govtypes.DefaultConfig()
|
||||||
govKeeper := govkeeper.NewKeeper(
|
govKeeper := govkeeper.NewKeeper(
|
||||||
@ -560,6 +620,10 @@ func NewApp(
|
|||||||
transferModule,
|
transferModule,
|
||||||
vesting.NewAppModule(app.accountKeeper, app.bankKeeper),
|
vesting.NewAppModule(app.accountKeeper, app.bankKeeper),
|
||||||
authzmodule.NewAppModule(appCodec, app.authzKeeper, app.accountKeeper, app.bankKeeper, app.interfaceRegistry),
|
authzmodule.NewAppModule(appCodec, app.authzKeeper, app.accountKeeper, app.bankKeeper, app.interfaceRegistry),
|
||||||
|
issuance.NewAppModule(app.issuanceKeeper, app.accountKeeper, app.bankKeeper),
|
||||||
|
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.bankKeeper),
|
||||||
|
pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper),
|
||||||
|
committee.NewAppModule(app.committeeKeeper, app.accountKeeper),
|
||||||
validatorvesting.NewAppModule(app.bankKeeper),
|
validatorvesting.NewAppModule(app.bankKeeper),
|
||||||
evmutil.NewAppModule(app.evmutilKeeper, app.bankKeeper, app.accountKeeper),
|
evmutil.NewAppModule(app.evmutilKeeper, app.bankKeeper, app.accountKeeper),
|
||||||
mint.NewAppModule(appCodec, app.mintKeeper, app.accountKeeper, nil, mintSubspace),
|
mint.NewAppModule(appCodec, app.mintKeeper, app.accountKeeper, nil, mintSubspace),
|
||||||
@ -571,6 +635,9 @@ func NewApp(
|
|||||||
upgradetypes.ModuleName,
|
upgradetypes.ModuleName,
|
||||||
// Capability begin blocker runs non state changing initialization.
|
// Capability begin blocker runs non state changing initialization.
|
||||||
capabilitytypes.ModuleName,
|
capabilitytypes.ModuleName,
|
||||||
|
// Committee begin blocker changes module params by enacting proposals.
|
||||||
|
// Run before to ensure params are updated together before state changes.
|
||||||
|
committeetypes.ModuleName,
|
||||||
minttypes.ModuleName,
|
minttypes.ModuleName,
|
||||||
distrtypes.ModuleName,
|
distrtypes.ModuleName,
|
||||||
// During begin block slashing happens after distr.BeginBlocker so that
|
// During begin block slashing happens after distr.BeginBlocker so that
|
||||||
@ -581,6 +648,9 @@ func NewApp(
|
|||||||
stakingtypes.ModuleName,
|
stakingtypes.ModuleName,
|
||||||
feemarkettypes.ModuleName,
|
feemarkettypes.ModuleName,
|
||||||
evmtypes.ModuleName,
|
evmtypes.ModuleName,
|
||||||
|
bep3types.ModuleName,
|
||||||
|
issuancetypes.ModuleName,
|
||||||
|
pricefeedtypes.ModuleName,
|
||||||
ibcexported.ModuleName,
|
ibcexported.ModuleName,
|
||||||
// Add all remaining modules with an empty begin blocker below since cosmos 0.45.0 requires it
|
// Add all remaining modules with an empty begin blocker below since cosmos 0.45.0 requires it
|
||||||
vestingtypes.ModuleName,
|
vestingtypes.ModuleName,
|
||||||
@ -609,10 +679,14 @@ func NewApp(
|
|||||||
evmtypes.ModuleName,
|
evmtypes.ModuleName,
|
||||||
// fee market module must go after evm module in order to retrieve the block gas used.
|
// fee market module must go after evm module in order to retrieve the block gas used.
|
||||||
feemarkettypes.ModuleName,
|
feemarkettypes.ModuleName,
|
||||||
|
pricefeedtypes.ModuleName,
|
||||||
// Add all remaining modules with an empty end blocker below since cosmos 0.45.0 requires it
|
// Add all remaining modules with an empty end blocker below since cosmos 0.45.0 requires it
|
||||||
capabilitytypes.ModuleName,
|
capabilitytypes.ModuleName,
|
||||||
|
issuancetypes.ModuleName,
|
||||||
slashingtypes.ModuleName,
|
slashingtypes.ModuleName,
|
||||||
distrtypes.ModuleName,
|
distrtypes.ModuleName,
|
||||||
|
bep3types.ModuleName,
|
||||||
|
committeetypes.ModuleName,
|
||||||
upgradetypes.ModuleName,
|
upgradetypes.ModuleName,
|
||||||
evidencetypes.ModuleName,
|
evidencetypes.ModuleName,
|
||||||
vestingtypes.ModuleName,
|
vestingtypes.ModuleName,
|
||||||
@ -648,6 +722,10 @@ func NewApp(
|
|||||||
ibctransfertypes.ModuleName,
|
ibctransfertypes.ModuleName,
|
||||||
evmtypes.ModuleName,
|
evmtypes.ModuleName,
|
||||||
feemarkettypes.ModuleName,
|
feemarkettypes.ModuleName,
|
||||||
|
issuancetypes.ModuleName,
|
||||||
|
bep3types.ModuleName,
|
||||||
|
pricefeedtypes.ModuleName,
|
||||||
|
committeetypes.ModuleName,
|
||||||
evmutiltypes.ModuleName,
|
evmutiltypes.ModuleName,
|
||||||
genutiltypes.ModuleName, // runs arbitrary txs included in genisis state, so run after modules have been initialized
|
genutiltypes.ModuleName, // runs arbitrary txs included in genisis state, so run after modules have been initialized
|
||||||
// Add all remaining modules with an empty InitGenesis below since cosmos 0.45.0 requires it
|
// Add all remaining modules with an empty InitGenesis below since cosmos 0.45.0 requires it
|
||||||
@ -698,6 +776,8 @@ func NewApp(
|
|||||||
if options.MempoolEnableAuth {
|
if options.MempoolEnableAuth {
|
||||||
fetchers = append(fetchers,
|
fetchers = append(fetchers,
|
||||||
func(sdk.Context) []sdk.AccAddress { return options.MempoolAuthAddresses },
|
func(sdk.Context) []sdk.AccAddress { return options.MempoolAuthAddresses },
|
||||||
|
app.bep3Keeper.GetAuthorizedAddresses,
|
||||||
|
app.pricefeedKeeper.GetAuthorizedAddresses,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,11 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/0glabs/0g-chain/chaincfg"
|
"github.com/0glabs/0g-chain/chaincfg"
|
||||||
|
bep3keeper "github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
committeekeeper "github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
evmutilkeeper "github.com/0glabs/0g-chain/x/evmutil/keeper"
|
evmutilkeeper "github.com/0glabs/0g-chain/x/evmutil/keeper"
|
||||||
|
issuancekeeper "github.com/0glabs/0g-chain/x/issuance/keeper"
|
||||||
|
pricefeedkeeper "github.com/0glabs/0g-chain/x/pricefeed/keeper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -103,7 +107,10 @@ func (tApp TestApp) GetDistrKeeper() distkeeper.Keeper { return tApp.di
|
|||||||
func (tApp TestApp) GetGovKeeper() govkeeper.Keeper { return tApp.govKeeper }
|
func (tApp TestApp) GetGovKeeper() govkeeper.Keeper { return tApp.govKeeper }
|
||||||
func (tApp TestApp) GetCrisisKeeper() crisiskeeper.Keeper { return tApp.crisisKeeper }
|
func (tApp TestApp) GetCrisisKeeper() crisiskeeper.Keeper { return tApp.crisisKeeper }
|
||||||
func (tApp TestApp) GetParamsKeeper() paramskeeper.Keeper { return tApp.paramsKeeper }
|
func (tApp TestApp) GetParamsKeeper() paramskeeper.Keeper { return tApp.paramsKeeper }
|
||||||
|
func (tApp TestApp) GetIssuanceKeeper() issuancekeeper.Keeper { return tApp.issuanceKeeper }
|
||||||
|
func (tApp TestApp) GetBep3Keeper() bep3keeper.Keeper { return tApp.bep3Keeper }
|
||||||
|
func (tApp TestApp) GetPriceFeedKeeper() pricefeedkeeper.Keeper { return tApp.pricefeedKeeper }
|
||||||
|
func (tApp TestApp) GetCommitteeKeeper() committeekeeper.Keeper { return tApp.committeeKeeper }
|
||||||
func (tApp TestApp) GetEvmutilKeeper() evmutilkeeper.Keeper { return tApp.evmutilKeeper }
|
func (tApp TestApp) GetEvmutilKeeper() evmutilkeeper.Keeper { return tApp.evmutilKeeper }
|
||||||
func (tApp TestApp) GetEvmKeeper() *evmkeeper.Keeper { return tApp.evmKeeper }
|
func (tApp TestApp) GetEvmKeeper() *evmkeeper.Keeper { return tApp.evmKeeper }
|
||||||
|
|
||||||
|
BIN
proto/zgc/bep3/v1beta1/.tx.proto.swp
Normal file
BIN
proto/zgc/bep3/v1beta1/.tx.proto.swp
Normal file
Binary file not shown.
160
proto/zgc/bep3/v1beta1/bep3.proto
Normal file
160
proto/zgc/bep3/v1beta1/bep3.proto
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.bep3.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos/base/v1beta1/coin.proto";
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/bep3/types";
|
||||||
|
|
||||||
|
// Params defines the parameters for the bep3 module.
|
||||||
|
message Params {
|
||||||
|
// asset_params define the parameters for each bep3 asset
|
||||||
|
repeated AssetParam asset_params = 1 [
|
||||||
|
(gogoproto.castrepeated) = "AssetParams",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetParam defines parameters for each bep3 asset.
|
||||||
|
message AssetParam {
|
||||||
|
// denom represents the denominatin for this asset
|
||||||
|
string denom = 1;
|
||||||
|
// coin_id represents the registered coin type to use (https://github.com/satoshilabs/slips/blob/master/slip-0044.md)
|
||||||
|
int64 coin_id = 2 [(gogoproto.customname) = "CoinID"];
|
||||||
|
// supply_limit defines the maximum supply allowed for the asset - a total or time based rate limit
|
||||||
|
SupplyLimit supply_limit = 3 [(gogoproto.nullable) = false];
|
||||||
|
// active specifies if the asset is live or paused
|
||||||
|
bool active = 4;
|
||||||
|
// deputy_address the 0g-chain address of the deputy
|
||||||
|
bytes deputy_address = 5 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.AddressBytes",
|
||||||
|
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
|
||||||
|
];
|
||||||
|
// fixed_fee defines the fee for incoming swaps
|
||||||
|
string fixed_fee = 6 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.Int",
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
// min_swap_amount defines the minimum amount able to be swapped in a single message
|
||||||
|
string min_swap_amount = 7 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.Int",
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
// max_swap_amount defines the maximum amount able to be swapped in a single message
|
||||||
|
string max_swap_amount = 8 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.Int",
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
// min_block_lock defined the minimum blocks to lock
|
||||||
|
uint64 min_block_lock = 9;
|
||||||
|
// min_block_lock defined the maximum blocks to lock
|
||||||
|
uint64 max_block_lock = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupplyLimit define the absolute and time-based limits for an assets's supply.
|
||||||
|
message SupplyLimit {
|
||||||
|
// limit defines the total supply allowed
|
||||||
|
string limit = 1 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.Int",
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
// time_limited enables or disables time based supply limiting
|
||||||
|
bool time_limited = 2;
|
||||||
|
// time_period specifies the duration that time_based_limit is evalulated
|
||||||
|
google.protobuf.Duration time_period = 3 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdduration) = true
|
||||||
|
];
|
||||||
|
// time_based_limit defines the maximum supply that can be swapped within time_period
|
||||||
|
string time_based_limit = 4 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.Int",
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapStatus is the status of an AtomicSwap
|
||||||
|
enum SwapStatus {
|
||||||
|
option (gogoproto.goproto_enum_prefix) = false;
|
||||||
|
|
||||||
|
// SWAP_STATUS_UNSPECIFIED represents an unspecified status
|
||||||
|
SWAP_STATUS_UNSPECIFIED = 0;
|
||||||
|
// SWAP_STATUS_OPEN represents an open swap
|
||||||
|
SWAP_STATUS_OPEN = 1;
|
||||||
|
// SWAP_STATUS_COMPLETED represents a completed swap
|
||||||
|
SWAP_STATUS_COMPLETED = 2;
|
||||||
|
// SWAP_STATUS_EXPIRED represents an expired swap
|
||||||
|
SWAP_STATUS_EXPIRED = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapDirection is the direction of an AtomicSwap
|
||||||
|
enum SwapDirection {
|
||||||
|
option (gogoproto.goproto_enum_prefix) = false;
|
||||||
|
|
||||||
|
// SWAP_DIRECTION_UNSPECIFIED represents unspecified or invalid swap direcation
|
||||||
|
SWAP_DIRECTION_UNSPECIFIED = 0;
|
||||||
|
// SWAP_DIRECTION_INCOMING represents is incoming swap (to the 0g-chain)
|
||||||
|
SWAP_DIRECTION_INCOMING = 1;
|
||||||
|
// SWAP_DIRECTION_OUTGOING represents an outgoing swap (from the 0g- chain)
|
||||||
|
SWAP_DIRECTION_OUTGOING = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtomicSwap defines an atomic swap between chains for the pricefeed module.
|
||||||
|
message AtomicSwap {
|
||||||
|
// amount represents the amount being swapped
|
||||||
|
repeated cosmos.base.v1beta1.Coin amount = 1 [
|
||||||
|
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
// random_number_hash represents the hash of the random number
|
||||||
|
bytes random_number_hash = 2 [(gogoproto.casttype) = "github.com/cometbft/cometbft/libs/bytes.HexBytes"];
|
||||||
|
// expire_height represents the height when the swap expires
|
||||||
|
uint64 expire_height = 3;
|
||||||
|
// timestamp represents the timestamp of the swap
|
||||||
|
int64 timestamp = 4;
|
||||||
|
// sender is the 0g-chain sender of the swap
|
||||||
|
bytes sender = 5 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.AddressBytes",
|
||||||
|
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
|
||||||
|
];
|
||||||
|
// recipient is the 0g-chain recipient of the swap
|
||||||
|
bytes recipient = 6 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.AddressBytes",
|
||||||
|
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
|
||||||
|
];
|
||||||
|
// sender_other_chain is the sender on the other chain
|
||||||
|
string sender_other_chain = 7;
|
||||||
|
// recipient_other_chain is the recipient on the other chain
|
||||||
|
string recipient_other_chain = 8;
|
||||||
|
// closed_block is the block when the swap is closed
|
||||||
|
int64 closed_block = 9;
|
||||||
|
// status represents the current status of the swap
|
||||||
|
SwapStatus status = 10;
|
||||||
|
// cross_chain identifies whether the atomic swap is cross chain
|
||||||
|
bool cross_chain = 11;
|
||||||
|
// direction identifies if the swap is incoming or outgoing
|
||||||
|
SwapDirection direction = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupply defines information about an asset's supply.
|
||||||
|
message AssetSupply {
|
||||||
|
// incoming_supply represents the incoming supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin incoming_supply = 1 [(gogoproto.nullable) = false];
|
||||||
|
// outgoing_supply represents the outgoing supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin outgoing_supply = 2 [(gogoproto.nullable) = false];
|
||||||
|
// current_supply represents the current on-chain supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin current_supply = 3 [(gogoproto.nullable) = false];
|
||||||
|
// time_limited_current_supply represents the time limited current supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin time_limited_current_supply = 4 [(gogoproto.nullable) = false];
|
||||||
|
// time_elapsed represents the time elapsed
|
||||||
|
google.protobuf.Duration time_elapsed = 5 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdduration) = true
|
||||||
|
];
|
||||||
|
}
|
32
proto/zgc/bep3/v1beta1/genesis.proto
Normal file
32
proto/zgc/bep3/v1beta1/genesis.proto
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.bep3.v1beta1;
|
||||||
|
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "zgc/bep3/v1beta1/bep3.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/bep3/types";
|
||||||
|
|
||||||
|
// GenesisState defines the pricefeed module's genesis state.
|
||||||
|
message GenesisState {
|
||||||
|
// params defines all the parameters of the module.
|
||||||
|
Params params = 1 [(gogoproto.nullable) = false];
|
||||||
|
|
||||||
|
// atomic_swaps represents the state of stored atomic swaps
|
||||||
|
repeated AtomicSwap atomic_swaps = 2 [
|
||||||
|
(gogoproto.castrepeated) = "AtomicSwaps",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
|
||||||
|
// supplies represents the supply information of each atomic swap
|
||||||
|
repeated AssetSupply supplies = 3 [
|
||||||
|
(gogoproto.castrepeated) = "AssetSupplies",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
|
||||||
|
// previous_block_time represents the time of the previous block
|
||||||
|
google.protobuf.Timestamp previous_block_time = 4 [
|
||||||
|
(gogoproto.stdtime) = true,
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
165
proto/zgc/bep3/v1beta1/query.proto
Normal file
165
proto/zgc/bep3/v1beta1/query.proto
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.bep3.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos/base/query/v1beta1/pagination.proto";
|
||||||
|
import "cosmos/base/v1beta1/coin.proto";
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "zgc/bep3/v1beta1/bep3.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/bep3/types";
|
||||||
|
|
||||||
|
// Query defines the gRPC querier service for bep3 module
|
||||||
|
service Query {
|
||||||
|
// Params queries module params
|
||||||
|
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/bep3/v1beta1/params";
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupply queries info about an asset's supply
|
||||||
|
rpc AssetSupply(QueryAssetSupplyRequest) returns (QueryAssetSupplyResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/bep3/v1beta1/assetsupply/{denom}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupplies queries a list of asset supplies
|
||||||
|
rpc AssetSupplies(QueryAssetSuppliesRequest) returns (QueryAssetSuppliesResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/bep3/v1beta1/assetsupplies";
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtomicSwap queries info about an atomic swap
|
||||||
|
rpc AtomicSwap(QueryAtomicSwapRequest) returns (QueryAtomicSwapResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/bep3/v1beta1/atomicswap/{swap_id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtomicSwaps queries a list of atomic swaps
|
||||||
|
rpc AtomicSwaps(QueryAtomicSwapsRequest) returns (QueryAtomicSwapsResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/bep3/v1beta1/atomicswaps";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParamsRequest defines the request type for querying x/bep3 parameters.
|
||||||
|
message QueryParamsRequest {}
|
||||||
|
|
||||||
|
// QueryParamsResponse defines the response type for querying x/bep3 parameters.
|
||||||
|
message QueryParamsResponse {
|
||||||
|
// params represents the parameters of the module
|
||||||
|
Params params = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAssetSupplyRequest is the request type for the Query/AssetSupply RPC method.
|
||||||
|
message QueryAssetSupplyRequest {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
// denom filters the asset response for the specified denom
|
||||||
|
string denom = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupplyResponse defines information about an asset's supply.
|
||||||
|
message AssetSupplyResponse {
|
||||||
|
// incoming_supply represents the incoming supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin incoming_supply = 1 [(gogoproto.nullable) = false];
|
||||||
|
// outgoing_supply represents the outgoing supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin outgoing_supply = 2 [(gogoproto.nullable) = false];
|
||||||
|
// current_supply represents the current on-chain supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin current_supply = 3 [(gogoproto.nullable) = false];
|
||||||
|
// time_limited_current_supply represents the time limited current supply of an asset
|
||||||
|
cosmos.base.v1beta1.Coin time_limited_current_supply = 4 [(gogoproto.nullable) = false];
|
||||||
|
// time_elapsed represents the time elapsed
|
||||||
|
google.protobuf.Duration time_elapsed = 5 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdduration) = true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAssetSupplyResponse is the response type for the Query/AssetSupply RPC method.
|
||||||
|
message QueryAssetSupplyResponse {
|
||||||
|
// asset_supply represents the supply of the asset
|
||||||
|
AssetSupplyResponse asset_supply = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAssetSuppliesRequest is the request type for the Query/AssetSupplies RPC method.
|
||||||
|
message QueryAssetSuppliesRequest {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAssetSuppliesResponse is the response type for the Query/AssetSupplies RPC method.
|
||||||
|
message QueryAssetSuppliesResponse {
|
||||||
|
// asset_supplies represents the supplies of returned assets
|
||||||
|
repeated AssetSupplyResponse asset_supplies = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAtomicSwapRequest is the request type for the Query/AtomicSwap RPC method.
|
||||||
|
message QueryAtomicSwapRequest {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
// swap_id represents the id of the swap to query
|
||||||
|
string swap_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAtomicSwapResponse is the response type for the Query/AtomicSwap RPC method.
|
||||||
|
message QueryAtomicSwapResponse {
|
||||||
|
AtomicSwapResponse atomic_swap = 2 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtomicSwapResponse represents the returned atomic swap properties
|
||||||
|
message AtomicSwapResponse {
|
||||||
|
// id represents the id of the atomic swap
|
||||||
|
string id = 1;
|
||||||
|
// amount represents the amount being swapped
|
||||||
|
repeated cosmos.base.v1beta1.Coin amount = 2 [
|
||||||
|
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
// random_number_hash represents the hash of the random number
|
||||||
|
string random_number_hash = 3;
|
||||||
|
// expire_height represents the height when the swap expires
|
||||||
|
uint64 expire_height = 4;
|
||||||
|
// timestamp represents the timestamp of the swap
|
||||||
|
int64 timestamp = 5;
|
||||||
|
// sender is the 0g-chain sender of the swap
|
||||||
|
string sender = 6 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
|
// recipient is the 0g-chain recipient of the swap
|
||||||
|
string recipient = 7 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
|
// sender_other_chain is the sender on the other chain
|
||||||
|
string sender_other_chain = 8;
|
||||||
|
// recipient_other_chain is the recipient on the other chain
|
||||||
|
string recipient_other_chain = 9;
|
||||||
|
// closed_block is the block when the swap is closed
|
||||||
|
int64 closed_block = 10;
|
||||||
|
// status represents the current status of the swap
|
||||||
|
SwapStatus status = 11;
|
||||||
|
// cross_chain identifies whether the atomic swap is cross chain
|
||||||
|
bool cross_chain = 12;
|
||||||
|
// direction identifies if the swap is incoming or outgoing
|
||||||
|
SwapDirection direction = 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAtomicSwapsRequest is the request type for the Query/AtomicSwaps RPC method.
|
||||||
|
message QueryAtomicSwapsRequest {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
// involve filters by address
|
||||||
|
string involve = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
|
// expiration filters by expiration block height
|
||||||
|
uint64 expiration = 2;
|
||||||
|
// status filters by swap status
|
||||||
|
SwapStatus status = 3;
|
||||||
|
// direction fitlers by swap direction
|
||||||
|
SwapDirection direction = 4;
|
||||||
|
|
||||||
|
cosmos.base.query.v1beta1.PageRequest pagination = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAtomicSwapsResponse is the response type for the Query/AtomicSwaps RPC method.
|
||||||
|
message QueryAtomicSwapsResponse {
|
||||||
|
// atomic_swap represents the returned atomic swaps for the request
|
||||||
|
repeated AtomicSwapResponse atomic_swaps = 1 [(gogoproto.nullable) = false];
|
||||||
|
|
||||||
|
cosmos.base.query.v1beta1.PageResponse pagination = 3;
|
||||||
|
}
|
69
proto/zgc/bep3/v1beta1/tx.proto
Normal file
69
proto/zgc/bep3/v1beta1/tx.proto
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.bep3.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos/base/v1beta1/coin.proto";
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/bep3/types";
|
||||||
|
|
||||||
|
// Msg defines the bep3 Msg service.
|
||||||
|
service Msg {
|
||||||
|
// CreateAtomicSwap defines a method for creating an atomic swap
|
||||||
|
rpc CreateAtomicSwap(MsgCreateAtomicSwap) returns (MsgCreateAtomicSwapResponse);
|
||||||
|
|
||||||
|
// ClaimAtomicSwap defines a method for claiming an atomic swap
|
||||||
|
rpc ClaimAtomicSwap(MsgClaimAtomicSwap) returns (MsgClaimAtomicSwapResponse);
|
||||||
|
|
||||||
|
// RefundAtomicSwap defines a method for refunding an atomic swap
|
||||||
|
rpc RefundAtomicSwap(MsgRefundAtomicSwap) returns (MsgRefundAtomicSwapResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgCreateAtomicSwap defines the Msg/CreateAtomicSwap request type.
|
||||||
|
message MsgCreateAtomicSwap {
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string from = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
|
string to = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
|
string recipient_other_chain = 3;
|
||||||
|
string sender_other_chain = 4;
|
||||||
|
string random_number_hash = 5;
|
||||||
|
int64 timestamp = 6;
|
||||||
|
repeated cosmos.base.v1beta1.Coin amount = 7 [
|
||||||
|
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
uint64 height_span = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgCreateAtomicSwapResponse defines the Msg/CreateAtomicSwap response type.
|
||||||
|
message MsgCreateAtomicSwapResponse {}
|
||||||
|
|
||||||
|
// MsgClaimAtomicSwap defines the Msg/ClaimAtomicSwap request type.
|
||||||
|
message MsgClaimAtomicSwap {
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string from = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
|
string swap_id = 2 [(gogoproto.customname) = "SwapID"];
|
||||||
|
string random_number = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgClaimAtomicSwapResponse defines the Msg/ClaimAtomicSwap response type.
|
||||||
|
message MsgClaimAtomicSwapResponse {}
|
||||||
|
|
||||||
|
// MsgRefundAtomicSwap defines the Msg/RefundAtomicSwap request type.
|
||||||
|
message MsgRefundAtomicSwap {
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string from = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
|
string swap_id = 2 [(gogoproto.customname) = "SwapID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgRefundAtomicSwapResponse defines the Msg/RefundAtomicSwap response type.
|
||||||
|
message MsgRefundAtomicSwapResponse {}
|
70
proto/zgc/committee/v1beta1/committee.proto
Normal file
70
proto/zgc/committee/v1beta1/committee.proto
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.committee.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/any.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/committee/types";
|
||||||
|
option (gogoproto.goproto_getters_all) = false;
|
||||||
|
|
||||||
|
// BaseCommittee is a common type shared by all Committees
|
||||||
|
message BaseCommittee {
|
||||||
|
option (cosmos_proto.implements_interface) = "Committee";
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
uint64 id = 1 [(gogoproto.customname) = "ID"];
|
||||||
|
string description = 2;
|
||||||
|
repeated bytes members = 3 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.AddressBytes",
|
||||||
|
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
|
||||||
|
];
|
||||||
|
repeated google.protobuf.Any permissions = 4 [(cosmos_proto.accepts_interface) = "Permission"];
|
||||||
|
|
||||||
|
// Smallest percentage that must vote for a proposal to pass
|
||||||
|
string vote_threshold = 5 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
|
||||||
|
// The length of time a proposal remains active for. Proposals will close earlier if they get enough votes.
|
||||||
|
google.protobuf.Duration proposal_duration = 6 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdduration) = true
|
||||||
|
];
|
||||||
|
TallyOption tally_option = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemberCommittee is an alias of BaseCommittee
|
||||||
|
message MemberCommittee {
|
||||||
|
option (cosmos_proto.implements_interface) = "Committee";
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
BaseCommittee base_committee = 1 [(gogoproto.embed) = true];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenCommittee supports voting on proposals by token holders
|
||||||
|
message TokenCommittee {
|
||||||
|
option (cosmos_proto.implements_interface) = "Committee";
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
BaseCommittee base_committee = 1 [(gogoproto.embed) = true];
|
||||||
|
string quorum = 2 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
string tally_denom = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TallyOption enumerates the valid types of a tally.
|
||||||
|
enum TallyOption {
|
||||||
|
option (gogoproto.goproto_enum_prefix) = false;
|
||||||
|
|
||||||
|
// TALLY_OPTION_UNSPECIFIED defines a null tally option.
|
||||||
|
TALLY_OPTION_UNSPECIFIED = 0;
|
||||||
|
// Votes are tallied each block and the proposal passes as soon as the vote threshold is reached
|
||||||
|
TALLY_OPTION_FIRST_PAST_THE_POST = 1;
|
||||||
|
// Votes are tallied exactly once, when the deadline time is reached
|
||||||
|
TALLY_OPTION_DEADLINE = 2;
|
||||||
|
}
|
62
proto/zgc/committee/v1beta1/genesis.proto
Normal file
62
proto/zgc/committee/v1beta1/genesis.proto
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.committee.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/any.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/committee/types";
|
||||||
|
|
||||||
|
// GenesisState defines the committee module's genesis state.
|
||||||
|
message GenesisState {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
uint64 next_proposal_id = 1 [(gogoproto.customname) = "NextProposalID"];
|
||||||
|
repeated google.protobuf.Any committees = 2 [(cosmos_proto.accepts_interface) = "Committee"];
|
||||||
|
repeated Proposal proposals = 3 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.castrepeated) = "Proposals"
|
||||||
|
];
|
||||||
|
repeated Vote votes = 4 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proposal is an internal record of a governance proposal submitted to a committee.
|
||||||
|
message Proposal {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
google.protobuf.Any content = 1 [(cosmos_proto.accepts_interface) = "cosmos.gov.v1beta1.Content"];
|
||||||
|
uint64 id = 2 [(gogoproto.customname) = "ID"];
|
||||||
|
uint64 committee_id = 3 [(gogoproto.customname) = "CommitteeID"];
|
||||||
|
google.protobuf.Timestamp deadline = 4 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdtime) = true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vote is an internal record of a single governance vote.
|
||||||
|
message Vote {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
uint64 proposal_id = 1 [(gogoproto.customname) = "ProposalID"];
|
||||||
|
bytes voter = 2 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.AddressBytes",
|
||||||
|
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
|
||||||
|
];
|
||||||
|
VoteType vote_type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VoteType enumerates the valid types of a vote.
|
||||||
|
enum VoteType {
|
||||||
|
option (gogoproto.goproto_enum_prefix) = false;
|
||||||
|
|
||||||
|
// VOTE_TYPE_UNSPECIFIED defines a no-op vote option.
|
||||||
|
VOTE_TYPE_UNSPECIFIED = 0;
|
||||||
|
// VOTE_TYPE_YES defines a yes vote option.
|
||||||
|
VOTE_TYPE_YES = 1;
|
||||||
|
// VOTE_TYPE_NO defines a no vote option.
|
||||||
|
VOTE_TYPE_NO = 2;
|
||||||
|
// VOTE_TYPE_ABSTAIN defines an abstain vote option.
|
||||||
|
VOTE_TYPE_ABSTAIN = 3;
|
||||||
|
}
|
72
proto/zgc/committee/v1beta1/permissions.proto
Normal file
72
proto/zgc/committee/v1beta1/permissions.proto
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.committee.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/committee/types";
|
||||||
|
|
||||||
|
// GodPermission allows any governance proposal. It is used mainly for testing.
|
||||||
|
message GodPermission {
|
||||||
|
option (cosmos_proto.implements_interface) = "Permission";
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoftwareUpgradePermission permission type for software upgrade proposals
|
||||||
|
message SoftwareUpgradePermission {
|
||||||
|
option (cosmos_proto.implements_interface) = "Permission";
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextPermission allows any text governance proposal.
|
||||||
|
message TextPermission {
|
||||||
|
option (cosmos_proto.implements_interface) = "Permission";
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommunityCDPRepayDebtPermission allows submission of CommunityCDPRepayDebtProposal
|
||||||
|
message CommunityCDPRepayDebtPermission {
|
||||||
|
option (cosmos_proto.implements_interface) = "Permission";
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommunityCDPWithdrawCollateralPermission allows submission of CommunityCDPWithdrawCollateralProposal
|
||||||
|
message CommunityCDPWithdrawCollateralPermission {
|
||||||
|
option (cosmos_proto.implements_interface) = "Permission";
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommunityPoolLendWithdrawPermission allows submission of CommunityPoolLendWithdrawProposal
|
||||||
|
message CommunityPoolLendWithdrawPermission {
|
||||||
|
option (cosmos_proto.implements_interface) = "Permission";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamsChangePermission allows any parameter or sub parameter change proposal.
|
||||||
|
message ParamsChangePermission {
|
||||||
|
option (cosmos_proto.implements_interface) = "Permission";
|
||||||
|
repeated AllowedParamsChange allowed_params_changes = 1 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.castrepeated) = "AllowedParamsChanges"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedParamsChange contains data on the allowed parameter changes for subspace, key, and sub params requirements.
|
||||||
|
message AllowedParamsChange {
|
||||||
|
string subspace = 1;
|
||||||
|
string key = 2;
|
||||||
|
|
||||||
|
// Requirements for when the subparam value is a single record. This contains list of allowed attribute keys that can
|
||||||
|
// be changed on the subparam record.
|
||||||
|
repeated string single_subparam_allowed_attrs = 3;
|
||||||
|
|
||||||
|
// Requirements for when the subparam value is a list of records. The requirements contains requirements for each
|
||||||
|
// record in the list.
|
||||||
|
repeated SubparamRequirement multi_subparams_requirements = 4 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubparamRequirement contains requirements for a single record in a subparam value list
|
||||||
|
message SubparamRequirement {
|
||||||
|
// The required attr key of the param record.
|
||||||
|
string key = 1;
|
||||||
|
|
||||||
|
// The required param value for the param record key. The key and value is used to match to the target param record.
|
||||||
|
string val = 2;
|
||||||
|
|
||||||
|
// The sub param attrs that are allowed to be changed.
|
||||||
|
repeated string allowed_subparam_attr_changes = 3;
|
||||||
|
}
|
27
proto/zgc/committee/v1beta1/proposal.proto
Normal file
27
proto/zgc/committee/v1beta1/proposal.proto
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.committee.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/any.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/committee/types";
|
||||||
|
option (gogoproto.goproto_getters_all) = false;
|
||||||
|
|
||||||
|
// CommitteeChangeProposal is a gov proposal for creating a new committee or modifying an existing one.
|
||||||
|
message CommitteeChangeProposal {
|
||||||
|
option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content";
|
||||||
|
|
||||||
|
string title = 1;
|
||||||
|
string description = 2;
|
||||||
|
google.protobuf.Any new_committee = 3 [(cosmos_proto.accepts_interface) = "Committee"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitteeDeleteProposal is a gov proposal for removing a committee.
|
||||||
|
message CommitteeDeleteProposal {
|
||||||
|
option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content";
|
||||||
|
|
||||||
|
string title = 1;
|
||||||
|
string description = 2;
|
||||||
|
uint64 committee_id = 3 [(gogoproto.customname) = "CommitteeID"];
|
||||||
|
}
|
181
proto/zgc/committee/v1beta1/query.proto
Normal file
181
proto/zgc/committee/v1beta1/query.proto
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.committee.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos/base/query/v1beta1/pagination.proto";
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/protobuf/any.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "zgc/committee/v1beta1/genesis.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/committee/types";
|
||||||
|
option (gogoproto.goproto_getters_all) = false;
|
||||||
|
|
||||||
|
// Query defines the gRPC querier service for committee module
|
||||||
|
service Query {
|
||||||
|
// Committees queries all committess of the committee module.
|
||||||
|
rpc Committees(QueryCommitteesRequest) returns (QueryCommitteesResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/committees";
|
||||||
|
}
|
||||||
|
// Committee queries a committee based on committee ID.
|
||||||
|
rpc Committee(QueryCommitteeRequest) returns (QueryCommitteeResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/committees/{committee_id}";
|
||||||
|
}
|
||||||
|
// Proposals queries proposals based on committee ID.
|
||||||
|
rpc Proposals(QueryProposalsRequest) returns (QueryProposalsResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/proposals";
|
||||||
|
}
|
||||||
|
// Deposits queries a proposal based on proposal ID.
|
||||||
|
rpc Proposal(QueryProposalRequest) returns (QueryProposalResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/proposals/{proposal_id}";
|
||||||
|
}
|
||||||
|
// NextProposalID queries the next proposal ID of the committee module.
|
||||||
|
rpc NextProposalID(QueryNextProposalIDRequest) returns (QueryNextProposalIDResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/next-proposal-id";
|
||||||
|
}
|
||||||
|
// Votes queries all votes for a single proposal ID.
|
||||||
|
rpc Votes(QueryVotesRequest) returns (QueryVotesResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/proposals/{proposal_id}/votes";
|
||||||
|
}
|
||||||
|
// Vote queries the vote of a single voter for a single proposal ID.
|
||||||
|
rpc Vote(QueryVoteRequest) returns (QueryVoteResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/proposals/{proposal_id}/votes/{voter}";
|
||||||
|
}
|
||||||
|
// Tally queries the tally of a single proposal ID.
|
||||||
|
rpc Tally(QueryTallyRequest) returns (QueryTallyResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/proposals/{proposal_id}/tally";
|
||||||
|
}
|
||||||
|
// RawParams queries the raw params data of any subspace and key.
|
||||||
|
rpc RawParams(QueryRawParamsRequest) returns (QueryRawParamsResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/committee/v1beta1/raw-params";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryCommitteesRequest defines the request type for querying x/committee committees.
|
||||||
|
message QueryCommitteesRequest {}
|
||||||
|
|
||||||
|
// QueryCommitteesResponse defines the response type for querying x/committee committees.
|
||||||
|
message QueryCommitteesResponse {
|
||||||
|
repeated google.protobuf.Any committees = 1 [(cosmos_proto.accepts_interface) = "Committee"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryCommitteeRequest defines the request type for querying x/committee committee.
|
||||||
|
message QueryCommitteeRequest {
|
||||||
|
uint64 committee_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryCommitteeResponse defines the response type for querying x/committee committee.
|
||||||
|
message QueryCommitteeResponse {
|
||||||
|
google.protobuf.Any committee = 1 [(cosmos_proto.accepts_interface) = "Committee"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryProposalsRequest defines the request type for querying x/committee proposals.
|
||||||
|
message QueryProposalsRequest {
|
||||||
|
uint64 committee_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryProposalsResponse defines the response type for querying x/committee proposals.
|
||||||
|
message QueryProposalsResponse {
|
||||||
|
repeated QueryProposalResponse proposals = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryProposalRequest defines the request type for querying x/committee proposal.
|
||||||
|
message QueryProposalRequest {
|
||||||
|
uint64 proposal_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryProposalResponse defines the response type for querying x/committee proposal.
|
||||||
|
message QueryProposalResponse {
|
||||||
|
google.protobuf.Any pub_proposal = 1 [
|
||||||
|
(cosmos_proto.accepts_interface) = "cosmos.gov.v1beta1.Content",
|
||||||
|
(gogoproto.customname) = "PubProposal"
|
||||||
|
];
|
||||||
|
uint64 id = 2 [(gogoproto.customname) = "ID"];
|
||||||
|
uint64 committee_id = 3 [(gogoproto.customname) = "CommitteeID"];
|
||||||
|
google.protobuf.Timestamp deadline = 4 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdtime) = true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryNextProposalIDRequest defines the request type for querying x/committee NextProposalID.
|
||||||
|
message QueryNextProposalIDRequest {}
|
||||||
|
|
||||||
|
// QueryNextProposalIDRequest defines the response type for querying x/committee NextProposalID.
|
||||||
|
message QueryNextProposalIDResponse {
|
||||||
|
uint64 next_proposal_id = 1 [(gogoproto.customname) = "NextProposalID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryVotesRequest defines the request type for querying x/committee votes.
|
||||||
|
message QueryVotesRequest {
|
||||||
|
uint64 proposal_id = 1;
|
||||||
|
cosmos.base.query.v1beta1.PageRequest pagination = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryVotesResponse defines the response type for querying x/committee votes.
|
||||||
|
message QueryVotesResponse {
|
||||||
|
// votes defined the queried votes.
|
||||||
|
repeated QueryVoteResponse votes = 1 [(gogoproto.nullable) = false];
|
||||||
|
|
||||||
|
// pagination defines the pagination in the response.
|
||||||
|
cosmos.base.query.v1beta1.PageResponse pagination = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryVoteRequest defines the request type for querying x/committee vote.
|
||||||
|
message QueryVoteRequest {
|
||||||
|
uint64 proposal_id = 1;
|
||||||
|
string voter = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryVoteResponse defines the response type for querying x/committee vote.
|
||||||
|
message QueryVoteResponse {
|
||||||
|
uint64 proposal_id = 1 [(gogoproto.customname) = "ProposalID"];
|
||||||
|
string voter = 2;
|
||||||
|
VoteType vote_type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTallyRequest defines the request type for querying x/committee tally.
|
||||||
|
message QueryTallyRequest {
|
||||||
|
uint64 proposal_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryTallyResponse defines the response type for querying x/committee tally.
|
||||||
|
message QueryTallyResponse {
|
||||||
|
uint64 proposal_id = 1 [(gogoproto.customname) = "ProposalID"];
|
||||||
|
string yes_votes = 2 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
string no_votes = 3 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
string current_votes = 4 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
string possible_votes = 5 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
string vote_threshold = 6 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
string quorum = 7 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRawParamsRequest defines the request type for querying x/committee raw params.
|
||||||
|
message QueryRawParamsRequest {
|
||||||
|
string subspace = 1;
|
||||||
|
string key = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRawParamsResponse defines the response type for querying x/committee raw params.
|
||||||
|
message QueryRawParamsResponse {
|
||||||
|
string raw_data = 1;
|
||||||
|
}
|
40
proto/zgc/committee/v1beta1/tx.proto
Normal file
40
proto/zgc/committee/v1beta1/tx.proto
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.committee.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/any.proto";
|
||||||
|
import "zgc/committee/v1beta1/genesis.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/committee/types";
|
||||||
|
option (gogoproto.goproto_getters_all) = false;
|
||||||
|
|
||||||
|
// Msg defines the committee Msg service
|
||||||
|
service Msg {
|
||||||
|
// SubmitProposal defines a method for submitting a committee proposal
|
||||||
|
rpc SubmitProposal(MsgSubmitProposal) returns (MsgSubmitProposalResponse);
|
||||||
|
// Vote defines a method for voting on a proposal
|
||||||
|
rpc Vote(MsgVote) returns (MsgVoteResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgSubmitProposal is used by committee members to create a new proposal that they can vote on.
|
||||||
|
message MsgSubmitProposal {
|
||||||
|
google.protobuf.Any pub_proposal = 1 [(cosmos_proto.accepts_interface) = "cosmos.gov.v1beta1.Content"];
|
||||||
|
string proposer = 2;
|
||||||
|
uint64 committee_id = 3 [(gogoproto.customname) = "CommitteeID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgSubmitProposalResponse defines the SubmitProposal response type
|
||||||
|
message MsgSubmitProposalResponse {
|
||||||
|
uint64 proposal_id = 1 [(gogoproto.customname) = "ProposalID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgVote is submitted by committee members to vote on proposals.
|
||||||
|
message MsgVote {
|
||||||
|
uint64 proposal_id = 1 [(gogoproto.customname) = "ProposalID"];
|
||||||
|
string voter = 2;
|
||||||
|
VoteType vote_type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgVoteResponse defines the Vote response type
|
||||||
|
message MsgVoteResponse {}
|
64
proto/zgc/issuance/v1beta1/genesis.proto
Normal file
64
proto/zgc/issuance/v1beta1/genesis.proto
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.issuance.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos/base/v1beta1/coin.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/issuance/types";
|
||||||
|
|
||||||
|
// GenesisState defines the issuance module's genesis state.
|
||||||
|
message GenesisState {
|
||||||
|
// params defines all the parameters of the module.
|
||||||
|
Params params = 1 [(gogoproto.nullable) = false];
|
||||||
|
|
||||||
|
repeated AssetSupply supplies = 2 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params defines the parameters for the issuance module.
|
||||||
|
message Params {
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
repeated Asset assets = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset type for assets in the issuance module
|
||||||
|
message Asset {
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
string owner = 1;
|
||||||
|
string denom = 2;
|
||||||
|
repeated string blocked_addresses = 3;
|
||||||
|
bool paused = 4;
|
||||||
|
bool blockable = 5;
|
||||||
|
RateLimit rate_limit = 6 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimit parameters for rate-limiting the supply of an issued asset
|
||||||
|
message RateLimit {
|
||||||
|
bool active = 1;
|
||||||
|
|
||||||
|
bytes limit = 2 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.jsontag) = "limit,omitempty"
|
||||||
|
];
|
||||||
|
|
||||||
|
google.protobuf.Duration time_period = 3 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdduration) = true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupply contains information about an asset's rate-limited supply (the
|
||||||
|
// total supply of the asset is tracked in the top-level supply module)
|
||||||
|
message AssetSupply {
|
||||||
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
cosmos.base.v1beta1.Coin current_supply = 1 [(gogoproto.nullable) = false];
|
||||||
|
|
||||||
|
google.protobuf.Duration time_elapsed = 2 [
|
||||||
|
(gogoproto.nullable) = false,
|
||||||
|
(gogoproto.stdduration) = true
|
||||||
|
];
|
||||||
|
}
|
24
proto/zgc/issuance/v1beta1/query.proto
Normal file
24
proto/zgc/issuance/v1beta1/query.proto
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.issuance.v1beta1;
|
||||||
|
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "zgc/issuance/v1beta1/genesis.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/issuance/types";
|
||||||
|
|
||||||
|
// Query defines the gRPC querier service for issuance module
|
||||||
|
service Query {
|
||||||
|
// Params queries all parameters of the issuance module.
|
||||||
|
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/issuance/v1beta1/params";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParamsRequest defines the request type for querying x/issuance parameters.
|
||||||
|
message QueryParamsRequest {}
|
||||||
|
|
||||||
|
// QueryParamsResponse defines the response type for querying x/issuance parameters.
|
||||||
|
message QueryParamsResponse {
|
||||||
|
Params params = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
89
proto/zgc/issuance/v1beta1/tx.proto
Normal file
89
proto/zgc/issuance/v1beta1/tx.proto
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.issuance.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos/base/v1beta1/coin.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/issuance/types";
|
||||||
|
|
||||||
|
// Msg defines the issuance Msg service.
|
||||||
|
service Msg {
|
||||||
|
// IssueTokens message type used by the issuer to issue new tokens
|
||||||
|
rpc IssueTokens(MsgIssueTokens) returns (MsgIssueTokensResponse);
|
||||||
|
|
||||||
|
// RedeemTokens message type used by the issuer to redeem (burn) tokens
|
||||||
|
rpc RedeemTokens(MsgRedeemTokens) returns (MsgRedeemTokensResponse);
|
||||||
|
|
||||||
|
// BlockAddress message type used by the issuer to block an address from holding or transferring tokens
|
||||||
|
rpc BlockAddress(MsgBlockAddress) returns (MsgBlockAddressResponse);
|
||||||
|
|
||||||
|
// UnblockAddress message type used by the issuer to unblock an address from holding or transferring tokens
|
||||||
|
rpc UnblockAddress(MsgUnblockAddress) returns (MsgUnblockAddressResponse);
|
||||||
|
|
||||||
|
// SetPauseStatus message type used to pause or unpause status
|
||||||
|
rpc SetPauseStatus(MsgSetPauseStatus) returns (MsgSetPauseStatusResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgIssueTokens represents a message used by the issuer to issue new tokens
|
||||||
|
message MsgIssueTokens {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string sender = 1;
|
||||||
|
cosmos.base.v1beta1.Coin tokens = 2 [(gogoproto.nullable) = false];
|
||||||
|
string receiver = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgIssueTokensResponse defines the Msg/IssueTokens response type.
|
||||||
|
message MsgIssueTokensResponse {}
|
||||||
|
|
||||||
|
// MsgRedeemTokens represents a message used by the issuer to redeem (burn) tokens
|
||||||
|
message MsgRedeemTokens {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string sender = 1;
|
||||||
|
cosmos.base.v1beta1.Coin tokens = 2 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgRedeemTokensResponse defines the Msg/RedeemTokens response type.
|
||||||
|
message MsgRedeemTokensResponse {}
|
||||||
|
|
||||||
|
// MsgBlockAddress represents a message used by the issuer to block an address from holding or transferring tokens
|
||||||
|
message MsgBlockAddress {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string sender = 1;
|
||||||
|
string denom = 2;
|
||||||
|
string blocked_address = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgBlockAddressResponse defines the Msg/BlockAddress response type.
|
||||||
|
message MsgBlockAddressResponse {}
|
||||||
|
|
||||||
|
// MsgUnblockAddress message type used by the issuer to unblock an address from holding or transferring tokens
|
||||||
|
message MsgUnblockAddress {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string sender = 1;
|
||||||
|
string denom = 2;
|
||||||
|
string blocked_address = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgUnblockAddressResponse defines the Msg/UnblockAddress response type.
|
||||||
|
message MsgUnblockAddressResponse {}
|
||||||
|
|
||||||
|
// MsgSetPauseStatus message type used by the issuer to pause or unpause status
|
||||||
|
message MsgSetPauseStatus {
|
||||||
|
option (gogoproto.equal) = false;
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string sender = 1;
|
||||||
|
string denom = 2;
|
||||||
|
bool status = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgSetPauseStatusResponse defines the Msg/SetPauseStatus response type.
|
||||||
|
message MsgSetPauseStatusResponse {}
|
20
proto/zgc/pricefeed/v1beta1/genesis.proto
Normal file
20
proto/zgc/pricefeed/v1beta1/genesis.proto
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.pricefeed.v1beta1;
|
||||||
|
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "zgc/pricefeed/v1beta1/store.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/pricefeed/types";
|
||||||
|
option (gogoproto.equal_all) = true;
|
||||||
|
option (gogoproto.verbose_equal_all) = true;
|
||||||
|
|
||||||
|
// GenesisState defines the pricefeed module's genesis state.
|
||||||
|
message GenesisState {
|
||||||
|
// params defines all the parameters of the module.
|
||||||
|
Params params = 1 [(gogoproto.nullable) = false];
|
||||||
|
|
||||||
|
repeated PostedPrice posted_prices = 2 [
|
||||||
|
(gogoproto.castrepeated) = "PostedPrices",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
163
proto/zgc/pricefeed/v1beta1/query.proto
Normal file
163
proto/zgc/pricefeed/v1beta1/query.proto
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.pricefeed.v1beta1;
|
||||||
|
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "zgc/pricefeed/v1beta1/store.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/pricefeed/types";
|
||||||
|
option (gogoproto.equal_all) = true;
|
||||||
|
option (gogoproto.verbose_equal_all) = true;
|
||||||
|
|
||||||
|
// Query defines the gRPC querier service for pricefeed module
|
||||||
|
service Query {
|
||||||
|
// Params queries all parameters of the pricefeed module.
|
||||||
|
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/pricefeed/v1beta1/params";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Price queries price details based on a market
|
||||||
|
rpc Price(QueryPriceRequest) returns (QueryPriceResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/pricefeed/v1beta1/prices/{market_id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prices queries all prices
|
||||||
|
rpc Prices(QueryPricesRequest) returns (QueryPricesResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/pricefeed/v1beta1/prices";
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawPrices queries all raw prices based on a market
|
||||||
|
rpc RawPrices(QueryRawPricesRequest) returns (QueryRawPricesResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/pricefeed/v1beta1/rawprices/{market_id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oracles queries all oracles based on a market
|
||||||
|
rpc Oracles(QueryOraclesRequest) returns (QueryOraclesResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/pricefeed/v1beta1/oracles/{market_id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Markets queries all markets
|
||||||
|
rpc Markets(QueryMarketsRequest) returns (QueryMarketsResponse) {
|
||||||
|
option (google.api.http).get = "/0g-chain/pricefeed/v1beta1/markets";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParamsRequest defines the request type for querying x/pricefeed
|
||||||
|
// parameters.
|
||||||
|
message QueryParamsRequest {}
|
||||||
|
|
||||||
|
// QueryParamsResponse defines the response type for querying x/pricefeed
|
||||||
|
// parameters.
|
||||||
|
message QueryParamsResponse {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
Params params = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryPriceRequest is the request type for the Query/PriceRequest RPC method.
|
||||||
|
message QueryPriceRequest {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string market_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryPriceResponse is the response type for the Query/Prices RPC method.
|
||||||
|
message QueryPriceResponse {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
CurrentPriceResponse price = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryPricesRequest is the request type for the Query/Prices RPC method.
|
||||||
|
message QueryPricesRequest {}
|
||||||
|
|
||||||
|
// QueryPricesResponse is the response type for the Query/Prices RPC method.
|
||||||
|
message QueryPricesResponse {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
repeated CurrentPriceResponse prices = 1 [
|
||||||
|
(gogoproto.castrepeated) = "CurrentPriceResponses",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRawPricesRequest is the request type for the Query/RawPrices RPC method.
|
||||||
|
message QueryRawPricesRequest {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string market_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRawPricesResponse is the response type for the Query/RawPrices RPC
|
||||||
|
// method.
|
||||||
|
message QueryRawPricesResponse {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
repeated PostedPriceResponse raw_prices = 1 [
|
||||||
|
(gogoproto.castrepeated) = "PostedPriceResponses",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryOraclesRequest is the request type for the Query/Oracles RPC method.
|
||||||
|
message QueryOraclesRequest {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
string market_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryOraclesResponse is the response type for the Query/Oracles RPC method.
|
||||||
|
message QueryOraclesResponse {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
// List of oracle addresses
|
||||||
|
repeated string oracles = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryMarketsRequest is the request type for the Query/Markets RPC method.
|
||||||
|
message QueryMarketsRequest {}
|
||||||
|
|
||||||
|
// QueryMarketsResponse is the response type for the Query/Markets RPC method.
|
||||||
|
message QueryMarketsResponse {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
// List of markets
|
||||||
|
repeated MarketResponse markets = 1 [
|
||||||
|
(gogoproto.castrepeated) = "MarketResponses",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostedPriceResponse defines a price for market posted by a specific oracle.
|
||||||
|
message PostedPriceResponse {
|
||||||
|
string market_id = 1 [(gogoproto.customname) = "MarketID"];
|
||||||
|
string oracle_address = 2;
|
||||||
|
string price = 3 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
google.protobuf.Timestamp expiry = 4 [
|
||||||
|
(gogoproto.stdtime) = true,
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentPriceResponse defines a current price for a particular market in the pricefeed
|
||||||
|
// module.
|
||||||
|
message CurrentPriceResponse {
|
||||||
|
string market_id = 1 [(gogoproto.customname) = "MarketID"];
|
||||||
|
string price = 2 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarketResponse defines an asset in the pricefeed.
|
||||||
|
message MarketResponse {
|
||||||
|
string market_id = 1 [(gogoproto.customname) = "MarketID"];
|
||||||
|
string base_asset = 2;
|
||||||
|
string quote_asset = 3;
|
||||||
|
repeated string oracles = 4;
|
||||||
|
bool active = 5;
|
||||||
|
}
|
57
proto/zgc/pricefeed/v1beta1/store.proto
Normal file
57
proto/zgc/pricefeed/v1beta1/store.proto
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.pricefeed.v1beta1;
|
||||||
|
|
||||||
|
import "cosmos_proto/cosmos.proto";
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/pricefeed/types";
|
||||||
|
option (gogoproto.equal_all) = true;
|
||||||
|
option (gogoproto.verbose_equal_all) = true;
|
||||||
|
|
||||||
|
// Params defines the parameters for the pricefeed module.
|
||||||
|
message Params {
|
||||||
|
repeated Market markets = 1 [
|
||||||
|
(gogoproto.castrepeated) = "Markets",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Market defines an asset in the pricefeed.
|
||||||
|
message Market {
|
||||||
|
string market_id = 1 [(gogoproto.customname) = "MarketID"];
|
||||||
|
string base_asset = 2;
|
||||||
|
string quote_asset = 3;
|
||||||
|
repeated bytes oracles = 4 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.AddressBytes",
|
||||||
|
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
|
||||||
|
];
|
||||||
|
bool active = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostedPrice defines a price for market posted by a specific oracle.
|
||||||
|
message PostedPrice {
|
||||||
|
string market_id = 1 [(gogoproto.customname) = "MarketID"];
|
||||||
|
bytes oracle_address = 2 [
|
||||||
|
(cosmos_proto.scalar) = "cosmos.AddressBytes",
|
||||||
|
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
|
||||||
|
];
|
||||||
|
string price = 3 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
google.protobuf.Timestamp expiry = 4 [
|
||||||
|
(gogoproto.stdtime) = true,
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentPrice defines a current price for a particular market in the pricefeed
|
||||||
|
// module.
|
||||||
|
message CurrentPrice {
|
||||||
|
string market_id = 1 [(gogoproto.customname) = "MarketID"];
|
||||||
|
string price = 2 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
35
proto/zgc/pricefeed/v1beta1/tx.proto
Normal file
35
proto/zgc/pricefeed/v1beta1/tx.proto
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package zgc.pricefeed.v1beta1;
|
||||||
|
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/0glabs/0g-chain/x/pricefeed/types";
|
||||||
|
option (gogoproto.equal_all) = true;
|
||||||
|
option (gogoproto.verbose_equal_all) = true;
|
||||||
|
|
||||||
|
// Msg defines the pricefeed Msg service.
|
||||||
|
service Msg {
|
||||||
|
// PostPrice defines a method for creating a new post price
|
||||||
|
rpc PostPrice(MsgPostPrice) returns (MsgPostPriceResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgPostPrice represents a method for creating a new post price
|
||||||
|
message MsgPostPrice {
|
||||||
|
option (gogoproto.goproto_getters) = false;
|
||||||
|
|
||||||
|
// address of client
|
||||||
|
string from = 1;
|
||||||
|
string market_id = 2 [(gogoproto.customname) = "MarketID"];
|
||||||
|
string price = 3 [
|
||||||
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
google.protobuf.Timestamp expiry = 4 [
|
||||||
|
(gogoproto.stdtime) = true,
|
||||||
|
(gogoproto.nullable) = false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgPostPriceResponse defines the Msg/PostPrice response type.
|
||||||
|
message MsgPostPriceResponse {}
|
20
x/bep3/abci.go
Normal file
20
x/bep3/abci.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package bep3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/telemetry"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeginBlocker on every block expires outdated atomic swaps and removes closed
|
||||||
|
// swap from long term storage (default storage time of 1 week)
|
||||||
|
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||||
|
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
|
||||||
|
|
||||||
|
k.UpdateTimeBasedSupplyLimits(ctx)
|
||||||
|
k.UpdateExpiredAtomicSwaps(ctx)
|
||||||
|
k.DeleteClosedAtomicSwapsFromLongtermStorage(ctx)
|
||||||
|
}
|
243
x/bep3/abci_test.go
Normal file
243
x/bep3/abci_test.go
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package bep3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
tmbytes "github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ABCITestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
keeper keeper.Keeper
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
swapIDs []tmbytes.HexBytes
|
||||||
|
randomNumbers []tmbytes.HexBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ABCITestSuite) SetupTest() {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
|
||||||
|
// Set up auth GenesisState
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(12)
|
||||||
|
coins := sdk.NewCoins(c("bnb", 10000000000), c("a0gi", 10000))
|
||||||
|
authGS := app.NewFundedGenStateWithSameCoins(tApp.AppCodec(), coins, addrs)
|
||||||
|
// Initialize test app
|
||||||
|
tApp.InitializeFromGenesisStates(authGS, NewBep3GenStateMulti(tApp.AppCodec(), addrs[11]))
|
||||||
|
|
||||||
|
suite.ctx = ctx
|
||||||
|
suite.app = tApp
|
||||||
|
suite.addrs = addrs
|
||||||
|
suite.ResetKeeper()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ABCITestSuite) ResetKeeper() {
|
||||||
|
suite.keeper = suite.app.GetBep3Keeper()
|
||||||
|
|
||||||
|
var swapIDs []tmbytes.HexBytes
|
||||||
|
var randomNumbers []tmbytes.HexBytes
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
// Set up atomic swap variables
|
||||||
|
expireHeight := types.DefaultMinBlockLock
|
||||||
|
amount := cs(c("bnb", int64(10000)))
|
||||||
|
timestamp := ts(i)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
// Create atomic swap and check err to confirm creation
|
||||||
|
err := suite.keeper.CreateAtomicSwap(suite.ctx, randomNumberHash, timestamp, expireHeight,
|
||||||
|
suite.addrs[11], suite.addrs[i], TestSenderOtherChain, TestRecipientOtherChain,
|
||||||
|
amount, true)
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
// Store swap's calculated ID and secret random number
|
||||||
|
swapID := types.CalculateSwapID(randomNumberHash, suite.addrs[11], TestSenderOtherChain)
|
||||||
|
swapIDs = append(swapIDs, swapID)
|
||||||
|
randomNumbers = append(randomNumbers, randomNumber[:])
|
||||||
|
}
|
||||||
|
suite.swapIDs = swapIDs
|
||||||
|
suite.randomNumbers = randomNumbers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ABCITestSuite) TestBeginBlocker_UpdateExpiredAtomicSwaps() {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
firstCtx sdk.Context
|
||||||
|
secondCtx sdk.Context
|
||||||
|
expectedStatus types.SwapStatus
|
||||||
|
expectInStorage bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "normal",
|
||||||
|
firstCtx: suite.ctx,
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 10),
|
||||||
|
expectedStatus: types.SWAP_STATUS_OPEN,
|
||||||
|
expectInStorage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "after expiration",
|
||||||
|
firstCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 410),
|
||||||
|
expectedStatus: types.SWAP_STATUS_EXPIRED,
|
||||||
|
expectInStorage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "after completion",
|
||||||
|
firstCtx: suite.ctx,
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 10),
|
||||||
|
expectedStatus: types.SWAP_STATUS_COMPLETED,
|
||||||
|
expectInStorage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "after deletion",
|
||||||
|
firstCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400 + int64(types.DefaultLongtermStorageDuration)),
|
||||||
|
expectedStatus: types.SWAP_STATUS_UNSPECIFIED,
|
||||||
|
expectInStorage: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
// Reset keeper and run the initial begin blocker
|
||||||
|
suite.ResetKeeper()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
bep3.BeginBlocker(tc.firstCtx, suite.keeper)
|
||||||
|
|
||||||
|
switch tc.expectedStatus {
|
||||||
|
case types.SWAP_STATUS_COMPLETED:
|
||||||
|
for i, swapID := range suite.swapIDs {
|
||||||
|
err := suite.keeper.ClaimAtomicSwap(tc.firstCtx, suite.addrs[5], swapID, suite.randomNumbers[i])
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
case types.SWAP_STATUS_UNSPECIFIED:
|
||||||
|
for _, swapID := range suite.swapIDs {
|
||||||
|
err := suite.keeper.RefundAtomicSwap(tc.firstCtx, suite.addrs[5], swapID)
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the second begin blocker
|
||||||
|
bep3.BeginBlocker(tc.secondCtx, suite.keeper)
|
||||||
|
|
||||||
|
// Check each swap's availibility and status
|
||||||
|
for _, swapID := range suite.swapIDs {
|
||||||
|
storedSwap, found := suite.keeper.GetAtomicSwap(tc.secondCtx, swapID)
|
||||||
|
if tc.expectInStorage {
|
||||||
|
suite.True(found)
|
||||||
|
} else {
|
||||||
|
suite.False(found)
|
||||||
|
}
|
||||||
|
suite.Equal(tc.expectedStatus, storedSwap.Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ABCITestSuite) TestBeginBlocker_DeleteClosedAtomicSwapsFromLongtermStorage() {
|
||||||
|
type Action int
|
||||||
|
const (
|
||||||
|
NULL Action = 0x00
|
||||||
|
Refund Action = 0x01
|
||||||
|
Claim Action = 0x02
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
firstCtx sdk.Context
|
||||||
|
action Action
|
||||||
|
secondCtx sdk.Context
|
||||||
|
expectInStorage bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no action with long storage duration",
|
||||||
|
firstCtx: suite.ctx,
|
||||||
|
action: NULL,
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + int64(types.DefaultLongtermStorageDuration)),
|
||||||
|
expectInStorage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "claim with short storage duration",
|
||||||
|
firstCtx: suite.ctx,
|
||||||
|
action: Claim,
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 5000),
|
||||||
|
expectInStorage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "claim with long storage duration",
|
||||||
|
firstCtx: suite.ctx,
|
||||||
|
action: Claim,
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + int64(types.DefaultLongtermStorageDuration)),
|
||||||
|
expectInStorage: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "refund with short storage duration",
|
||||||
|
firstCtx: suite.ctx,
|
||||||
|
action: Refund,
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 5000),
|
||||||
|
expectInStorage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "refund with long storage duration",
|
||||||
|
firstCtx: suite.ctx,
|
||||||
|
action: Refund,
|
||||||
|
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + int64(types.DefaultLongtermStorageDuration)),
|
||||||
|
expectInStorage: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
// Reset keeper and run the initial begin blocker
|
||||||
|
suite.ResetKeeper()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
bep3.BeginBlocker(tc.firstCtx, suite.keeper)
|
||||||
|
|
||||||
|
switch tc.action {
|
||||||
|
case Claim:
|
||||||
|
for i, swapID := range suite.swapIDs {
|
||||||
|
err := suite.keeper.ClaimAtomicSwap(tc.firstCtx, suite.addrs[5], swapID, suite.randomNumbers[i])
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
case Refund:
|
||||||
|
for _, swapID := range suite.swapIDs {
|
||||||
|
swap, _ := suite.keeper.GetAtomicSwap(tc.firstCtx, swapID)
|
||||||
|
refundCtx := suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + int64(swap.ExpireHeight))
|
||||||
|
bep3.BeginBlocker(refundCtx, suite.keeper)
|
||||||
|
err := suite.keeper.RefundAtomicSwap(refundCtx, suite.addrs[5], swapID)
|
||||||
|
suite.Nil(err)
|
||||||
|
// Add expire height to second ctx block height
|
||||||
|
tc.secondCtx = tc.secondCtx.WithBlockHeight(tc.secondCtx.BlockHeight() + int64(swap.ExpireHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the second begin blocker
|
||||||
|
bep3.BeginBlocker(tc.secondCtx, suite.keeper)
|
||||||
|
|
||||||
|
// Check each swap's availability and status
|
||||||
|
for _, swapID := range suite.swapIDs {
|
||||||
|
_, found := suite.keeper.GetAtomicSwap(tc.secondCtx, swapID)
|
||||||
|
if tc.expectInStorage {
|
||||||
|
suite.True(found)
|
||||||
|
} else {
|
||||||
|
suite.False(found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABCITestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ABCITestSuite))
|
||||||
|
}
|
336
x/bep3/client/cli/query.go
Normal file
336
x/bep3/client/cli/query.go
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Query atomic swaps flags
|
||||||
|
const (
|
||||||
|
flagInvolve = "involve"
|
||||||
|
flagExpiration = "expiration"
|
||||||
|
flagStatus = "status"
|
||||||
|
flagDirection = "direction"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetQueryCmd returns the cli query commands for this module
|
||||||
|
func GetQueryCmd(queryRoute string) *cobra.Command {
|
||||||
|
// Group bep3 queries under a subcommand
|
||||||
|
bep3QueryCmd := &cobra.Command{
|
||||||
|
Use: "bep3",
|
||||||
|
Short: "Querying commands for the bep3 module",
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
SuggestionsMinimumDistance: 2,
|
||||||
|
RunE: client.ValidateCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := []*cobra.Command{
|
||||||
|
QueryCalcSwapIDCmd(queryRoute),
|
||||||
|
QueryCalcRandomNumberHashCmd(queryRoute),
|
||||||
|
QueryGetAssetSupplyCmd(queryRoute),
|
||||||
|
QueryGetAssetSuppliesCmd(queryRoute),
|
||||||
|
QueryGetAtomicSwapCmd(queryRoute),
|
||||||
|
QueryGetAtomicSwapsCmd(queryRoute),
|
||||||
|
QueryParamsCmd(queryRoute),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
flags.AddQueryFlagsToCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
bep3QueryCmd.AddCommand(cmds...)
|
||||||
|
|
||||||
|
return bep3QueryCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryCalcRandomNumberHashCmd calculates the random number hash for a number and timestamp
|
||||||
|
func QueryCalcRandomNumberHashCmd(queryRoute string) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "calc-rnh [unix-timestamp]",
|
||||||
|
Short: "calculates an example random number hash from an optional timestamp",
|
||||||
|
Example: "bep3 calc-rnh now",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userTimestamp := "now"
|
||||||
|
if len(args) > 0 {
|
||||||
|
userTimestamp = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamp defaults to time.Now() unless it's explicitly set
|
||||||
|
var timestamp int64
|
||||||
|
if strings.Compare(userTimestamp, "now") == 0 {
|
||||||
|
timestamp = tmtime.Now().Unix()
|
||||||
|
} else {
|
||||||
|
userTimestamp, err := strconv.ParseInt(userTimestamp, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timestamp = userTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load hex-encoded cryptographically strong pseudo-random number
|
||||||
|
randomNumber, err := types.GenerateSecureRandomNumber()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber, timestamp)
|
||||||
|
|
||||||
|
// Prepare random number, timestamp, and hash for output
|
||||||
|
randomNumberStr := fmt.Sprintf("Random number: %s\n", hex.EncodeToString(randomNumber))
|
||||||
|
timestampStr := fmt.Sprintf("Timestamp: %d\n", timestamp)
|
||||||
|
randomNumberHashStr := fmt.Sprintf("Random number hash: %s", hex.EncodeToString(randomNumberHash))
|
||||||
|
output := []string{randomNumberStr, timestampStr, randomNumberHashStr}
|
||||||
|
return clientCtx.PrintObjectLegacy(strings.Join(output, ""))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryCalcSwapIDCmd calculates the swapID for a random number hash, sender, and sender other chain
|
||||||
|
func QueryCalcSwapIDCmd(queryRoute string) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "calc-swapid [random-number-hash] [sender] [sender-other-chain]",
|
||||||
|
Short: "calculate swap ID for the given random number hash, sender, and sender other chain",
|
||||||
|
Example: "bep3 calc-swapid 0677bd8a303dd981810f34d8e5cc6507f13b391899b84d3c1be6c6045a17d747 0g1l0xsq2z7gqd7yly0g40y5836g0appumark77ny bnb1ud3q90r98l3mhd87kswv3h8cgrymzeljct8qn7",
|
||||||
|
Args: cobra.MinimumNArgs(3),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse query params
|
||||||
|
randomNumberHash, err := hex.DecodeString(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sender, err := sdk.AccAddressFromBech32(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
senderOtherChain := args[2]
|
||||||
|
|
||||||
|
// Calculate swap ID and convert to human-readable string
|
||||||
|
swapID := types.CalculateSwapID(randomNumberHash, sender, senderOtherChain)
|
||||||
|
return clientCtx.PrintObjectLegacy(hex.EncodeToString(swapID))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryGetAssetSupplyCmd queries as asset's current in swap supply, active, supply, and supply limit
|
||||||
|
func QueryGetAssetSupplyCmd(queryRoute string) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "supply [denom]",
|
||||||
|
Short: "get information about an asset's supply",
|
||||||
|
Example: "bep3 supply bnb",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
|
||||||
|
res, err := queryClient.AssetSupply(context.Background(), &types.QueryAssetSupplyRequest{
|
||||||
|
Denom: args[0],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryGetAssetSuppliesCmd queries AssetSupplies in the store
|
||||||
|
func QueryGetAssetSuppliesCmd(queryRoute string) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "supplies",
|
||||||
|
Short: "get a list of all asset supplies",
|
||||||
|
Example: "bep3 supplies",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
|
||||||
|
res, err := queryClient.AssetSupplies(context.Background(), &types.QueryAssetSuppliesRequest{
|
||||||
|
// TODO: Pagination here?
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res.AssetSupplies) == 0 {
|
||||||
|
return fmt.Errorf("there are currently no asset supplies")
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryGetAtomicSwapCmd queries an AtomicSwap by swapID
|
||||||
|
func QueryGetAtomicSwapCmd(queryRoute string) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "swap [swap-id]",
|
||||||
|
Short: "get atomic swap information",
|
||||||
|
Example: "bep3 swap 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
|
||||||
|
res, err := queryClient.AtomicSwap(context.Background(), &types.QueryAtomicSwapRequest{
|
||||||
|
SwapId: args[0],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryGetAtomicSwapsCmd queries AtomicSwaps in the store
|
||||||
|
func QueryGetAtomicSwapsCmd(queryRoute string) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "swaps",
|
||||||
|
Short: "query atomic swaps with optional filters",
|
||||||
|
Long: strings.TrimSpace(`Query for all paginated atomic swaps that match optional filters:
|
||||||
|
Example:
|
||||||
|
$ kvcli q bep3 swaps --involve=0g1l0xsq2z7gqd7yly0g40y5836g0appumark77ny
|
||||||
|
$ kvcli q bep3 swaps --expiration=280
|
||||||
|
$ kvcli q bep3 swaps --status=(Open|Completed|Expired)
|
||||||
|
$ kvcli q bep3 swaps --direction=(Incoming|Outgoing)
|
||||||
|
$ kvcli q bep3 swaps --page=2 --limit=100
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
bechInvolveAddr, err := cmd.Flags().GetString(flagInvolve)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
strExpiration, err := cmd.Flags().GetString(flagExpiration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
strSwapStatus, err := cmd.Flags().GetString(flagStatus)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
strSwapDirection, err := cmd.Flags().GetString(flagDirection)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pageReq, err := client.ReadPageRequest(cmd.Flags())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := types.QueryAtomicSwapsRequest{
|
||||||
|
Pagination: pageReq,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bechInvolveAddr) != 0 {
|
||||||
|
involveAddr, err := sdk.AccAddressFromBech32(bechInvolveAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Involve = involveAddr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strExpiration) != 0 {
|
||||||
|
expiration, err := strconv.ParseUint(strExpiration, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Expiration = expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strSwapStatus) != 0 {
|
||||||
|
swapStatus := types.NewSwapStatusFromString(strSwapStatus)
|
||||||
|
if !swapStatus.IsValid() {
|
||||||
|
return fmt.Errorf("invalid swap status %s", strSwapStatus)
|
||||||
|
}
|
||||||
|
req.Status = swapStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strSwapDirection) != 0 {
|
||||||
|
swapDirection := types.NewSwapDirectionFromString(strSwapDirection)
|
||||||
|
if !swapDirection.IsValid() {
|
||||||
|
return fmt.Errorf("invalid swap direction %s", strSwapDirection)
|
||||||
|
}
|
||||||
|
req.Direction = swapDirection
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
|
||||||
|
res, err := queryClient.AtomicSwaps(context.Background(), &req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().String(flagInvolve, "", "(optional) filter by atomic swaps that involve an address")
|
||||||
|
cmd.Flags().String(flagExpiration, "", "(optional) filter by atomic swaps that expire before a block height")
|
||||||
|
cmd.Flags().String(flagStatus, "", "(optional) filter by atomic swap status, status: open/completed/expired")
|
||||||
|
cmd.Flags().String(flagDirection, "", "(optional) filter by atomic swap direction, direction: incoming/outgoing")
|
||||||
|
|
||||||
|
flags.AddPaginationFlagsToCmd(cmd, "swaps")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParamsCmd queries the bep3 module parameters
|
||||||
|
func QueryParamsCmd(queryRoute string) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "params",
|
||||||
|
Short: "get the bep3 module parameters",
|
||||||
|
Example: "bep3 params",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
|
||||||
|
res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientCtx.PrintProto(&res.Params)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
195
x/bep3/client/cli/tx.go
Normal file
195
x/bep3/client/cli/tx.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
|
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTxCmd returns the transaction commands for this module
|
||||||
|
func GetTxCmd() *cobra.Command {
|
||||||
|
bep3TxCmd := &cobra.Command{
|
||||||
|
Use: "bep3",
|
||||||
|
Short: "bep3 transactions subcommands",
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
SuggestionsMinimumDistance: 2,
|
||||||
|
RunE: client.ValidateCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := []*cobra.Command{
|
||||||
|
GetCmdCreateAtomicSwap(),
|
||||||
|
GetCmdClaimAtomicSwap(),
|
||||||
|
GetCmdRefundAtomicSwap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
flags.AddTxFlagsToCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
bep3TxCmd.AddCommand(cmds...)
|
||||||
|
|
||||||
|
return bep3TxCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCmdCreateAtomicSwap cli command for creating atomic swaps
|
||||||
|
func GetCmdCreateAtomicSwap() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "create [to] [recipient-other-chain] [sender-other-chain] [timestamp] [coins] [height-span]",
|
||||||
|
Short: "create a new atomic swap",
|
||||||
|
Example: fmt.Sprintf("%s tx %s create 0g1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7 bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7 now 100bnb 270 --from validator",
|
||||||
|
version.AppName, types.ModuleName),
|
||||||
|
Args: cobra.ExactArgs(6),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
from := clientCtx.GetFromAddress() // same as 0g-chain executor's deputy address
|
||||||
|
to, err := sdk.AccAddressFromBech32(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
recipientOtherChain := args[1] // same as the other executor's deputy address
|
||||||
|
senderOtherChain := args[2]
|
||||||
|
|
||||||
|
// Timestamp defaults to time.Now() unless it's explicitly set
|
||||||
|
var timestamp int64
|
||||||
|
if strings.Compare(args[3], "now") == 0 {
|
||||||
|
timestamp = tmtime.Now().Unix()
|
||||||
|
} else {
|
||||||
|
timestamp, err = strconv.ParseInt(args[3], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate cryptographically strong pseudo-random number
|
||||||
|
randomNumber, err := types.GenerateSecureRandomNumber()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber, timestamp)
|
||||||
|
|
||||||
|
// Print random number, timestamp, and hash to user's console
|
||||||
|
fmt.Printf("\nRandom number: %s\n", hex.EncodeToString(randomNumber))
|
||||||
|
fmt.Printf("Timestamp: %d\n", timestamp)
|
||||||
|
fmt.Printf("Random number hash: %s\n\n", hex.EncodeToString(randomNumberHash))
|
||||||
|
|
||||||
|
coins, err := sdk.ParseCoinsNormalized(args[4])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
heightSpan, err := strconv.ParseUint(args[5], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := types.NewMsgCreateAtomicSwap(
|
||||||
|
from.String(), to.String(), recipientOtherChain, senderOtherChain,
|
||||||
|
randomNumberHash, timestamp, coins, heightSpan,
|
||||||
|
)
|
||||||
|
|
||||||
|
err = msg.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCmdClaimAtomicSwap cli command for claiming an atomic swap
|
||||||
|
func GetCmdClaimAtomicSwap() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "claim [swap-id] [random-number]",
|
||||||
|
Short: "claim coins in an atomic swap using the secret number",
|
||||||
|
Example: fmt.Sprintf(
|
||||||
|
"%s tx %s claim 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af 56f13e6a5cd397447f8b5f8c82fdb5bbf56127db75269f5cc14e50acd8ac9a4c --from accA",
|
||||||
|
version.AppName, types.ModuleName,
|
||||||
|
),
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
from := clientCtx.GetFromAddress()
|
||||||
|
|
||||||
|
swapID, err := hex.DecodeString(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strings.TrimSpace(args[1])) == 0 {
|
||||||
|
return fmt.Errorf("random-number cannot be empty")
|
||||||
|
}
|
||||||
|
randomNumber, err := hex.DecodeString(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := types.NewMsgClaimAtomicSwap(from.String(), swapID, randomNumber)
|
||||||
|
|
||||||
|
err = msg.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCmdRefundAtomicSwap cli command for claiming an atomic swap
|
||||||
|
func GetCmdRefundAtomicSwap() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "refund [swap-id]",
|
||||||
|
Short: "refund the coins in an atomic swap",
|
||||||
|
Example: fmt.Sprintf(
|
||||||
|
"%s tx %s refund 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af --from accA",
|
||||||
|
version.AppName, types.ModuleName,
|
||||||
|
),
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
from := clientCtx.GetFromAddress()
|
||||||
|
|
||||||
|
swapID, err := hex.DecodeString(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := types.NewMsgRefundAtomicSwap(from.String(), swapID)
|
||||||
|
|
||||||
|
err = msg.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
141
x/bep3/genesis.go
Normal file
141
x/bep3/genesis.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package bep3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitGenesis initializes the store state from a genesis state.
|
||||||
|
func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, accountKeeper types.AccountKeeper, gs *types.GenesisState) {
|
||||||
|
// Check if the module account exists
|
||||||
|
moduleAcc := accountKeeper.GetModuleAccount(ctx, types.ModuleName)
|
||||||
|
if moduleAcc == nil {
|
||||||
|
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
|
||||||
|
}
|
||||||
|
|
||||||
|
hasBurnPermissions := false
|
||||||
|
hasMintPermissions := false
|
||||||
|
for _, perm := range moduleAcc.GetPermissions() {
|
||||||
|
if perm == authtypes.Burner {
|
||||||
|
hasBurnPermissions = true
|
||||||
|
}
|
||||||
|
if perm == authtypes.Minter {
|
||||||
|
hasMintPermissions = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasBurnPermissions {
|
||||||
|
panic(fmt.Sprintf("%s module account does not have burn permissions", types.ModuleName))
|
||||||
|
}
|
||||||
|
if !hasMintPermissions {
|
||||||
|
panic(fmt.Sprintf("%s module account does not have mint permissions", types.ModuleName))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gs.Validate(); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
keeper.SetPreviousBlockTime(ctx, gs.PreviousBlockTime)
|
||||||
|
|
||||||
|
keeper.SetParams(ctx, gs.Params)
|
||||||
|
for _, supply := range gs.Supplies {
|
||||||
|
keeper.SetAssetSupply(ctx, supply, supply.GetDenom())
|
||||||
|
}
|
||||||
|
|
||||||
|
var incomingSupplies sdk.Coins
|
||||||
|
var outgoingSupplies sdk.Coins
|
||||||
|
for _, swap := range gs.AtomicSwaps {
|
||||||
|
if swap.Validate() != nil {
|
||||||
|
panic(fmt.Sprintf("invalid swap %s", swap.GetSwapID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomic swap assets must be both supported and active
|
||||||
|
err := keeper.ValidateLiveAsset(ctx, swap.Amount[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("swap has invalid asset: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
keeper.SetAtomicSwap(ctx, swap)
|
||||||
|
|
||||||
|
// Add swap to block index or longterm storage based on swap.Status
|
||||||
|
// Increment incoming or outgoing supply based on swap.Direction
|
||||||
|
switch swap.Direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
switch swap.Status {
|
||||||
|
case types.SWAP_STATUS_OPEN:
|
||||||
|
// This index expires unclaimed swaps
|
||||||
|
keeper.InsertIntoByBlockIndex(ctx, swap)
|
||||||
|
incomingSupplies = incomingSupplies.Add(swap.Amount...)
|
||||||
|
case types.SWAP_STATUS_EXPIRED:
|
||||||
|
incomingSupplies = incomingSupplies.Add(swap.Amount...)
|
||||||
|
case types.SWAP_STATUS_COMPLETED:
|
||||||
|
// This index stores swaps until deletion
|
||||||
|
keeper.InsertIntoLongtermStorage(ctx, swap)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("swap %s has invalid status %s", swap.GetSwapID(), swap.Status.String()))
|
||||||
|
}
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
switch swap.Status {
|
||||||
|
case types.SWAP_STATUS_OPEN:
|
||||||
|
keeper.InsertIntoByBlockIndex(ctx, swap)
|
||||||
|
outgoingSupplies = outgoingSupplies.Add(swap.Amount...)
|
||||||
|
case types.SWAP_STATUS_EXPIRED:
|
||||||
|
outgoingSupplies = outgoingSupplies.Add(swap.Amount...)
|
||||||
|
case types.SWAP_STATUS_COMPLETED:
|
||||||
|
keeper.InsertIntoLongtermStorage(ctx, swap)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("swap %s has invalid status %s", swap.GetSwapID(), swap.Status.String()))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("swap %s has invalid direction %s", swap.GetSwapID(), swap.Direction.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset's given incoming/outgoing supply much match the amount of coins in incoming/outgoing atomic swaps
|
||||||
|
supplies := keeper.GetAllAssetSupplies(ctx)
|
||||||
|
for _, supply := range supplies {
|
||||||
|
incomingSupply := incomingSupplies.AmountOf(supply.GetDenom())
|
||||||
|
if !supply.IncomingSupply.Amount.Equal(incomingSupply) {
|
||||||
|
panic(fmt.Sprintf("asset's incoming supply %s does not match amount %s in incoming atomic swaps",
|
||||||
|
supply.IncomingSupply, incomingSupply))
|
||||||
|
}
|
||||||
|
outgoingSupply := outgoingSupplies.AmountOf(supply.GetDenom())
|
||||||
|
if !supply.OutgoingSupply.Amount.Equal(outgoingSupply) {
|
||||||
|
panic(fmt.Sprintf("asset's outgoing supply %s does not match amount %s in outgoing atomic swaps",
|
||||||
|
supply.OutgoingSupply, outgoingSupply))
|
||||||
|
}
|
||||||
|
limit, err := keeper.GetSupplyLimit(ctx, supply.GetDenom())
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("asset's supply limit not found: %s", err))
|
||||||
|
}
|
||||||
|
if supply.CurrentSupply.Amount.GT(limit.Limit) {
|
||||||
|
panic(fmt.Sprintf("asset's current supply %s is over the supply limit %s", supply.CurrentSupply, limit.Limit))
|
||||||
|
}
|
||||||
|
if supply.IncomingSupply.Amount.GT(limit.Limit) {
|
||||||
|
panic(fmt.Sprintf("asset's incoming supply %s is over the supply limit %s", supply.IncomingSupply, limit.Limit))
|
||||||
|
}
|
||||||
|
if supply.IncomingSupply.Amount.Add(supply.CurrentSupply.Amount).GT(limit.Limit) {
|
||||||
|
panic(fmt.Sprintf("asset's incoming supply + current supply %s is over the supply limit %s", supply.IncomingSupply.Add(supply.CurrentSupply), limit.Limit))
|
||||||
|
}
|
||||||
|
if supply.OutgoingSupply.Amount.GT(limit.Limit) {
|
||||||
|
panic(fmt.Sprintf("asset's outgoing supply %s is over the supply limit %s", supply.OutgoingSupply, limit.Limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportGenesis writes the current store values to a genesis file, which can be imported again with InitGenesis
|
||||||
|
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) (data types.GenesisState) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
swaps := k.GetAllAtomicSwaps(ctx)
|
||||||
|
supplies := k.GetAllAssetSupplies(ctx)
|
||||||
|
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||||
|
if !found {
|
||||||
|
previousBlockTime = types.DefaultPreviousBlockTime
|
||||||
|
}
|
||||||
|
return types.NewGenesisState(params, swaps, supplies, previousBlockTime)
|
||||||
|
}
|
391
x/bep3/genesis_test.go
Normal file
391
x/bep3/genesis_test.go
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
package bep3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"cosmossdk.io/math"
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenesisTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
keeper keeper.Keeper
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
suite.ctx = tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
suite.keeper = tApp.GetBep3Keeper()
|
||||||
|
suite.app = tApp
|
||||||
|
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
||||||
|
suite.addrs = addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) TestModulePermissionsCheck() {
|
||||||
|
cdc := suite.app.AppCodec()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
permissions []string
|
||||||
|
expectedPanic string
|
||||||
|
}{
|
||||||
|
{"no permissions", []string{}, "bep3 module account does not have burn permissions"},
|
||||||
|
{"mint permissions", []string{authtypes.Minter}, "bep3 module account does not have burn permissions"},
|
||||||
|
{"burn permissions", []string{authtypes.Burner}, "bep3 module account does not have mint permissions"},
|
||||||
|
{"burn and mint permissions", []string{authtypes.Burner, authtypes.Minter}, ""},
|
||||||
|
{"mint and burn permissions", []string{authtypes.Minter, authtypes.Burner}, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
suite.SetupTest()
|
||||||
|
authGenesis := authtypes.NewGenesisState(
|
||||||
|
authtypes.DefaultParams(),
|
||||||
|
authtypes.GenesisAccounts{authtypes.NewEmptyModuleAccount(types.ModuleName, tc.permissions...)},
|
||||||
|
)
|
||||||
|
bep3Genesis := types.DefaultGenesisState()
|
||||||
|
genState := app.GenesisState{
|
||||||
|
authtypes.ModuleName: cdc.MustMarshalJSON(authGenesis),
|
||||||
|
types.ModuleName: cdc.MustMarshalJSON(&bep3Genesis),
|
||||||
|
}
|
||||||
|
|
||||||
|
initApp := func() { suite.app.InitializeFromGenesisStates(genState) }
|
||||||
|
|
||||||
|
if tc.expectedPanic == "" {
|
||||||
|
suite.NotPanics(initApp)
|
||||||
|
} else {
|
||||||
|
suite.PanicsWithValue(tc.expectedPanic, initApp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) TestGenesisState() {
|
||||||
|
type GenState func() app.GenesisState
|
||||||
|
|
||||||
|
cdc := suite.app.AppCodec()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
genState GenState
|
||||||
|
expectPass bool
|
||||||
|
expectedErr interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
return NewBep3GenStateMulti(cdc, suite.addrs[0])
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "import atomic swaps and asset supplies",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||||
|
var swaps types.AtomicSwaps
|
||||||
|
var supplies types.AssetSupplies
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
swap, supply := loadSwapAndSupply(addrs[i], i)
|
||||||
|
swaps = append(swaps, swap)
|
||||||
|
supplies = append(supplies, supply)
|
||||||
|
}
|
||||||
|
gs.AtomicSwaps = swaps
|
||||||
|
gs.Supplies = supplies
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "0 deputy fees",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
gs.Params.AssetParams[0].FixedFee = sdk.ZeroInt()
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incoming supply doesn't match amount in incoming atomic swaps",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0]) // incoming supply is zero
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||||
|
swap, _ := loadSwapAndSupply(addrs[0], 0)
|
||||||
|
gs.AtomicSwaps = types.AtomicSwaps{swap}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "asset's incoming supply 0bnb does not match amount 50000 in incoming atomic swaps",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "current supply above limit",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
bnbSupplyLimit := math.ZeroInt()
|
||||||
|
for _, ap := range gs.Params.AssetParams {
|
||||||
|
if ap.Denom == "bnb" {
|
||||||
|
bnbSupplyLimit = ap.SupplyLimit.Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gs.Supplies = types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(
|
||||||
|
c("bnb", 0),
|
||||||
|
c("bnb", 0),
|
||||||
|
sdk.NewCoin("bnb", bnbSupplyLimit.Add(i(1))),
|
||||||
|
c("bnb", 0),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "asset's current supply 350000000000001bnb is over the supply limit 350000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incoming supply above limit",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
// Set up overlimit amount
|
||||||
|
bnbSupplyLimit := math.ZeroInt()
|
||||||
|
for _, ap := range gs.Params.AssetParams {
|
||||||
|
if ap.Denom == "bnb" {
|
||||||
|
bnbSupplyLimit = ap.SupplyLimit.Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overLimitAmount := bnbSupplyLimit.Add(i(1))
|
||||||
|
|
||||||
|
// Set up an atomic swap with amount equal to the currently asset supply
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||||
|
timestamp := ts(0)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
swap := types.NewAtomicSwap(cs(c("bnb", overLimitAmount.Int64())), randomNumberHash,
|
||||||
|
types.DefaultMinBlockLock, timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
|
||||||
|
TestRecipientOtherChain, 0, types.SWAP_STATUS_OPEN, true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
gs.AtomicSwaps = types.AtomicSwaps{swap}
|
||||||
|
|
||||||
|
// Set up asset supply with overlimit current supply
|
||||||
|
gs.Supplies = types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(
|
||||||
|
c("bnb", overLimitAmount.Int64()),
|
||||||
|
c("bnb", 0),
|
||||||
|
c("bnb", 0),
|
||||||
|
c("bnb", 0),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "asset's incoming supply 350000000000001bnb is over the supply limit 350000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incoming supply + current supply above limit",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
// Set up overlimit amount
|
||||||
|
bnbSupplyLimit := math.ZeroInt()
|
||||||
|
for _, ap := range gs.Params.AssetParams {
|
||||||
|
if ap.Denom == "bnb" {
|
||||||
|
bnbSupplyLimit = ap.SupplyLimit.Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
halfLimit := bnbSupplyLimit.Int64() / 2
|
||||||
|
overHalfLimit := halfLimit + 1
|
||||||
|
|
||||||
|
// Set up an atomic swap with amount equal to the currently asset supply
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||||
|
timestamp := ts(0)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
swap := types.NewAtomicSwap(cs(c("bnb", halfLimit)), randomNumberHash,
|
||||||
|
uint64(360), timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
|
||||||
|
TestRecipientOtherChain, 0, types.SWAP_STATUS_OPEN, true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
gs.AtomicSwaps = types.AtomicSwaps{swap}
|
||||||
|
|
||||||
|
// Set up asset supply with overlimit supply
|
||||||
|
gs.Supplies = types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(
|
||||||
|
c("bnb", halfLimit),
|
||||||
|
c("bnb", 0),
|
||||||
|
c("bnb", overHalfLimit),
|
||||||
|
c("bnb", 0),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "asset's incoming supply + current supply 350000000000001bnb is over the supply limit 350000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "outgoing supply above limit",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
// Set up overlimit amount
|
||||||
|
bnbSupplyLimit := math.ZeroInt()
|
||||||
|
for _, ap := range gs.Params.AssetParams {
|
||||||
|
if ap.Denom == "bnb" {
|
||||||
|
bnbSupplyLimit = ap.SupplyLimit.Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overLimitAmount := bnbSupplyLimit.Add(i(1))
|
||||||
|
|
||||||
|
// Set up an atomic swap with amount equal to the currently asset supply
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||||
|
timestamp := ts(0)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
swap := types.NewAtomicSwap(cs(c("bnb", overLimitAmount.Int64())), randomNumberHash,
|
||||||
|
types.DefaultMinBlockLock, timestamp, addrs[1], suite.addrs[0], TestSenderOtherChain,
|
||||||
|
TestRecipientOtherChain, 0, types.SWAP_STATUS_OPEN, true, types.SWAP_DIRECTION_OUTGOING)
|
||||||
|
gs.AtomicSwaps = types.AtomicSwaps{swap}
|
||||||
|
|
||||||
|
// Set up asset supply with overlimit outgoing supply
|
||||||
|
gs.Supplies = types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(
|
||||||
|
c("bnb", 0),
|
||||||
|
c("bnb", overLimitAmount.Int64()),
|
||||||
|
c("bnb", 0),
|
||||||
|
c("bnb", 0),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "asset's outgoing supply 350000000000001bnb is over the supply limit 350000000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "asset supply denom is not a supported asset",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
gs.Supplies = types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(
|
||||||
|
c("fake", 0),
|
||||||
|
c("fake", 0),
|
||||||
|
c("fake", 0),
|
||||||
|
c("fake", 0),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "asset's supply limit not found: fake: asset not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "atomic swap asset type is unsupported",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||||
|
timestamp := ts(0)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
swap := types.NewAtomicSwap(cs(c("fake", 500000)), randomNumberHash,
|
||||||
|
uint64(360), timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
|
||||||
|
TestRecipientOtherChain, 0, types.SWAP_STATUS_OPEN, true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
|
||||||
|
gs.AtomicSwaps = types.AtomicSwaps{swap}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "swap has invalid asset: fake: asset not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "atomic swap status is invalid",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||||
|
timestamp := ts(0)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
swap := types.NewAtomicSwap(cs(c("bnb", 5000)), randomNumberHash,
|
||||||
|
uint64(360), timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
|
||||||
|
TestRecipientOtherChain, 0, types.SWAP_STATUS_UNSPECIFIED, true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
|
||||||
|
gs.AtomicSwaps = types.AtomicSwaps{swap}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "failed to validate bep3 genesis state: invalid swap status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "minimum block lock cannot be > maximum block lock",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
gs.Params.AssetParams[0].MinBlockLock = 201
|
||||||
|
gs.Params.AssetParams[0].MaxBlockLock = 200
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "failed to validate bep3 genesis state: asset bnb has minimum block lock > maximum block lock 201 > 200",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty supported asset denom",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
gs.Params.AssetParams[0].Denom = ""
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "failed to validate bep3 genesis state: asset denom invalid: ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative supported asset limit",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
gs.Params.AssetParams[0].SupplyLimit.Limit = i(-100)
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "failed to validate bep3 genesis state: asset bnb has invalid (negative) supply limit: -100",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate supported asset denom",
|
||||||
|
genState: func() app.GenesisState {
|
||||||
|
gs := baseGenState(suite.addrs[0])
|
||||||
|
gs.Params.AssetParams[1].Denom = "bnb"
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&gs)}
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "failed to validate bep3 genesis state: asset bnb cannot have duplicate denom",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
suite.SetupTest()
|
||||||
|
gs := tc.genState()
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.NotPanics(func() {
|
||||||
|
suite.app.InitializeFromGenesisStates(gs)
|
||||||
|
}, tc.name)
|
||||||
|
} else {
|
||||||
|
suite.PanicsWithValue(tc.expectedErr, func() {
|
||||||
|
suite.app.InitializeFromGenesisStates(gs)
|
||||||
|
}, tc.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(GenesisTestSuite))
|
||||||
|
}
|
114
x/bep3/integration_test.go
Normal file
114
x/bep3/integration_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package bep3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TestSenderOtherChain = "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7"
|
||||||
|
TestRecipientOtherChain = "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7"
|
||||||
|
TestDeputy = "0g1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj"
|
||||||
|
TestUser = "0g1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
StandardSupplyLimit = i(100000000000)
|
||||||
|
DenomMap = map[int]string{0: "bnb", 1: "inc"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func i(in int64) sdkmath.Int { return sdkmath.NewInt(in) }
|
||||||
|
func d(de int64) sdk.Dec { return sdk.NewDec(de) }
|
||||||
|
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
||||||
|
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
||||||
|
func ts(minOffset int) int64 { return tmtime.Now().Add(time.Duration(minOffset) * time.Minute).Unix() }
|
||||||
|
|
||||||
|
func NewBep3GenStateMulti(cdc codec.JSONCodec, deputy sdk.AccAddress) app.GenesisState {
|
||||||
|
bep3Genesis := baseGenState(deputy)
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&bep3Genesis)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseGenState(deputy sdk.AccAddress) types.GenesisState {
|
||||||
|
bep3Genesis := types.GenesisState{
|
||||||
|
Params: types.Params{
|
||||||
|
AssetParams: types.AssetParams{
|
||||||
|
{
|
||||||
|
Denom: "bnb",
|
||||||
|
CoinID: 714,
|
||||||
|
SupplyLimit: types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(350000000000000),
|
||||||
|
TimeLimited: false,
|
||||||
|
TimeBasedLimit: sdk.ZeroInt(),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
},
|
||||||
|
Active: true,
|
||||||
|
DeputyAddress: deputy,
|
||||||
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
|
MinSwapAmount: sdk.OneInt(),
|
||||||
|
MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
MinBlockLock: types.DefaultMinBlockLock,
|
||||||
|
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Denom: "inc",
|
||||||
|
CoinID: 9999,
|
||||||
|
SupplyLimit: types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(100000000000),
|
||||||
|
TimeLimited: false,
|
||||||
|
TimeBasedLimit: sdk.ZeroInt(),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
},
|
||||||
|
Active: true,
|
||||||
|
DeputyAddress: deputy,
|
||||||
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
|
MinSwapAmount: sdk.OneInt(),
|
||||||
|
MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
MinBlockLock: types.DefaultMinBlockLock,
|
||||||
|
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Supplies: types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
time.Duration(0),
|
||||||
|
),
|
||||||
|
types.NewAssetSupply(
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
time.Duration(0),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
PreviousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
}
|
||||||
|
return bep3Genesis
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSwapAndSupply(addr sdk.AccAddress, index int) (types.AtomicSwap, types.AssetSupply) {
|
||||||
|
coin := c(DenomMap[index], 50000)
|
||||||
|
expireOffset := types.DefaultMinBlockLock // Default expire height + offet to match timestamp
|
||||||
|
timestamp := ts(index) // One minute apart
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
swap := types.NewAtomicSwap(cs(coin), randomNumberHash,
|
||||||
|
expireOffset, timestamp, addr, addr, TestSenderOtherChain,
|
||||||
|
TestRecipientOtherChain, 1, types.SWAP_STATUS_OPEN, true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
|
||||||
|
supply := types.NewAssetSupply(coin, c(coin.Denom, 0),
|
||||||
|
c(coin.Denom, 0), c(coin.Denom, 0), time.Duration(0))
|
||||||
|
|
||||||
|
return swap, supply
|
||||||
|
}
|
184
x/bep3/keeper/asset.go
Normal file
184
x/bep3/keeper/asset.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IncrementCurrentAssetSupply increments an asset's supply by the coin
|
||||||
|
func (k Keeper) IncrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrap(types.ErrAssetNotSupported, coin.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := k.GetSupplyLimit(ctx, coin.Denom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
supplyLimit := sdk.NewCoin(coin.Denom, limit.Limit)
|
||||||
|
|
||||||
|
// Resulting current supply must be under asset's limit
|
||||||
|
if supplyLimit.IsLT(supply.CurrentSupply.Add(coin)) {
|
||||||
|
return errorsmod.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, supply.CurrentSupply, supplyLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit.TimeLimited {
|
||||||
|
timeBasedSupplyLimit := sdk.NewCoin(coin.Denom, limit.TimeBasedLimit)
|
||||||
|
if timeBasedSupplyLimit.IsLT(supply.TimeLimitedCurrentSupply.Add(coin)) {
|
||||||
|
return errorsmod.Wrapf(types.ErrExceedsTimeBasedSupplyLimit, "increase %s, current time-based asset supply %s, limit %s", coin, supply.TimeLimitedCurrentSupply, timeBasedSupplyLimit)
|
||||||
|
}
|
||||||
|
supply.TimeLimitedCurrentSupply = supply.TimeLimitedCurrentSupply.Add(coin)
|
||||||
|
}
|
||||||
|
|
||||||
|
supply.CurrentSupply = supply.CurrentSupply.Add(coin)
|
||||||
|
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecrementCurrentAssetSupply decrement an asset's supply by the coin
|
||||||
|
func (k Keeper) DecrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrap(types.ErrAssetNotSupported, coin.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resulting current supply must be greater than or equal to 0
|
||||||
|
// Use sdkmath.Int instead of sdk.Coin to prevent panic if true
|
||||||
|
if supply.CurrentSupply.Amount.Sub(coin.Amount).IsNegative() {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidCurrentSupply, "decrease %s, asset supply %s", coin, supply.CurrentSupply)
|
||||||
|
}
|
||||||
|
|
||||||
|
supply.CurrentSupply = supply.CurrentSupply.Sub(coin)
|
||||||
|
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementIncomingAssetSupply increments an asset's incoming supply
|
||||||
|
func (k Keeper) IncrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrap(types.ErrAssetNotSupported, coin.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result of (current + incoming + amount) must be under asset's limit
|
||||||
|
totalSupply := supply.CurrentSupply.Add(supply.IncomingSupply)
|
||||||
|
|
||||||
|
limit, err := k.GetSupplyLimit(ctx, coin.Denom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
supplyLimit := sdk.NewCoin(coin.Denom, limit.Limit)
|
||||||
|
if supplyLimit.IsLT(totalSupply.Add(coin)) {
|
||||||
|
return errorsmod.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, totalSupply, supplyLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit.TimeLimited {
|
||||||
|
timeLimitedTotalSupply := supply.TimeLimitedCurrentSupply.Add(supply.IncomingSupply)
|
||||||
|
timeBasedSupplyLimit := sdk.NewCoin(coin.Denom, limit.TimeBasedLimit)
|
||||||
|
if timeBasedSupplyLimit.IsLT(timeLimitedTotalSupply.Add(coin)) {
|
||||||
|
return errorsmod.Wrapf(types.ErrExceedsTimeBasedSupplyLimit, "increase %s, time-based asset supply %s, limit %s", coin, supply.TimeLimitedCurrentSupply, timeBasedSupplyLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supply.IncomingSupply = supply.IncomingSupply.Add(coin)
|
||||||
|
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecrementIncomingAssetSupply decrements an asset's incoming supply
|
||||||
|
func (k Keeper) DecrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrap(types.ErrAssetNotSupported, coin.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resulting incoming supply must be greater than or equal to 0
|
||||||
|
// Use sdkmath.Int instead of sdk.Coin to prevent panic if true
|
||||||
|
if supply.IncomingSupply.Amount.Sub(coin.Amount).IsNegative() {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidIncomingSupply, "decrease %s, incoming supply %s", coin, supply.IncomingSupply)
|
||||||
|
}
|
||||||
|
|
||||||
|
supply.IncomingSupply = supply.IncomingSupply.Sub(coin)
|
||||||
|
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementOutgoingAssetSupply increments an asset's outgoing supply
|
||||||
|
func (k Keeper) IncrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrap(types.ErrAssetNotSupported, coin.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result of (outgoing + amount) must be less than current supply
|
||||||
|
if supply.CurrentSupply.IsLT(supply.OutgoingSupply.Add(coin)) {
|
||||||
|
return errorsmod.Wrapf(types.ErrExceedsAvailableSupply, "swap amount %s, available supply %s", coin,
|
||||||
|
supply.CurrentSupply.Amount.Sub(supply.OutgoingSupply.Amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
supply.OutgoingSupply = supply.OutgoingSupply.Add(coin)
|
||||||
|
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecrementOutgoingAssetSupply decrements an asset's outgoing supply
|
||||||
|
func (k Keeper) DecrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrap(types.ErrAssetNotSupported, coin.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resulting outgoing supply must be greater than or equal to 0
|
||||||
|
// Use sdkmath.Int instead of sdk.Coin to prevent panic if true
|
||||||
|
if supply.OutgoingSupply.Amount.Sub(coin.Amount).IsNegative() {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidOutgoingSupply, "decrease %s, outgoing supply %s", coin, supply.OutgoingSupply)
|
||||||
|
}
|
||||||
|
|
||||||
|
supply.OutgoingSupply = supply.OutgoingSupply.Sub(coin)
|
||||||
|
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNewAssetSupply creates a new AssetSupply in the store for the input denom
|
||||||
|
func (k Keeper) CreateNewAssetSupply(ctx sdk.Context, denom string) types.AssetSupply {
|
||||||
|
supply := types.NewAssetSupply(
|
||||||
|
sdk.NewCoin(denom, sdk.ZeroInt()), sdk.NewCoin(denom, sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin(denom, sdk.ZeroInt()), sdk.NewCoin(denom, sdk.ZeroInt()), time.Duration(0))
|
||||||
|
k.SetAssetSupply(ctx, supply, denom)
|
||||||
|
return supply
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTimeBasedSupplyLimits updates the time based supply for each asset, resetting it if the current time window has elapsed.
|
||||||
|
func (k Keeper) UpdateTimeBasedSupplyLimits(ctx sdk.Context) {
|
||||||
|
assets, found := k.GetAssets(ctx)
|
||||||
|
if !found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||||
|
if !found {
|
||||||
|
previousBlockTime = ctx.BlockTime()
|
||||||
|
k.SetPreviousBlockTime(ctx, previousBlockTime)
|
||||||
|
}
|
||||||
|
timeElapsed := ctx.BlockTime().Sub(previousBlockTime)
|
||||||
|
for _, asset := range assets {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, asset.Denom)
|
||||||
|
// if a new asset has been added by governance, create a new asset supply for it in the store
|
||||||
|
if !found {
|
||||||
|
supply = k.CreateNewAssetSupply(ctx, asset.Denom)
|
||||||
|
}
|
||||||
|
newTimeElapsed := supply.TimeElapsed + timeElapsed
|
||||||
|
if asset.SupplyLimit.TimeLimited && newTimeElapsed < asset.SupplyLimit.TimePeriod {
|
||||||
|
supply.TimeElapsed = newTimeElapsed
|
||||||
|
} else {
|
||||||
|
supply.TimeElapsed = time.Duration(0)
|
||||||
|
supply.TimeLimitedCurrentSupply = sdk.NewCoin(asset.Denom, sdk.ZeroInt())
|
||||||
|
}
|
||||||
|
k.SetAssetSupply(ctx, supply, asset.Denom)
|
||||||
|
}
|
||||||
|
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||||
|
}
|
703
x/bep3/keeper/asset_test.go
Normal file
703
x/bep3/keeper/asset_test.go
Normal file
@ -0,0 +1,703 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AssetTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
|
||||||
|
// Initialize test app and set context
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
|
||||||
|
// Initialize genesis state
|
||||||
|
deputy, _ := sdk.AccAddressFromBech32(TestDeputy)
|
||||||
|
tApp.InitializeFromGenesisStates(NewBep3GenStateMulti(tApp.AppCodec(), deputy))
|
||||||
|
|
||||||
|
keeper := tApp.GetBep3Keeper()
|
||||||
|
params := keeper.GetParams(ctx)
|
||||||
|
params.AssetParams[0].SupplyLimit.Limit = sdkmath.NewInt(50)
|
||||||
|
params.AssetParams[1].SupplyLimit.Limit = sdkmath.NewInt(100)
|
||||||
|
params.AssetParams[1].SupplyLimit.TimeBasedLimit = sdkmath.NewInt(15)
|
||||||
|
keeper.SetParams(ctx, params)
|
||||||
|
// Set asset supply with standard value for testing
|
||||||
|
supply := types.NewAssetSupply(c("bnb", 5), c("bnb", 5), c("bnb", 40), c("bnb", 0), time.Duration(0))
|
||||||
|
keeper.SetAssetSupply(ctx, supply, supply.IncomingSupply.Denom)
|
||||||
|
|
||||||
|
supply = types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0))
|
||||||
|
keeper.SetAssetSupply(ctx, supply, supply.IncomingSupply.Denom)
|
||||||
|
keeper.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||||
|
|
||||||
|
suite.app = tApp
|
||||||
|
suite.ctx = ctx
|
||||||
|
suite.keeper = keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestIncrementCurrentAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 5),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equal limit",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 10),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exceeds limit",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 11),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported asset",
|
||||||
|
args{
|
||||||
|
coin: c("xyz", 5),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.True(found)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(preSupply.CurrentSupply.Add(tc.args.coin), postSupply.CurrentSupply)
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Equal(preSupply.CurrentSupply, postSupply.CurrentSupply)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestIncrementTimeLimitedCurrentAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
expectedSupply types.AssetSupply
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("inc", 5),
|
||||||
|
expectedSupply: types.AssetSupply{
|
||||||
|
IncomingSupply: c("inc", 10),
|
||||||
|
OutgoingSupply: c("inc", 5),
|
||||||
|
CurrentSupply: c("inc", 10),
|
||||||
|
TimeLimitedCurrentSupply: c("inc", 5),
|
||||||
|
TimeElapsed: time.Duration(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"over limit",
|
||||||
|
args{
|
||||||
|
coin: c("inc", 16),
|
||||||
|
expectedSupply: types.AssetSupply{},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "asset supply over limit for current time period",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
supply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
suite.Require().Equal(tc.args.expectedSupply, supply)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestDecrementCurrentAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 30),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equal current",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 40),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exceeds current",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 41),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported asset",
|
||||||
|
args{
|
||||||
|
coin: c("xyz", 30),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
err := suite.keeper.DecrementCurrentAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.True(found)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.True(preSupply.CurrentSupply.Sub(tc.args.coin).IsEqual(postSupply.CurrentSupply))
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Equal(preSupply.CurrentSupply, postSupply.CurrentSupply)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestIncrementIncomingAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 2),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"incoming + current = limit",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 5),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"incoming + current > limit",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 6),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported asset",
|
||||||
|
args{
|
||||||
|
coin: c("xyz", 2),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
err := suite.keeper.IncrementIncomingAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.True(found)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(preSupply.IncomingSupply.Add(tc.args.coin), postSupply.IncomingSupply)
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Equal(preSupply.IncomingSupply, postSupply.IncomingSupply)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestIncrementTimeLimitedIncomingAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
expectedSupply types.AssetSupply
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("inc", 5),
|
||||||
|
expectedSupply: types.AssetSupply{
|
||||||
|
IncomingSupply: c("inc", 15),
|
||||||
|
OutgoingSupply: c("inc", 5),
|
||||||
|
CurrentSupply: c("inc", 5),
|
||||||
|
TimeLimitedCurrentSupply: c("inc", 0),
|
||||||
|
TimeElapsed: time.Duration(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"over limit",
|
||||||
|
args{
|
||||||
|
coin: c("inc", 6),
|
||||||
|
expectedSupply: types.AssetSupply{},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "asset supply over limit for current time period",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
err := suite.keeper.IncrementIncomingAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
supply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
suite.Require().Equal(tc.args.expectedSupply, supply)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestDecrementIncomingAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 4),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equal incoming",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 5),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exceeds incoming",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 6),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported asset",
|
||||||
|
args{
|
||||||
|
coin: c("xyz", 4),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
err := suite.keeper.DecrementIncomingAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.True(found)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.True(preSupply.IncomingSupply.Sub(tc.args.coin).IsEqual(postSupply.IncomingSupply))
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Equal(preSupply.IncomingSupply, postSupply.IncomingSupply)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestIncrementOutgoingAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 30),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outgoing + amount = current",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 35),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outoing + amount > current",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 36),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported asset",
|
||||||
|
args{
|
||||||
|
coin: c("xyz", 30),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
err := suite.keeper.IncrementOutgoingAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.True(found)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(preSupply.OutgoingSupply.Add(tc.args.coin), postSupply.OutgoingSupply)
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Equal(preSupply.OutgoingSupply, postSupply.OutgoingSupply)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestDecrementOutgoingAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 4),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equal outgoing",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 5),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exceeds outgoing",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 6),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported asset",
|
||||||
|
args{
|
||||||
|
coin: c("xyz", 4),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
err := suite.keeper.DecrementOutgoingAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.True(found)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.True(preSupply.OutgoingSupply.Sub(tc.args.coin).IsEqual(postSupply.OutgoingSupply))
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Equal(preSupply.OutgoingSupply, postSupply.OutgoingSupply)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestUpdateTimeBasedSupplyLimits() {
|
||||||
|
type args struct {
|
||||||
|
asset string
|
||||||
|
duration time.Duration
|
||||||
|
expectedSupply types.AssetSupply
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPanic bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"rate-limited increment time",
|
||||||
|
args{
|
||||||
|
asset: "inc",
|
||||||
|
duration: time.Second,
|
||||||
|
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Second),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPanic: false,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rate-limited increment time half",
|
||||||
|
args{
|
||||||
|
asset: "inc",
|
||||||
|
duration: time.Minute * 30,
|
||||||
|
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Minute*30),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPanic: false,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rate-limited period change",
|
||||||
|
args{
|
||||||
|
asset: "inc",
|
||||||
|
duration: time.Hour + time.Second,
|
||||||
|
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPanic: false,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rate-limited period change exact",
|
||||||
|
args{
|
||||||
|
asset: "inc",
|
||||||
|
duration: time.Hour,
|
||||||
|
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPanic: false,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rate-limited period change big",
|
||||||
|
args{
|
||||||
|
asset: "inc",
|
||||||
|
duration: time.Hour * 4,
|
||||||
|
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPanic: false,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non rate-limited increment time",
|
||||||
|
args{
|
||||||
|
asset: "bnb",
|
||||||
|
duration: time.Second,
|
||||||
|
expectedSupply: types.NewAssetSupply(c("bnb", 5), c("bnb", 5), c("bnb", 40), c("bnb", 0), time.Duration(0)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPanic: false,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"new asset increment time",
|
||||||
|
args{
|
||||||
|
asset: "lol",
|
||||||
|
duration: time.Second,
|
||||||
|
expectedSupply: types.NewAssetSupply(c("lol", 0), c("lol", 0), c("lol", 0), c("lol", 0), time.Second),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPanic: false,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
deputy, _ := sdk.AccAddressFromBech32(TestDeputy)
|
||||||
|
newParams := types.Params{
|
||||||
|
AssetParams: types.AssetParams{
|
||||||
|
{
|
||||||
|
Denom: "bnb",
|
||||||
|
CoinID: 714,
|
||||||
|
SupplyLimit: types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(350000000000000),
|
||||||
|
TimeLimited: false,
|
||||||
|
TimeBasedLimit: sdk.ZeroInt(),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
},
|
||||||
|
Active: true,
|
||||||
|
DeputyAddress: deputy,
|
||||||
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
|
MinSwapAmount: sdk.OneInt(),
|
||||||
|
MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
MinBlockLock: types.DefaultMinBlockLock,
|
||||||
|
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Denom: "inc",
|
||||||
|
CoinID: 9999,
|
||||||
|
SupplyLimit: types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(100),
|
||||||
|
TimeLimited: true,
|
||||||
|
TimeBasedLimit: sdkmath.NewInt(10),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
},
|
||||||
|
Active: false,
|
||||||
|
DeputyAddress: deputy,
|
||||||
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
|
MinSwapAmount: sdk.OneInt(),
|
||||||
|
MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
MinBlockLock: types.DefaultMinBlockLock,
|
||||||
|
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Denom: "lol",
|
||||||
|
CoinID: 9999,
|
||||||
|
SupplyLimit: types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(100),
|
||||||
|
TimeLimited: true,
|
||||||
|
TimeBasedLimit: sdkmath.NewInt(10),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
},
|
||||||
|
Active: false,
|
||||||
|
DeputyAddress: deputy,
|
||||||
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
|
MinSwapAmount: sdk.OneInt(),
|
||||||
|
MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
MinBlockLock: types.DefaultMinBlockLock,
|
||||||
|
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.keeper.SetParams(suite.ctx, newParams)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(tc.args.duration))
|
||||||
|
suite.NotPanics(
|
||||||
|
func() {
|
||||||
|
suite.keeper.UpdateTimeBasedSupplyLimits(suite.ctx)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if !tc.errArgs.expectPanic {
|
||||||
|
supply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.asset)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(tc.args.expectedSupply, supply)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssetTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(AssetTestSuite))
|
||||||
|
}
|
181
x/bep3/keeper/grpc_query.go
Normal file
181
x/bep3/keeper/grpc_query.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type queryServer struct {
|
||||||
|
keeper Keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueryServerImpl creates a new server for handling gRPC queries.
|
||||||
|
func NewQueryServerImpl(k Keeper) types.QueryServer {
|
||||||
|
return &queryServer{keeper: k}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.QueryServer = queryServer{}
|
||||||
|
|
||||||
|
// Params queries module params
|
||||||
|
func (s queryServer) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
|
params := s.keeper.GetParams(sdkCtx)
|
||||||
|
|
||||||
|
return &types.QueryParamsResponse{Params: params}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupply queries info about an asset's supply
|
||||||
|
func (s queryServer) AssetSupply(ctx context.Context, req *types.QueryAssetSupplyRequest) (*types.QueryAssetSupplyResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
|
assetSupply, ok := s.keeper.GetAssetSupply(sdkCtx, req.Denom)
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "denom not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.QueryAssetSupplyResponse{AssetSupply: mapAssetSupplyToResponse(assetSupply)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupplies queries a list of asset supplies
|
||||||
|
func (s queryServer) AssetSupplies(ctx context.Context, req *types.QueryAssetSuppliesRequest) (*types.QueryAssetSuppliesResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
|
|
||||||
|
var queryResults []types.AssetSupplyResponse
|
||||||
|
s.keeper.IterateAssetSupplies(sdkCtx, func(assetSupply types.AssetSupply) bool {
|
||||||
|
queryResults = append(queryResults, mapAssetSupplyToResponse(assetSupply))
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return &types.QueryAssetSuppliesResponse{
|
||||||
|
AssetSupplies: queryResults,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtomicSwap queries info about an atomic swap
|
||||||
|
func (s queryServer) AtomicSwap(ctx context.Context, req *types.QueryAtomicSwapRequest) (*types.QueryAtomicSwapResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
swapId, err := hex.DecodeString(req.SwapId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "invalid atomic swap id")
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
|
atomicSwap, ok := s.keeper.GetAtomicSwap(sdkCtx, swapId)
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "invalid atomic swap")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.QueryAtomicSwapResponse{
|
||||||
|
AtomicSwap: mapAtomicSwapToResponse(atomicSwap),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtomicSwaps queries a list of atomic swaps
|
||||||
|
func (s queryServer) AtomicSwaps(ctx context.Context, req *types.QueryAtomicSwapsRequest) (*types.QueryAtomicSwapsResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
|
store := prefix.NewStore(sdkCtx.KVStore(s.keeper.key), types.AtomicSwapKeyPrefix)
|
||||||
|
|
||||||
|
var queryResults []types.AtomicSwapResponse
|
||||||
|
pageRes, err := query.FilteredPaginate(store, req.Pagination, func(_, value []byte, shouldAccumulate bool) (bool, error) {
|
||||||
|
var atomicSwap types.AtomicSwap
|
||||||
|
err := s.keeper.cdc.Unmarshal(value, &atomicSwap)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Involve) > 0 {
|
||||||
|
if atomicSwap.Sender.String() != req.Involve && atomicSwap.Recipient.String() != req.Involve {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match expiration block limit (if supplied)
|
||||||
|
if req.Expiration > 0 {
|
||||||
|
if atomicSwap.ExpireHeight > req.Expiration {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match status (if supplied/valid)
|
||||||
|
if req.Status.IsValid() {
|
||||||
|
if atomicSwap.Status != req.Status {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match direction (if supplied/valid)
|
||||||
|
if req.Direction.IsValid() {
|
||||||
|
if atomicSwap.Direction != req.Direction {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldAccumulate {
|
||||||
|
queryResults = append(queryResults, mapAtomicSwapToResponse(atomicSwap))
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "paginate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.QueryAtomicSwapsResponse{
|
||||||
|
AtomicSwaps: queryResults,
|
||||||
|
Pagination: pageRes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapAssetSupplyToResponse(assetSupply types.AssetSupply) types.AssetSupplyResponse {
|
||||||
|
return types.AssetSupplyResponse{
|
||||||
|
IncomingSupply: assetSupply.IncomingSupply,
|
||||||
|
OutgoingSupply: assetSupply.OutgoingSupply,
|
||||||
|
CurrentSupply: assetSupply.CurrentSupply,
|
||||||
|
TimeLimitedCurrentSupply: assetSupply.TimeLimitedCurrentSupply,
|
||||||
|
TimeElapsed: assetSupply.TimeElapsed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapAtomicSwapToResponse(atomicSwap types.AtomicSwap) types.AtomicSwapResponse {
|
||||||
|
return types.AtomicSwapResponse{
|
||||||
|
Id: atomicSwap.GetSwapID().String(),
|
||||||
|
Amount: atomicSwap.Amount,
|
||||||
|
RandomNumberHash: atomicSwap.RandomNumberHash.String(),
|
||||||
|
ExpireHeight: atomicSwap.ExpireHeight,
|
||||||
|
Timestamp: atomicSwap.Timestamp,
|
||||||
|
Sender: atomicSwap.Sender.String(),
|
||||||
|
Recipient: atomicSwap.Recipient.String(),
|
||||||
|
SenderOtherChain: atomicSwap.SenderOtherChain,
|
||||||
|
RecipientOtherChain: atomicSwap.RecipientOtherChain,
|
||||||
|
ClosedBlock: atomicSwap.ClosedBlock,
|
||||||
|
Status: atomicSwap.Status,
|
||||||
|
CrossChain: atomicSwap.CrossChain,
|
||||||
|
Direction: atomicSwap.Direction,
|
||||||
|
}
|
||||||
|
}
|
119
x/bep3/keeper/integration_test.go
Normal file
119
x/bep3/keeper/integration_test.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
|
||||||
|
"github.com/cometbft/cometbft/crypto"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TestSenderOtherChain = "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7"
|
||||||
|
TestRecipientOtherChain = "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7"
|
||||||
|
TestDeputy = "0g1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DenomMap = map[int]string{0: "btc", 1: "eth", 2: "bnb", 3: "xrp", 4: "dai"}
|
||||||
|
TestUser1 = sdk.AccAddress(crypto.AddressHash([]byte("0gTestUser1")))
|
||||||
|
TestUser2 = sdk.AccAddress(crypto.AddressHash([]byte("0gTestUser2")))
|
||||||
|
)
|
||||||
|
|
||||||
|
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
||||||
|
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
||||||
|
func ts(minOffset int) int64 { return tmtime.Now().Add(time.Duration(minOffset) * time.Minute).Unix() }
|
||||||
|
|
||||||
|
func NewAuthGenStateFromAccs(cdc codec.JSONCodec, accounts ...authtypes.GenesisAccount) app.GenesisState {
|
||||||
|
authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), accounts)
|
||||||
|
return app.GenesisState{authtypes.ModuleName: cdc.MustMarshalJSON(authGenesis)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBep3GenStateMulti(cdc codec.JSONCodec, deputyAddress sdk.AccAddress) app.GenesisState {
|
||||||
|
bep3Genesis := types.GenesisState{
|
||||||
|
Params: types.Params{
|
||||||
|
AssetParams: types.AssetParams{
|
||||||
|
{
|
||||||
|
Denom: "bnb",
|
||||||
|
CoinID: 714,
|
||||||
|
SupplyLimit: types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(350000000000000),
|
||||||
|
TimeLimited: false,
|
||||||
|
TimeBasedLimit: sdk.ZeroInt(),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
},
|
||||||
|
Active: true,
|
||||||
|
DeputyAddress: deputyAddress,
|
||||||
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
|
MinSwapAmount: sdk.OneInt(),
|
||||||
|
MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
MinBlockLock: types.DefaultMinBlockLock,
|
||||||
|
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Denom: "inc",
|
||||||
|
CoinID: 9999,
|
||||||
|
SupplyLimit: types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(100000000000000),
|
||||||
|
TimeLimited: true,
|
||||||
|
TimeBasedLimit: sdkmath.NewInt(50000000000),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
},
|
||||||
|
Active: false,
|
||||||
|
DeputyAddress: deputyAddress,
|
||||||
|
FixedFee: sdkmath.NewInt(1000),
|
||||||
|
MinSwapAmount: sdk.OneInt(),
|
||||||
|
MaxSwapAmount: sdkmath.NewInt(100000000000),
|
||||||
|
MinBlockLock: types.DefaultMinBlockLock,
|
||||||
|
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Supplies: types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||||
|
time.Duration(0),
|
||||||
|
),
|
||||||
|
types.NewAssetSupply(
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||||
|
time.Duration(0),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
PreviousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
}
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&bep3Genesis)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func atomicSwaps(ctx sdk.Context, count int) types.AtomicSwaps {
|
||||||
|
var swaps types.AtomicSwaps
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
swap := atomicSwap(ctx, i)
|
||||||
|
swaps = append(swaps, swap)
|
||||||
|
}
|
||||||
|
return swaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func atomicSwap(ctx sdk.Context, index int) types.AtomicSwap {
|
||||||
|
expireOffset := uint64(200) // Default expire height + offet to match timestamp
|
||||||
|
timestamp := ts(index) // One minute apart
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
return types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash,
|
||||||
|
uint64(ctx.BlockHeight())+expireOffset, timestamp, TestUser1, TestUser2,
|
||||||
|
TestSenderOtherChain, TestRecipientOtherChain, 0, types.SWAP_STATUS_OPEN, true,
|
||||||
|
types.SWAP_DIRECTION_INCOMING)
|
||||||
|
}
|
251
x/bep3/keeper/keeper.go
Normal file
251
x/bep3/keeper/keeper.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cometbft/cometbft/libs/log"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keeper of the bep3 store
|
||||||
|
type Keeper struct {
|
||||||
|
key storetypes.StoreKey
|
||||||
|
cdc codec.Codec
|
||||||
|
paramSubspace paramtypes.Subspace
|
||||||
|
bankKeeper types.BankKeeper
|
||||||
|
accountKeeper types.AccountKeeper
|
||||||
|
Maccs map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeeper creates a bep3 keeper
|
||||||
|
func NewKeeper(cdc codec.Codec, key storetypes.StoreKey, sk types.BankKeeper, ak types.AccountKeeper,
|
||||||
|
paramstore paramtypes.Subspace, maccs map[string]bool,
|
||||||
|
) Keeper {
|
||||||
|
if !paramstore.HasKeyTable() {
|
||||||
|
paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
|
||||||
|
}
|
||||||
|
|
||||||
|
keeper := Keeper{
|
||||||
|
key: key,
|
||||||
|
cdc: cdc,
|
||||||
|
paramSubspace: paramstore,
|
||||||
|
bankKeeper: sk,
|
||||||
|
accountKeeper: ak,
|
||||||
|
Maccs: maccs,
|
||||||
|
}
|
||||||
|
return keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger returns a module-specific logger.
|
||||||
|
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
||||||
|
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Atomic Swaps
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// SetAtomicSwap puts the AtomicSwap into the store, and updates any indexes.
|
||||||
|
func (k Keeper) SetAtomicSwap(ctx sdk.Context, atomicSwap types.AtomicSwap) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
|
||||||
|
bz := k.cdc.MustMarshal(&atomicSwap)
|
||||||
|
store.Set(atomicSwap.GetSwapID(), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAtomicSwap gets an AtomicSwap from the store.
|
||||||
|
func (k Keeper) GetAtomicSwap(ctx sdk.Context, swapID []byte) (types.AtomicSwap, bool) {
|
||||||
|
var atomicSwap types.AtomicSwap
|
||||||
|
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
|
||||||
|
bz := store.Get(swapID)
|
||||||
|
if bz == nil {
|
||||||
|
return atomicSwap, false
|
||||||
|
}
|
||||||
|
|
||||||
|
k.cdc.MustUnmarshal(bz, &atomicSwap)
|
||||||
|
return atomicSwap, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAtomicSwap removes an AtomicSwap from the AtomicSwapKeyPrefix.
|
||||||
|
func (k Keeper) RemoveAtomicSwap(ctx sdk.Context, swapID []byte) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
|
||||||
|
store.Delete(swapID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateAtomicSwaps provides an iterator over all stored AtomicSwaps.
|
||||||
|
// For each AtomicSwap, cb will be called. If cb returns true, the iterator will close and stop.
|
||||||
|
func (k Keeper) IterateAtomicSwaps(ctx sdk.Context, cb func(atomicSwap types.AtomicSwap) (stop bool)) {
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var atomicSwap types.AtomicSwap
|
||||||
|
k.cdc.MustUnmarshal(iterator.Value(), &atomicSwap)
|
||||||
|
|
||||||
|
if cb(atomicSwap) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllAtomicSwaps returns all AtomicSwaps from the store
|
||||||
|
func (k Keeper) GetAllAtomicSwaps(ctx sdk.Context) (atomicSwaps types.AtomicSwaps) {
|
||||||
|
k.IterateAtomicSwaps(ctx, func(atomicSwap types.AtomicSwap) bool {
|
||||||
|
atomicSwaps = append(atomicSwaps, atomicSwap)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Atomic Swap Block Index
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// InsertIntoByBlockIndex adds a swap ID and expiration time into the byBlock index.
|
||||||
|
func (k Keeper) InsertIntoByBlockIndex(ctx sdk.Context, atomicSwap types.AtomicSwap) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapByBlockPrefix)
|
||||||
|
store.Set(types.GetAtomicSwapByHeightKey(atomicSwap.ExpireHeight, atomicSwap.GetSwapID()), atomicSwap.GetSwapID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFromByBlockIndex removes an AtomicSwap from the byBlock index.
|
||||||
|
func (k Keeper) RemoveFromByBlockIndex(ctx sdk.Context, atomicSwap types.AtomicSwap) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapByBlockPrefix)
|
||||||
|
store.Delete(types.GetAtomicSwapByHeightKey(atomicSwap.ExpireHeight, atomicSwap.GetSwapID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateAtomicSwapsByBlock provides an iterator over AtomicSwaps ordered by AtomicSwap expiration block
|
||||||
|
// For each AtomicSwap cb will be called. If cb returns true the iterator will close and stop.
|
||||||
|
func (k Keeper) IterateAtomicSwapsByBlock(ctx sdk.Context, inclusiveCutoffTime uint64, cb func(swapID []byte) (stop bool)) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapByBlockPrefix)
|
||||||
|
iterator := store.Iterator(
|
||||||
|
nil, // start at the very start of the prefix store
|
||||||
|
sdk.PrefixEndBytes(sdk.Uint64ToBigEndian(inclusiveCutoffTime)), // end of range
|
||||||
|
)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
|
||||||
|
id := iterator.Value()
|
||||||
|
|
||||||
|
if cb(id) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Atomic Swap Longterm Storage Index
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// InsertIntoLongtermStorage adds a swap ID and deletion time into the longterm storage index.
|
||||||
|
// Completed swaps are stored for 1 week.
|
||||||
|
func (k Keeper) InsertIntoLongtermStorage(ctx sdk.Context, atomicSwap types.AtomicSwap) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapLongtermStoragePrefix)
|
||||||
|
deletionHeight := uint64(atomicSwap.ClosedBlock) + types.DefaultLongtermStorageDuration
|
||||||
|
store.Set(types.GetAtomicSwapByHeightKey(deletionHeight, atomicSwap.GetSwapID()), atomicSwap.GetSwapID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFromLongtermStorage removes a swap from the into the longterm storage index
|
||||||
|
func (k Keeper) RemoveFromLongtermStorage(ctx sdk.Context, atomicSwap types.AtomicSwap) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapLongtermStoragePrefix)
|
||||||
|
deletionHeight := uint64(atomicSwap.ClosedBlock) + types.DefaultLongtermStorageDuration
|
||||||
|
store.Delete(types.GetAtomicSwapByHeightKey(deletionHeight, atomicSwap.GetSwapID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateAtomicSwapsLongtermStorage provides an iterator over AtomicSwaps ordered by deletion height.
|
||||||
|
// For each AtomicSwap cb will be called. If cb returns true the iterator will close and stop.
|
||||||
|
func (k Keeper) IterateAtomicSwapsLongtermStorage(ctx sdk.Context, inclusiveCutoffTime uint64,
|
||||||
|
cb func(swapID []byte) (stop bool),
|
||||||
|
) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapLongtermStoragePrefix)
|
||||||
|
iterator := store.Iterator(
|
||||||
|
nil, // start at the very start of the prefix store
|
||||||
|
sdk.PrefixEndBytes(sdk.Uint64ToBigEndian(inclusiveCutoffTime)), // end of range
|
||||||
|
)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
|
||||||
|
id := iterator.Value()
|
||||||
|
|
||||||
|
if cb(id) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Asset Supplies
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// GetAssetSupply gets an asset's current supply from the store.
|
||||||
|
func (k Keeper) GetAssetSupply(ctx sdk.Context, denom string) (types.AssetSupply, bool) {
|
||||||
|
var assetSupply types.AssetSupply
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AssetSupplyPrefix)
|
||||||
|
bz := store.Get([]byte(denom))
|
||||||
|
if bz == nil {
|
||||||
|
return types.AssetSupply{}, false
|
||||||
|
}
|
||||||
|
k.cdc.MustUnmarshal(bz, &assetSupply)
|
||||||
|
return assetSupply, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetSupply updates an asset's supply
|
||||||
|
func (k Keeper) SetAssetSupply(ctx sdk.Context, supply types.AssetSupply, denom string) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AssetSupplyPrefix)
|
||||||
|
store.Set([]byte(denom), k.cdc.MustMarshal(&supply))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateAssetSupplies provides an iterator over all stored AssetSupplies.
|
||||||
|
func (k Keeper) IterateAssetSupplies(ctx sdk.Context, cb func(supply types.AssetSupply) (stop bool)) {
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.AssetSupplyPrefix)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var supply types.AssetSupply
|
||||||
|
k.cdc.MustUnmarshal(iterator.Value(), &supply)
|
||||||
|
|
||||||
|
if cb(supply) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllAssetSupplies returns all asset supplies from the store
|
||||||
|
func (k Keeper) GetAllAssetSupplies(ctx sdk.Context) (supplies types.AssetSupplies) {
|
||||||
|
k.IterateAssetSupplies(ctx, func(supply types.AssetSupply) bool {
|
||||||
|
supplies = append(supplies, supply)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPreviousBlockTime get the blocktime for the previous block
|
||||||
|
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||||
|
b := store.Get(types.PreviousBlockTimeKey)
|
||||||
|
if b == nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
if err := blockTime.UnmarshalBinary(b); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return blockTime, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPreviousBlockTime set the time of the previous block
|
||||||
|
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||||
|
b, err := blockTime.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
store.Set(types.PreviousBlockTimeKey, b)
|
||||||
|
}
|
340
x/bep3/keeper/keeper_test.go
Normal file
340
x/bep3/keeper/keeper_test.go
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const LongtermStorageDuration = 86400
|
||||||
|
|
||||||
|
type KeeperTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
suite.ResetChain()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) ResetChain() {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
keeper := tApp.GetBep3Keeper()
|
||||||
|
|
||||||
|
suite.app = tApp
|
||||||
|
suite.ctx = ctx
|
||||||
|
suite.keeper = keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestGetSetAtomicSwap() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set new atomic swap
|
||||||
|
atomicSwap := atomicSwap(suite.ctx, 1)
|
||||||
|
suite.keeper.SetAtomicSwap(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Check atomic swap in store
|
||||||
|
s, found := suite.keeper.GetAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(atomicSwap, s)
|
||||||
|
|
||||||
|
// Check fake atomic swap not in store
|
||||||
|
fakeSwapID := types.CalculateSwapID(atomicSwap.RandomNumberHash, TestUser2, "otheraddress")
|
||||||
|
_, found = suite.keeper.GetAtomicSwap(suite.ctx, fakeSwapID)
|
||||||
|
suite.False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestRemoveAtomicSwap() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set new atomic swap
|
||||||
|
atomicSwap := atomicSwap(suite.ctx, 1)
|
||||||
|
suite.keeper.SetAtomicSwap(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Check atomic swap in store
|
||||||
|
s, found := suite.keeper.GetAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(atomicSwap, s)
|
||||||
|
|
||||||
|
suite.keeper.RemoveAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
|
||||||
|
|
||||||
|
// Check atomic swap not in store
|
||||||
|
_, found = suite.keeper.GetAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
|
||||||
|
suite.False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestIterateAtomicSwaps() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set atomic swaps
|
||||||
|
atomicSwaps := atomicSwaps(suite.ctx, 4)
|
||||||
|
for _, s := range atomicSwaps {
|
||||||
|
suite.keeper.SetAtomicSwap(suite.ctx, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read each atomic swap from the store
|
||||||
|
var readAtomicSwaps types.AtomicSwaps
|
||||||
|
suite.keeper.IterateAtomicSwaps(suite.ctx, func(a types.AtomicSwap) bool {
|
||||||
|
readAtomicSwaps = append(readAtomicSwaps, a)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check expected values
|
||||||
|
suite.Equal(len(atomicSwaps), len(readAtomicSwaps))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestGetAllAtomicSwaps() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set atomic swaps
|
||||||
|
atomicSwaps := atomicSwaps(suite.ctx, 4)
|
||||||
|
for _, s := range atomicSwaps {
|
||||||
|
suite.keeper.SetAtomicSwap(suite.ctx, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get and check atomic swaps
|
||||||
|
res := suite.keeper.GetAllAtomicSwaps(suite.ctx)
|
||||||
|
suite.Equal(4, len(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestInsertIntoByBlockIndex() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set new atomic swap in by block index
|
||||||
|
atomicSwap := atomicSwap(suite.ctx, 1)
|
||||||
|
suite.keeper.InsertIntoByBlockIndex(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Block index lacks getter methods, must use iteration to get count of swaps in store
|
||||||
|
var swapIDs [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, atomicSwap.ExpireHeight+1, func(id []byte) bool {
|
||||||
|
swapIDs = append(swapIDs, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
suite.Equal(len(swapIDs), 1)
|
||||||
|
|
||||||
|
// Marshal the expected swapID
|
||||||
|
cdc := suite.app.LegacyAmino()
|
||||||
|
res, _ := cdc.Amino.MarshalBinaryBare(atomicSwap.GetSwapID())
|
||||||
|
expectedSwapID := res[1:]
|
||||||
|
|
||||||
|
suite.Equal(expectedSwapID, swapIDs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestRemoveFromByBlockIndex() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set new atomic swap in by block index
|
||||||
|
atomicSwap := atomicSwap(suite.ctx, 1)
|
||||||
|
suite.keeper.InsertIntoByBlockIndex(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Check stored data in block index
|
||||||
|
var swapIDsPre [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, atomicSwap.ExpireHeight+1, func(id []byte) bool {
|
||||||
|
swapIDsPre = append(swapIDsPre, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
suite.Equal(len(swapIDsPre), 1)
|
||||||
|
|
||||||
|
suite.keeper.RemoveFromByBlockIndex(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Check stored data not in block index
|
||||||
|
var swapIDsPost [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, atomicSwap.ExpireHeight+1, func(id []byte) bool {
|
||||||
|
swapIDsPost = append(swapIDsPost, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
suite.Equal(len(swapIDsPost), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestIterateAtomicSwapsByBlock() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
blockCtx sdk.Context
|
||||||
|
swap types.AtomicSwap
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCases []args
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
// Set up context 100 blocks apart
|
||||||
|
blockCtx := suite.ctx.WithBlockHeight(int64(i) * 100)
|
||||||
|
|
||||||
|
// Initialize a new atomic swap (different randomNumberHash = different swap IDs)
|
||||||
|
timestamp := tmtime.Now().Add(time.Duration(i) * time.Minute).Unix()
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
atomicSwap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash,
|
||||||
|
uint64(blockCtx.BlockHeight()), timestamp, TestUser1, TestUser2,
|
||||||
|
TestSenderOtherChain, TestRecipientOtherChain, 0, types.SWAP_STATUS_OPEN,
|
||||||
|
true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
|
||||||
|
// Insert into block index
|
||||||
|
suite.keeper.InsertIntoByBlockIndex(blockCtx, atomicSwap)
|
||||||
|
// Add to local block index
|
||||||
|
testCases = append(testCases, args{blockCtx, atomicSwap})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the expected swap IDs for a given cutoff block
|
||||||
|
cutoffBlock := int64(450)
|
||||||
|
var expectedSwapIDs [][]byte
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if tc.blockCtx.BlockHeight() < cutoffBlock || tc.blockCtx.BlockHeight() == cutoffBlock {
|
||||||
|
expectedSwapIDs = append(expectedSwapIDs, tc.swap.GetSwapID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the swap IDs from store for a given cutoff block
|
||||||
|
var readSwapIDs [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, uint64(cutoffBlock), func(id []byte) bool {
|
||||||
|
readSwapIDs = append(readSwapIDs, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Equal(expectedSwapIDs, readSwapIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestInsertIntoLongtermStorage() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set atomic swap in longterm storage
|
||||||
|
atomicSwap := atomicSwap(suite.ctx, 1)
|
||||||
|
atomicSwap.ClosedBlock = suite.ctx.BlockHeight()
|
||||||
|
suite.keeper.InsertIntoLongtermStorage(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Longterm storage lacks getter methods, must use iteration to get count of swaps in store
|
||||||
|
var swapIDs [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(atomicSwap.ClosedBlock+LongtermStorageDuration), func(id []byte) bool {
|
||||||
|
swapIDs = append(swapIDs, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
suite.Equal(len(swapIDs), 1)
|
||||||
|
|
||||||
|
// Marshal the expected swapID
|
||||||
|
cdc := suite.app.LegacyAmino()
|
||||||
|
res, _ := cdc.Amino.MarshalBinaryBare(atomicSwap.GetSwapID())
|
||||||
|
expectedSwapID := res[1:]
|
||||||
|
|
||||||
|
suite.Equal(expectedSwapID, swapIDs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestRemoveFromLongtermStorage() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set atomic swap in longterm storage
|
||||||
|
atomicSwap := atomicSwap(suite.ctx, 1)
|
||||||
|
atomicSwap.ClosedBlock = suite.ctx.BlockHeight()
|
||||||
|
suite.keeper.InsertIntoLongtermStorage(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Longterm storage lacks getter methods, must use iteration to get count of swaps in store
|
||||||
|
var swapIDs [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(atomicSwap.ClosedBlock+LongtermStorageDuration), func(id []byte) bool {
|
||||||
|
swapIDs = append(swapIDs, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
suite.Equal(len(swapIDs), 1)
|
||||||
|
|
||||||
|
suite.keeper.RemoveFromLongtermStorage(suite.ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Check stored data not in block index
|
||||||
|
var swapIDsPost [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(atomicSwap.ClosedBlock+LongtermStorageDuration), func(id []byte) bool {
|
||||||
|
swapIDsPost = append(swapIDsPost, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
suite.Equal(len(swapIDsPost), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestIterateAtomicSwapsLongtermStorage() {
|
||||||
|
suite.ResetChain()
|
||||||
|
|
||||||
|
// Set up atomic swaps with stagged closed blocks
|
||||||
|
var swaps types.AtomicSwaps
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
timestamp := tmtime.Now().Unix()
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
atomicSwap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash,
|
||||||
|
uint64(suite.ctx.BlockHeight()), timestamp, TestUser1, TestUser2,
|
||||||
|
TestSenderOtherChain, TestRecipientOtherChain, 100, types.SWAP_STATUS_OPEN,
|
||||||
|
true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
|
||||||
|
// Set closed block staggered by 100 blocks and insert into longterm storage
|
||||||
|
atomicSwap.ClosedBlock = int64(i) * 100
|
||||||
|
suite.keeper.InsertIntoLongtermStorage(suite.ctx, atomicSwap)
|
||||||
|
// Add to local longterm storage
|
||||||
|
swaps = append(swaps, atomicSwap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the expected swap IDs for a given cutoff block.
|
||||||
|
cutoffBlock := int64(LongtermStorageDuration + 350)
|
||||||
|
var expectedSwapIDs [][]byte
|
||||||
|
for _, swap := range swaps {
|
||||||
|
if swap.ClosedBlock+LongtermStorageDuration < cutoffBlock ||
|
||||||
|
swap.ClosedBlock+LongtermStorageDuration == cutoffBlock {
|
||||||
|
expectedSwapIDs = append(expectedSwapIDs, swap.GetSwapID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the swap IDs from store for a given cutoff block
|
||||||
|
var readSwapIDs [][]byte
|
||||||
|
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(cutoffBlock), func(id []byte) bool {
|
||||||
|
readSwapIDs = append(readSwapIDs, id)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// At the cutoff block, iteration should return half of the swap IDs
|
||||||
|
suite.Equal(len(swaps)/2, len(expectedSwapIDs))
|
||||||
|
suite.Equal(len(swaps)/2, len(readSwapIDs))
|
||||||
|
// Should be the same IDs
|
||||||
|
suite.Equal(expectedSwapIDs, readSwapIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestGetSetAssetSupply() {
|
||||||
|
denom := "bnb"
|
||||||
|
// Put asset supply in store
|
||||||
|
assetSupply := types.NewAssetSupply(c(denom, 0), c(denom, 0), c(denom, 50000), c(denom, 0), time.Duration(0))
|
||||||
|
suite.keeper.SetAssetSupply(suite.ctx, assetSupply, denom)
|
||||||
|
|
||||||
|
// Check asset in store
|
||||||
|
storedAssetSupply, found := suite.keeper.GetAssetSupply(suite.ctx, denom)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(assetSupply, storedAssetSupply)
|
||||||
|
|
||||||
|
// Check fake asset supply not in store
|
||||||
|
fakeDenom := "xyz"
|
||||||
|
_, found = suite.keeper.GetAssetSupply(suite.ctx, fakeDenom)
|
||||||
|
suite.False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestGetAllAssetSupplies() {
|
||||||
|
// Put asset supply in store
|
||||||
|
assetSupply := types.NewAssetSupply(c("bnb", 0), c("bnb", 0), c("bnb", 50000), c("bnb", 0), time.Duration(0))
|
||||||
|
suite.keeper.SetAssetSupply(suite.ctx, assetSupply, "bnb")
|
||||||
|
assetSupply = types.NewAssetSupply(c("inc", 0), c("inc", 0), c("inc", 50000), c("inc", 0), time.Duration(0))
|
||||||
|
suite.keeper.SetAssetSupply(suite.ctx, assetSupply, "inc")
|
||||||
|
|
||||||
|
supplies := suite.keeper.GetAllAssetSupplies(suite.ctx)
|
||||||
|
suite.Equal(2, len(supplies))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeeperTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(KeeperTestSuite))
|
||||||
|
}
|
117
x/bep3/keeper/msg_server.go
Normal file
117
x/bep3/keeper/msg_server.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgServer struct {
|
||||||
|
keeper Keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgServerImpl returns an implementation of the bep3 MsgServer interface
|
||||||
|
// for the provided Keeper.
|
||||||
|
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
|
||||||
|
return &msgServer{keeper: keeper}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.MsgServer = msgServer{}
|
||||||
|
|
||||||
|
func (k msgServer) CreateAtomicSwap(goCtx context.Context, msg *types.MsgCreateAtomicSwap) (*types.MsgCreateAtomicSwapResponse, error) {
|
||||||
|
ctx := sdk.UnwrapSDKContext(goCtx)
|
||||||
|
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
to, err := sdk.AccAddressFromBech32(msg.To)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
randomNumberHash, err := hex.DecodeString(msg.RandomNumberHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = k.keeper.CreateAtomicSwap(ctx, randomNumberHash, msg.Timestamp, msg.HeightSpan,
|
||||||
|
from, to, msg.SenderOtherChain, msg.RecipientOtherChain, msg.Amount, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
sdk.EventTypeMessage,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySender, msg.From),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &types.MsgCreateAtomicSwapResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k msgServer) ClaimAtomicSwap(goCtx context.Context, msg *types.MsgClaimAtomicSwap) (*types.MsgClaimAtomicSwapResponse, error) {
|
||||||
|
ctx := sdk.UnwrapSDKContext(goCtx)
|
||||||
|
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
swapID, err := hex.DecodeString(msg.SwapID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
randomNumber, err := hex.DecodeString(msg.RandomNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k.keeper.ClaimAtomicSwap(ctx, from, swapID, randomNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
sdk.EventTypeMessage,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySender, msg.From),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &types.MsgClaimAtomicSwapResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k msgServer) RefundAtomicSwap(goCtx context.Context, msg *types.MsgRefundAtomicSwap) (*types.MsgRefundAtomicSwapResponse, error) {
|
||||||
|
ctx := sdk.UnwrapSDKContext(goCtx)
|
||||||
|
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
swapID, err := hex.DecodeString(msg.SwapID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k.keeper.RefundAtomicSwap(ctx, from, swapID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
sdk.EventTypeMessage,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySender, msg.From),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &types.MsgRefundAtomicSwapResponse{}, nil
|
||||||
|
}
|
129
x/bep3/keeper/msg_server_test.go
Normal file
129
x/bep3/keeper/msg_server_test.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
tmbytes "github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MsgServerTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
ctx sdk.Context
|
||||||
|
app app.TestApp
|
||||||
|
msgServer types.MsgServer
|
||||||
|
keeper keeper.Keeper
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgServerTestSuite) SetupTest() {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
|
||||||
|
cdc := tApp.AppCodec()
|
||||||
|
|
||||||
|
// Set up genesis state and initialize
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
||||||
|
coins := sdk.NewCoins(c("bnb", 10000000000), c("a0gi", 10000))
|
||||||
|
authGS := app.NewFundedGenStateWithSameCoins(tApp.AppCodec(), coins, addrs)
|
||||||
|
tApp.InitializeFromGenesisStates(authGS, NewBep3GenStateMulti(cdc, addrs[0]))
|
||||||
|
|
||||||
|
suite.addrs = addrs
|
||||||
|
suite.keeper = tApp.GetBep3Keeper()
|
||||||
|
suite.msgServer = keeper.NewMsgServerImpl(suite.keeper)
|
||||||
|
suite.app = tApp
|
||||||
|
suite.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgServerTestSuite) AddAtomicSwap() (tmbytes.HexBytes, tmbytes.HexBytes) {
|
||||||
|
expireHeight := types.DefaultMinBlockLock
|
||||||
|
amount := cs(c("bnb", int64(50000)))
|
||||||
|
timestamp := ts(0)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
// Create atomic swap and check err to confirm creation
|
||||||
|
err := suite.keeper.CreateAtomicSwap(suite.ctx, randomNumberHash, timestamp, expireHeight,
|
||||||
|
suite.addrs[0], suite.addrs[1], TestSenderOtherChain, TestRecipientOtherChain,
|
||||||
|
amount, true)
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
swapID := types.CalculateSwapID(randomNumberHash, suite.addrs[0], TestSenderOtherChain)
|
||||||
|
return swapID, randomNumber[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgServerTestSuite) TestMsgCreateAtomicSwap() {
|
||||||
|
amount := cs(c("bnb", int64(10000)))
|
||||||
|
timestamp := ts(0)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
msg := types.NewMsgCreateAtomicSwap(
|
||||||
|
suite.addrs[0].String(), suite.addrs[2].String(), TestRecipientOtherChain,
|
||||||
|
TestSenderOtherChain, randomNumberHash, timestamp, amount,
|
||||||
|
types.DefaultMinBlockLock)
|
||||||
|
|
||||||
|
res, err := suite.msgServer.CreateAtomicSwap(sdk.WrapSDKContext(suite.ctx), &msg)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NotNil(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgServerTestSuite) TestMsgClaimAtomicSwap() {
|
||||||
|
// Attempt claim msg on fake atomic swap
|
||||||
|
badRandomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
badRandomNumberHash := types.CalculateRandomHash(badRandomNumber[:], ts(0))
|
||||||
|
badSwapID := types.CalculateSwapID(badRandomNumberHash, suite.addrs[0], TestSenderOtherChain)
|
||||||
|
badMsg := types.NewMsgClaimAtomicSwap(suite.addrs[0].String(), badSwapID, badRandomNumber[:])
|
||||||
|
badRes, err := suite.msgServer.ClaimAtomicSwap(sdk.WrapSDKContext(suite.ctx), &badMsg)
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().Nil(badRes)
|
||||||
|
|
||||||
|
// Add an atomic swap before attempting new claim msg
|
||||||
|
swapID, randomNumber := suite.AddAtomicSwap()
|
||||||
|
msg := types.NewMsgClaimAtomicSwap(suite.addrs[0].String(), swapID, randomNumber)
|
||||||
|
res, err := suite.msgServer.ClaimAtomicSwap(sdk.WrapSDKContext(suite.ctx), &msg)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NotNil(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgServerTestSuite) TestMsgRefundAtomicSwap() {
|
||||||
|
// Attempt refund msg on fake atomic swap
|
||||||
|
badRandomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
badRandomNumberHash := types.CalculateRandomHash(badRandomNumber[:], ts(0))
|
||||||
|
badSwapID := types.CalculateSwapID(badRandomNumberHash, suite.addrs[0], TestSenderOtherChain)
|
||||||
|
badMsg := types.NewMsgRefundAtomicSwap(suite.addrs[0].String(), badSwapID)
|
||||||
|
badRes, err := suite.msgServer.RefundAtomicSwap(sdk.WrapSDKContext(suite.ctx), &badMsg)
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().Nil(badRes)
|
||||||
|
|
||||||
|
// Add an atomic swap and build refund msg
|
||||||
|
swapID, _ := suite.AddAtomicSwap()
|
||||||
|
msg := types.NewMsgRefundAtomicSwap(suite.addrs[0].String(), swapID)
|
||||||
|
|
||||||
|
// Attempt to refund active atomic swap
|
||||||
|
res1, err := suite.msgServer.RefundAtomicSwap(sdk.WrapSDKContext(suite.ctx), &msg)
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().Nil(res1)
|
||||||
|
|
||||||
|
// Expire the atomic swap with begin blocker and attempt refund
|
||||||
|
laterCtx := suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400)
|
||||||
|
bep3.BeginBlocker(laterCtx, suite.keeper)
|
||||||
|
res2, err := suite.msgServer.RefundAtomicSwap(sdk.WrapSDKContext(laterCtx), &msg)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NotNil(res2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgServerTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MsgServerTestSuite))
|
||||||
|
}
|
167
x/bep3/keeper/params.go
Normal file
167
x/bep3/keeper/params.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetParams returns the total set of bep3 parameters.
|
||||||
|
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
|
||||||
|
k.paramSubspace.GetParamSet(ctx, ¶ms)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetParams sets the bep3 parameters to the param space.
|
||||||
|
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||||
|
k.paramSubspace.SetParamSet(ctx, ¶ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Asset
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// GetAsset returns the asset param associated with the input denom
|
||||||
|
func (k Keeper) GetAsset(ctx sdk.Context, denom string) (types.AssetParam, error) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
for _, asset := range params.AssetParams {
|
||||||
|
if denom == asset.Denom {
|
||||||
|
return asset, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.AssetParam{}, errorsmod.Wrap(types.ErrAssetNotSupported, denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAsset sets an asset in the params
|
||||||
|
func (k Keeper) SetAsset(ctx sdk.Context, asset types.AssetParam) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
for i := range params.AssetParams {
|
||||||
|
if params.AssetParams[i].Denom == asset.Denom {
|
||||||
|
params.AssetParams[i] = asset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k.SetParams(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAssets returns a list containing all supported assets
|
||||||
|
func (k Keeper) GetAssets(ctx sdk.Context) (types.AssetParams, bool) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
return params.AssetParams, len(params.AssetParams) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Asset-specific getters
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// GetDeputyAddress returns the deputy address for the input denom
|
||||||
|
func (k Keeper) GetDeputyAddress(ctx sdk.Context, denom string) (sdk.AccAddress, error) {
|
||||||
|
asset, err := k.GetAsset(ctx, denom)
|
||||||
|
if err != nil {
|
||||||
|
return sdk.AccAddress{}, err
|
||||||
|
}
|
||||||
|
return asset.DeputyAddress, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFixedFee returns the fixed fee for incoming swaps
|
||||||
|
func (k Keeper) GetFixedFee(ctx sdk.Context, denom string) (sdkmath.Int, error) {
|
||||||
|
asset, err := k.GetAsset(ctx, denom)
|
||||||
|
if err != nil {
|
||||||
|
return sdkmath.Int{}, err
|
||||||
|
}
|
||||||
|
return asset.FixedFee, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMinSwapAmount returns the minimum swap amount
|
||||||
|
func (k Keeper) GetMinSwapAmount(ctx sdk.Context, denom string) (sdkmath.Int, error) {
|
||||||
|
asset, err := k.GetAsset(ctx, denom)
|
||||||
|
if err != nil {
|
||||||
|
return sdkmath.Int{}, err
|
||||||
|
}
|
||||||
|
return asset.MinSwapAmount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxSwapAmount returns the maximum swap amount
|
||||||
|
func (k Keeper) GetMaxSwapAmount(ctx sdk.Context, denom string) (sdkmath.Int, error) {
|
||||||
|
asset, err := k.GetAsset(ctx, denom)
|
||||||
|
if err != nil {
|
||||||
|
return sdkmath.Int{}, err
|
||||||
|
}
|
||||||
|
return asset.MaxSwapAmount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMinBlockLock returns the minimum block lock
|
||||||
|
func (k Keeper) GetMinBlockLock(ctx sdk.Context, denom string) (uint64, error) {
|
||||||
|
asset, err := k.GetAsset(ctx, denom)
|
||||||
|
if err != nil {
|
||||||
|
return uint64(0), err
|
||||||
|
}
|
||||||
|
return asset.MinBlockLock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxBlockLock returns the maximum block lock
|
||||||
|
func (k Keeper) GetMaxBlockLock(ctx sdk.Context, denom string) (uint64, error) {
|
||||||
|
asset, err := k.GetAsset(ctx, denom)
|
||||||
|
if err != nil {
|
||||||
|
return uint64(0), err
|
||||||
|
}
|
||||||
|
return asset.MaxBlockLock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAssetByCoinID returns an asset by its denom
|
||||||
|
func (k Keeper) GetAssetByCoinID(ctx sdk.Context, coinID int64) (types.AssetParam, bool) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
for _, asset := range params.AssetParams {
|
||||||
|
if asset.CoinID == coinID {
|
||||||
|
return asset, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.AssetParam{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLiveAsset checks if an asset is both supported and active
|
||||||
|
func (k Keeper) ValidateLiveAsset(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
asset, err := k.GetAsset(ctx, coin.Denom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !asset.Active {
|
||||||
|
return errorsmod.Wrap(types.ErrAssetNotActive, asset.Denom)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSupplyLimit returns the supply limit for the input denom
|
||||||
|
func (k Keeper) GetSupplyLimit(ctx sdk.Context, denom string) (types.SupplyLimit, error) {
|
||||||
|
asset, err := k.GetAsset(ctx, denom)
|
||||||
|
if err != nil {
|
||||||
|
return types.SupplyLimit{}, err
|
||||||
|
}
|
||||||
|
return asset.SupplyLimit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Cross Asset Getters
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// GetAuthorizedAddresses returns a list of addresses that have special authorization within this module, eg all the deputies.
|
||||||
|
func (k Keeper) GetAuthorizedAddresses(ctx sdk.Context) []sdk.AccAddress {
|
||||||
|
assetParams, found := k.GetAssets(ctx)
|
||||||
|
if !found {
|
||||||
|
// no assets params is a valid genesis state
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var addresses []sdk.AccAddress
|
||||||
|
uniqueAddresses := map[string]bool{}
|
||||||
|
|
||||||
|
for _, ap := range assetParams {
|
||||||
|
a := ap.DeputyAddress
|
||||||
|
// de-dup addresses
|
||||||
|
if _, found := uniqueAddresses[a.String()]; !found {
|
||||||
|
addresses = append(addresses, a)
|
||||||
|
}
|
||||||
|
uniqueAddresses[a.String()] = true
|
||||||
|
}
|
||||||
|
return addresses
|
||||||
|
}
|
178
x/bep3/keeper/params_test.go
Normal file
178
x/bep3/keeper/params_test.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParamsTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
ctx sdk.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) SetupTest() {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(10)
|
||||||
|
tApp.InitializeFromGenesisStates(NewBep3GenStateMulti(tApp.AppCodec(), addrs[0]))
|
||||||
|
suite.keeper = tApp.GetBep3Keeper()
|
||||||
|
suite.ctx = ctx
|
||||||
|
suite.addrs = addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetSetAsset() {
|
||||||
|
asset, err := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.NotPanics(func() { suite.keeper.SetAsset(suite.ctx, asset) })
|
||||||
|
_, err = suite.keeper.GetAsset(suite.ctx, "dne")
|
||||||
|
suite.Require().Error(err)
|
||||||
|
|
||||||
|
_, err = suite.keeper.GetAsset(suite.ctx, "inc")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetAssets() {
|
||||||
|
assets, found := suite.keeper.GetAssets(suite.ctx)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(2, len(assets))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetSetDeputyAddress() {
|
||||||
|
asset, err := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
asset.DeputyAddress = suite.addrs[1]
|
||||||
|
suite.NotPanics(func() { suite.keeper.SetAsset(suite.ctx, asset) })
|
||||||
|
|
||||||
|
asset, err = suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(suite.addrs[1], asset.DeputyAddress)
|
||||||
|
addr, err := suite.keeper.GetDeputyAddress(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(suite.addrs[1], addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetDeputyFixedFee() {
|
||||||
|
asset, err := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
bnbDeputyFixedFee := asset.FixedFee
|
||||||
|
|
||||||
|
res, err := suite.keeper.GetFixedFee(suite.ctx, asset.Denom)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(bnbDeputyFixedFee, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetMinMaxSwapAmount() {
|
||||||
|
asset, err := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
minAmount := asset.MinSwapAmount
|
||||||
|
|
||||||
|
res, err := suite.keeper.GetMinSwapAmount(suite.ctx, asset.Denom)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(minAmount, res)
|
||||||
|
|
||||||
|
maxAmount := asset.MaxSwapAmount
|
||||||
|
res, err = suite.keeper.GetMaxSwapAmount(suite.ctx, asset.Denom)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(maxAmount, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetMinMaxBlockLock() {
|
||||||
|
asset, err := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
minLock := asset.MinBlockLock
|
||||||
|
|
||||||
|
res, err := suite.keeper.GetMinBlockLock(suite.ctx, asset.Denom)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(minLock, res)
|
||||||
|
|
||||||
|
maxLock := asset.MaxBlockLock
|
||||||
|
res, err = suite.keeper.GetMaxBlockLock(suite.ctx, asset.Denom)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(maxLock, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetAssetByCoinID() {
|
||||||
|
asset, err := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
res, found := suite.keeper.GetAssetByCoinID(suite.ctx, asset.CoinID)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(asset, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestGetAuthorizedAddresses() {
|
||||||
|
deputyAddresses := suite.keeper.GetAuthorizedAddresses(suite.ctx)
|
||||||
|
// the test params use the same deputy address for two assets
|
||||||
|
expectedAddresses := []sdk.AccAddress{suite.addrs[0]}
|
||||||
|
|
||||||
|
suite.Require().ElementsMatch(expectedAddresses, deputyAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AssetTestSuite) TestValidateLiveAsset() {
|
||||||
|
type args struct {
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectedError error
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal",
|
||||||
|
args{
|
||||||
|
coin: c("bnb", 1),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"asset not supported",
|
||||||
|
args{
|
||||||
|
coin: c("bad", 1),
|
||||||
|
},
|
||||||
|
types.ErrAssetNotSupported,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"asset not active",
|
||||||
|
args{
|
||||||
|
coin: c("inc", 1),
|
||||||
|
},
|
||||||
|
types.ErrAssetNotActive,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
err := suite.keeper.ValidateLiveAsset(suite.ctx, tc.args.coin)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(errors.Is(err, tc.expectedError))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamsTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ParamsTestSuite))
|
||||||
|
}
|
309
x/bep3/keeper/swap.go
Normal file
309
x/bep3/keeper/swap.go
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateAtomicSwap creates a new atomic swap.
|
||||||
|
func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, timestamp int64, heightSpan uint64,
|
||||||
|
sender, recipient sdk.AccAddress, senderOtherChain, recipientOtherChain string,
|
||||||
|
amount sdk.Coins, crossChain bool,
|
||||||
|
) error {
|
||||||
|
// Confirm that this is not a duplicate swap
|
||||||
|
swapID := types.CalculateSwapID(randomNumberHash, sender, senderOtherChain)
|
||||||
|
_, found := k.GetAtomicSwap(ctx, swapID)
|
||||||
|
if found {
|
||||||
|
return errorsmod.Wrap(types.ErrAtomicSwapAlreadyExists, hex.EncodeToString(swapID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot send coins to a module account
|
||||||
|
if k.Maccs[recipient.String()] {
|
||||||
|
return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is a module account", recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(amount) != 1 {
|
||||||
|
return fmt.Errorf("amount must contain exactly one coin")
|
||||||
|
}
|
||||||
|
asset, err := k.GetAsset(ctx, amount[0].Denom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k.ValidateLiveAsset(ctx, amount[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap amount must be within the specified swap amount limits
|
||||||
|
if amount[0].Amount.LT(asset.MinSwapAmount) || amount[0].Amount.GT(asset.MaxSwapAmount) {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidAmount, "amount %d outside range [%s, %s]", amount[0].Amount, asset.MinSwapAmount, asset.MaxSwapAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unix timestamp must be in range [-15 mins, 30 mins] of the current time
|
||||||
|
pastTimestampLimit := ctx.BlockTime().Add(time.Duration(-15) * time.Minute).Unix()
|
||||||
|
futureTimestampLimit := ctx.BlockTime().Add(time.Duration(30) * time.Minute).Unix()
|
||||||
|
if timestamp < pastTimestampLimit || timestamp >= futureTimestampLimit {
|
||||||
|
return errorsmod.Wrap(types.ErrInvalidTimestamp, fmt.Sprintf("block time: %s, timestamp: %s", ctx.BlockTime().String(), time.Unix(timestamp, 0).UTC().String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
var direction types.SwapDirection
|
||||||
|
if sender.Equals(asset.DeputyAddress) {
|
||||||
|
if recipient.Equals(asset.DeputyAddress) {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidSwapAccount, "deputy cannot be both sender and receiver: %s", asset.DeputyAddress)
|
||||||
|
}
|
||||||
|
direction = types.SWAP_DIRECTION_INCOMING
|
||||||
|
} else {
|
||||||
|
if !recipient.Equals(asset.DeputyAddress) {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidSwapAccount, "deputy must be recipient for outgoing account: %s", recipient)
|
||||||
|
}
|
||||||
|
direction = types.SWAP_DIRECTION_OUTGOING
|
||||||
|
}
|
||||||
|
|
||||||
|
switch direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
// If recipient's account doesn't exist, register it in state so that the address can send
|
||||||
|
// a claim swap tx without needing to be registered in state by receiving a coin transfer.
|
||||||
|
recipientAcc := k.accountKeeper.GetAccount(ctx, recipient)
|
||||||
|
if recipientAcc == nil {
|
||||||
|
newAcc := k.accountKeeper.NewAccountWithAddress(ctx, recipient)
|
||||||
|
k.accountKeeper.SetAccount(ctx, newAcc)
|
||||||
|
}
|
||||||
|
// Incoming swaps have already had their fees collected by the deputy during the relay process.
|
||||||
|
err = k.IncrementIncomingAssetSupply(ctx, amount[0])
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
|
||||||
|
// Outgoing swaps must have a height span within the accepted range
|
||||||
|
if heightSpan < asset.MinBlockLock || heightSpan > asset.MaxBlockLock {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidHeightSpan, "height span %d outside range [%d, %d]", heightSpan, asset.MinBlockLock, asset.MaxBlockLock)
|
||||||
|
}
|
||||||
|
// Amount in outgoing swaps must be able to pay the deputy's fixed fee.
|
||||||
|
if amount[0].Amount.LTE(asset.FixedFee.Add(asset.MinSwapAmount)) {
|
||||||
|
return errorsmod.Wrap(types.ErrInsufficientAmount, amount[0].String())
|
||||||
|
}
|
||||||
|
err = k.IncrementOutgoingAssetSupply(ctx, amount[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Transfer coins to module - only needed for outgoing swaps
|
||||||
|
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid swap direction: %s", direction.String())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the details of the swap
|
||||||
|
expireHeight := uint64(ctx.BlockHeight()) + heightSpan
|
||||||
|
atomicSwap := types.NewAtomicSwap(amount, randomNumberHash, expireHeight, timestamp, sender,
|
||||||
|
recipient, senderOtherChain, recipientOtherChain, 0, types.SWAP_STATUS_OPEN, crossChain, direction)
|
||||||
|
|
||||||
|
// Insert the atomic swap under both keys
|
||||||
|
k.SetAtomicSwap(ctx, atomicSwap)
|
||||||
|
k.InsertIntoByBlockIndex(ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Emit 'create_atomic_swap' event
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeCreateAtomicSwap,
|
||||||
|
sdk.NewAttribute(types.AttributeKeySender, atomicSwap.Sender.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyRecipient, atomicSwap.Recipient.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, hex.EncodeToString(atomicSwap.GetSwapID())),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, hex.EncodeToString(atomicSwap.RandomNumberHash)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyTimestamp, fmt.Sprintf("%d", atomicSwap.Timestamp)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeySenderOtherChain, atomicSwap.SenderOtherChain),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyExpireHeight, fmt.Sprintf("%d", atomicSwap.ExpireHeight)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyAmount, atomicSwap.Amount.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyDirection, atomicSwap.Direction.String()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimAtomicSwap validates a claim attempt, and if successful, sends the escrowed amount and closes the AtomicSwap.
|
||||||
|
func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []byte, randomNumber []byte) error {
|
||||||
|
atomicSwap, found := k.GetAtomicSwap(ctx, swapID)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrapf(types.ErrAtomicSwapNotFound, "%s", swapID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only open atomic swaps can be claimed
|
||||||
|
if atomicSwap.Status != types.SWAP_STATUS_OPEN {
|
||||||
|
return errorsmod.Wrapf(types.ErrSwapNotClaimable, "status %s", atomicSwap.Status.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate hashed secret using submitted number
|
||||||
|
hashedSubmittedNumber := types.CalculateRandomHash(randomNumber, atomicSwap.Timestamp)
|
||||||
|
hashedSecret := types.CalculateSwapID(hashedSubmittedNumber, atomicSwap.Sender, atomicSwap.SenderOtherChain)
|
||||||
|
|
||||||
|
// Confirm that secret unlocks the atomic swap
|
||||||
|
if !bytes.Equal(hashedSecret, atomicSwap.GetSwapID()) {
|
||||||
|
return errorsmod.Wrapf(types.ErrInvalidClaimSecret, "the submitted random number is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch atomicSwap.Direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
err = k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = k.IncrementCurrentAssetSupply(ctx, atomicSwap.Amount[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// incoming case - coins should be MINTED, then sent to user
|
||||||
|
err = k.bankKeeper.MintCoins(ctx, types.ModuleName, atomicSwap.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Send intended recipient coins
|
||||||
|
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Recipient, atomicSwap.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = k.DecrementCurrentAssetSupply(ctx, atomicSwap.Amount[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// outgoing case - coins should be burned
|
||||||
|
err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, atomicSwap.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete swap
|
||||||
|
atomicSwap.Status = types.SWAP_STATUS_COMPLETED
|
||||||
|
atomicSwap.ClosedBlock = ctx.BlockHeight()
|
||||||
|
k.SetAtomicSwap(ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Remove from byBlock index and transition to longterm storage
|
||||||
|
k.RemoveFromByBlockIndex(ctx, atomicSwap)
|
||||||
|
k.InsertIntoLongtermStorage(ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Emit 'claim_atomic_swap' event
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeClaimAtomicSwap,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyClaimSender, from.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyRecipient, atomicSwap.Recipient.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, hex.EncodeToString(atomicSwap.GetSwapID())),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, hex.EncodeToString(atomicSwap.RandomNumberHash)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyRandomNumber, hex.EncodeToString(randomNumber)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefundAtomicSwap refunds an AtomicSwap, sending assets to the original sender and closing the AtomicSwap.
|
||||||
|
func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []byte) error {
|
||||||
|
atomicSwap, found := k.GetAtomicSwap(ctx, swapID)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrapf(types.ErrAtomicSwapNotFound, "%s", swapID)
|
||||||
|
}
|
||||||
|
// Only expired swaps may be refunded
|
||||||
|
if atomicSwap.Status != types.SWAP_STATUS_EXPIRED {
|
||||||
|
return errorsmod.Wrapf(types.ErrSwapNotRefundable, "status %s", atomicSwap.Status.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch atomicSwap.Direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
err = k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0])
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Refund coins to original swap sender for outgoing swaps
|
||||||
|
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Sender, atomicSwap.Amount)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete swap
|
||||||
|
atomicSwap.Status = types.SWAP_STATUS_COMPLETED
|
||||||
|
atomicSwap.ClosedBlock = ctx.BlockHeight()
|
||||||
|
k.SetAtomicSwap(ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Transition to longterm storage
|
||||||
|
k.InsertIntoLongtermStorage(ctx, atomicSwap)
|
||||||
|
|
||||||
|
// Emit 'refund_atomic_swap' event
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeRefundAtomicSwap,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyRefundSender, from.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeySender, atomicSwap.Sender.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, hex.EncodeToString(atomicSwap.GetSwapID())),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, hex.EncodeToString(atomicSwap.RandomNumberHash)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateExpiredAtomicSwaps finds all AtomicSwaps that are past (or at) their ending times and expires them.
|
||||||
|
func (k Keeper) UpdateExpiredAtomicSwaps(ctx sdk.Context) {
|
||||||
|
var expiredSwapIDs []string
|
||||||
|
k.IterateAtomicSwapsByBlock(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool {
|
||||||
|
atomicSwap, found := k.GetAtomicSwap(ctx, id)
|
||||||
|
if !found {
|
||||||
|
// NOTE: shouldn't happen. Continue to next item.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Expire the uncompleted swap and update both indexes
|
||||||
|
atomicSwap.Status = types.SWAP_STATUS_EXPIRED
|
||||||
|
// Note: claimed swaps have already been removed from byBlock index.
|
||||||
|
k.RemoveFromByBlockIndex(ctx, atomicSwap)
|
||||||
|
k.SetAtomicSwap(ctx, atomicSwap)
|
||||||
|
expiredSwapIDs = append(expiredSwapIDs, hex.EncodeToString(atomicSwap.GetSwapID()))
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emit 'swaps_expired' event
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeSwapsExpired,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyAtomicSwapIDs, fmt.Sprintf("%s", expiredSwapIDs)),
|
||||||
|
sdk.NewAttribute(types.AttributeExpirationBlock, fmt.Sprintf("%d", ctx.BlockHeight())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteClosedAtomicSwapsFromLongtermStorage removes swaps one week after completion.
|
||||||
|
func (k Keeper) DeleteClosedAtomicSwapsFromLongtermStorage(ctx sdk.Context) {
|
||||||
|
k.IterateAtomicSwapsLongtermStorage(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool {
|
||||||
|
swap, found := k.GetAtomicSwap(ctx, id)
|
||||||
|
if !found {
|
||||||
|
// NOTE: shouldn't happen. Continue to next item.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k.RemoveAtomicSwap(ctx, swap.GetSwapID())
|
||||||
|
k.RemoveFromLongtermStorage(ctx, swap)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
829
x/bep3/keeper/swap_test.go
Normal file
829
x/bep3/keeper/swap_test.go
Normal file
@ -0,0 +1,829 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
tmbytes "github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AtomicSwapTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
randMacc sdk.AccAddress
|
||||||
|
deputy sdk.AccAddress
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
timestamps []int64
|
||||||
|
randomNumberHashes []tmbytes.HexBytes
|
||||||
|
randomNumbers []tmbytes.HexBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
STARING_BNB_BALANCE = int64(3000000000000)
|
||||||
|
BNB_DENOM = "bnb"
|
||||||
|
OTHER_DENOM = "inc"
|
||||||
|
STARING_OTHER_BALANCE = int64(3000000000000)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *AtomicSwapTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
|
||||||
|
// Initialize test app and set context
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
cdc := tApp.AppCodec()
|
||||||
|
|
||||||
|
// Create and load 20 accounts with bnb tokens
|
||||||
|
coins := sdk.NewCoins(c(BNB_DENOM, STARING_BNB_BALANCE), c(OTHER_DENOM, STARING_OTHER_BALANCE))
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(20)
|
||||||
|
deputy := addrs[0]
|
||||||
|
authGS := app.NewFundedGenStateWithSameCoins(tApp.AppCodec(), coins, addrs)
|
||||||
|
|
||||||
|
// Initialize genesis state
|
||||||
|
tApp.InitializeFromGenesisStates(authGS, NewBep3GenStateMulti(cdc, deputy))
|
||||||
|
|
||||||
|
keeper := tApp.GetBep3Keeper()
|
||||||
|
params := keeper.GetParams(ctx)
|
||||||
|
params.AssetParams[1].Active = true
|
||||||
|
keeper.SetParams(ctx, params)
|
||||||
|
|
||||||
|
suite.app = tApp
|
||||||
|
suite.ctx = ctx
|
||||||
|
suite.deputy = deputy
|
||||||
|
suite.addrs = addrs
|
||||||
|
suite.keeper = keeper
|
||||||
|
|
||||||
|
// Load a random module account to test blacklisting
|
||||||
|
i := 0
|
||||||
|
var randModuleAcc sdk.AccAddress
|
||||||
|
for macc := range suite.keeper.Maccs {
|
||||||
|
if i == len(suite.keeper.Maccs)/2 {
|
||||||
|
acc, err := sdk.AccAddressFromBech32(macc)
|
||||||
|
suite.Nil(err)
|
||||||
|
randModuleAcc = acc
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
suite.randMacc = randModuleAcc
|
||||||
|
|
||||||
|
suite.GenerateSwapDetails()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AtomicSwapTestSuite) GenerateSwapDetails() {
|
||||||
|
var timestamps []int64
|
||||||
|
var randomNumberHashes []tmbytes.HexBytes
|
||||||
|
var randomNumbers []tmbytes.HexBytes
|
||||||
|
for i := 0; i < 15; i++ {
|
||||||
|
// Set up atomic swap details
|
||||||
|
timestamp := ts(i)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
timestamps = append(timestamps, timestamp)
|
||||||
|
randomNumberHashes = append(randomNumberHashes, randomNumberHash)
|
||||||
|
randomNumbers = append(randomNumbers, randomNumber[:])
|
||||||
|
}
|
||||||
|
suite.timestamps = timestamps
|
||||||
|
suite.randomNumberHashes = randomNumberHashes
|
||||||
|
suite.randomNumbers = randomNumbers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AtomicSwapTestSuite) TestCreateAtomicSwap() {
|
||||||
|
currentTmTime := tmtime.Now()
|
||||||
|
type args struct {
|
||||||
|
randomNumberHash []byte
|
||||||
|
timestamp int64
|
||||||
|
heightSpan uint64
|
||||||
|
sender sdk.AccAddress
|
||||||
|
recipient sdk.AccAddress
|
||||||
|
senderOtherChain string
|
||||||
|
recipientOtherChain string
|
||||||
|
coins sdk.Coins
|
||||||
|
crossChain bool
|
||||||
|
direction types.SwapDirection
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
blockTime time.Time
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
shouldBeFound bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"incoming swap",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
timestamp: suite.timestamps[0],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[1],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"incoming swap rate limited",
|
||||||
|
currentTmTime.Add(time.Minute * 10),
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[12],
|
||||||
|
timestamp: suite.timestamps[12],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[1],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c("inc", 50000000000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"incoming swap over rate limit",
|
||||||
|
currentTmTime.Add(time.Minute * 10),
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[13],
|
||||||
|
timestamp: suite.timestamps[13],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[1],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c("inc", 50000000001)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outgoing swap",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
timestamp: suite.timestamps[0],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.addrs[1],
|
||||||
|
recipient: suite.deputy,
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outgoing swap amount not greater than fixed fee",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[1],
|
||||||
|
timestamp: suite.timestamps[1],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.addrs[1],
|
||||||
|
recipient: suite.addrs[2],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 1000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupported asset",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[2],
|
||||||
|
timestamp: suite.timestamps[2],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[2],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c("xyz", 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outside timestamp range",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[3],
|
||||||
|
timestamp: suite.timestamps[3] - 2000,
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[3],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"future timestamp",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[4],
|
||||||
|
timestamp: suite.timestamps[4] + 5000,
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[4],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"small height span on outgoing swap",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[5],
|
||||||
|
timestamp: suite.timestamps[5],
|
||||||
|
heightSpan: uint64(100),
|
||||||
|
sender: suite.addrs[5],
|
||||||
|
recipient: suite.deputy,
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"big height span on outgoing swap",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[6],
|
||||||
|
timestamp: suite.timestamps[6],
|
||||||
|
heightSpan: uint64(300),
|
||||||
|
sender: suite.addrs[6],
|
||||||
|
recipient: suite.deputy,
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero amount",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[7],
|
||||||
|
timestamp: suite.timestamps[7],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[7],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 0)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duplicate swap",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
timestamp: suite.timestamps[0],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[1],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient is module account",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[8],
|
||||||
|
timestamp: suite.timestamps[8],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.randMacc,
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 5000)),
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exactly at maximum amount",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[10],
|
||||||
|
timestamp: suite.timestamps[10],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[4],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 1000000000000)), // 10,000 BNB
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"above maximum amount",
|
||||||
|
currentTmTime,
|
||||||
|
args{
|
||||||
|
randomNumberHash: suite.randomNumberHashes[11],
|
||||||
|
timestamp: suite.timestamps[11],
|
||||||
|
heightSpan: types.DefaultMinBlockLock,
|
||||||
|
sender: suite.deputy,
|
||||||
|
recipient: suite.addrs[5],
|
||||||
|
senderOtherChain: TestSenderOtherChain,
|
||||||
|
recipientOtherChain: TestRecipientOtherChain,
|
||||||
|
coins: cs(c(BNB_DENOM, 1000000000001)), // 10,001 BNB
|
||||||
|
crossChain: true,
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
// Increment current asset supply to support outgoing swaps
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(tc.blockTime)
|
||||||
|
if tc.args.direction == types.SWAP_DIRECTION_OUTGOING {
|
||||||
|
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coins[0])
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load asset denom (required for zero coins test case)
|
||||||
|
var swapAssetDenom string
|
||||||
|
if len(tc.args.coins) == 1 {
|
||||||
|
swapAssetDenom = tc.args.coins[0].Denom
|
||||||
|
} else {
|
||||||
|
swapAssetDenom = BNB_DENOM
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load sender's account prior to swap creation
|
||||||
|
bk := suite.app.GetBankKeeper()
|
||||||
|
|
||||||
|
senderBalancePre := bk.GetBalance(suite.ctx, tc.args.sender, swapAssetDenom)
|
||||||
|
assetSupplyPre, _ := suite.keeper.GetAssetSupply(suite.ctx, swapAssetDenom)
|
||||||
|
|
||||||
|
// Create atomic swap
|
||||||
|
err := suite.keeper.CreateAtomicSwap(suite.ctx, tc.args.randomNumberHash, tc.args.timestamp,
|
||||||
|
tc.args.heightSpan, tc.args.sender, tc.args.recipient, tc.args.senderOtherChain,
|
||||||
|
tc.args.recipientOtherChain, tc.args.coins, tc.args.crossChain)
|
||||||
|
|
||||||
|
// Load sender's account after swap creation
|
||||||
|
senderBalancePost := bk.GetBalance(suite.ctx, tc.args.sender, swapAssetDenom)
|
||||||
|
assetSupplyPost, _ := suite.keeper.GetAssetSupply(suite.ctx, swapAssetDenom)
|
||||||
|
|
||||||
|
// Load expected swap ID
|
||||||
|
expectedSwapID := types.CalculateSwapID(tc.args.randomNumberHash, tc.args.sender, tc.args.senderOtherChain)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Check incoming/outgoing asset supply increased
|
||||||
|
switch tc.args.direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply.Add(tc.args.coins[0]), assetSupplyPost.IncomingSupply)
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
// Check coins moved
|
||||||
|
suite.Equal(senderBalancePre.Sub(tc.args.coins[0]), senderBalancePost)
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply.Add(tc.args.coins[0]), assetSupplyPost.OutgoingSupply)
|
||||||
|
default:
|
||||||
|
suite.Fail("should not have invalid direction")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check swap in store
|
||||||
|
actualSwap, found := suite.keeper.GetAtomicSwap(suite.ctx, expectedSwapID)
|
||||||
|
suite.True(found)
|
||||||
|
suite.NotNil(actualSwap)
|
||||||
|
|
||||||
|
// Confirm swap contents
|
||||||
|
expectedSwap := types.AtomicSwap{
|
||||||
|
Amount: tc.args.coins,
|
||||||
|
RandomNumberHash: tc.args.randomNumberHash,
|
||||||
|
ExpireHeight: uint64(suite.ctx.BlockHeight()) + tc.args.heightSpan,
|
||||||
|
Timestamp: tc.args.timestamp,
|
||||||
|
Sender: tc.args.sender,
|
||||||
|
Recipient: tc.args.recipient,
|
||||||
|
SenderOtherChain: tc.args.senderOtherChain,
|
||||||
|
RecipientOtherChain: tc.args.recipientOtherChain,
|
||||||
|
ClosedBlock: 0,
|
||||||
|
Status: types.SWAP_STATUS_OPEN,
|
||||||
|
CrossChain: tc.args.crossChain,
|
||||||
|
Direction: tc.args.direction,
|
||||||
|
}
|
||||||
|
suite.Equal(expectedSwap, actualSwap)
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
// Check coins not moved
|
||||||
|
suite.Equal(senderBalancePre, senderBalancePost)
|
||||||
|
|
||||||
|
// Check incoming/outgoing asset supply not increased
|
||||||
|
switch tc.args.direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||||
|
default:
|
||||||
|
suite.Fail("should not have invalid direction")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if swap found in store
|
||||||
|
_, found := suite.keeper.GetAtomicSwap(suite.ctx, expectedSwapID)
|
||||||
|
if !tc.shouldBeFound {
|
||||||
|
suite.False(found)
|
||||||
|
} else {
|
||||||
|
suite.True(found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||||
|
suite.SetupTest()
|
||||||
|
currentTmTime := tmtime.Now()
|
||||||
|
invalidRandomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
type args struct {
|
||||||
|
coins sdk.Coins
|
||||||
|
swapID []byte
|
||||||
|
randomNumber []byte
|
||||||
|
direction types.SwapDirection
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
claimCtx sdk.Context
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal incoming swap",
|
||||||
|
suite.ctx,
|
||||||
|
args{
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
swapID: []byte{},
|
||||||
|
randomNumber: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normal incoming swap rate-limited",
|
||||||
|
suite.ctx.WithBlockTime(currentTmTime.Add(time.Minute * 10)),
|
||||||
|
args{
|
||||||
|
coins: cs(c(OTHER_DENOM, 50000)),
|
||||||
|
swapID: []byte{},
|
||||||
|
randomNumber: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normal outgoing swap",
|
||||||
|
suite.ctx,
|
||||||
|
args{
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
swapID: []byte{},
|
||||||
|
randomNumber: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid random number",
|
||||||
|
suite.ctx,
|
||||||
|
args{
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
swapID: []byte{},
|
||||||
|
randomNumber: invalidRandomNumber[:],
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wrong swap ID",
|
||||||
|
suite.ctx,
|
||||||
|
args{
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
swapID: types.CalculateSwapID(suite.randomNumberHashes[3], suite.addrs[6], TestRecipientOtherChain),
|
||||||
|
randomNumber: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"past expiration",
|
||||||
|
suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 2000),
|
||||||
|
args{
|
||||||
|
coins: cs(c(BNB_DENOM, 50000)),
|
||||||
|
swapID: []byte{},
|
||||||
|
randomNumber: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
suite.GenerateSwapDetails()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
expectedRecipient := suite.addrs[5]
|
||||||
|
sender := suite.deputy
|
||||||
|
|
||||||
|
// Set sender to other and increment current asset supply for outgoing swap
|
||||||
|
if tc.args.direction == types.SWAP_DIRECTION_OUTGOING {
|
||||||
|
sender = suite.addrs[6]
|
||||||
|
expectedRecipient = suite.deputy
|
||||||
|
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coins[0])
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create atomic swap
|
||||||
|
err := suite.keeper.CreateAtomicSwap(suite.ctx, suite.randomNumberHashes[i], suite.timestamps[i],
|
||||||
|
types.DefaultMinBlockLock, sender, expectedRecipient, TestSenderOtherChain, TestRecipientOtherChain,
|
||||||
|
tc.args.coins, true)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
realSwapID := types.CalculateSwapID(suite.randomNumberHashes[i], sender, TestSenderOtherChain)
|
||||||
|
|
||||||
|
// If args contains an invalid swap ID claim attempt will use it instead of the real swap ID
|
||||||
|
var claimSwapID []byte
|
||||||
|
if len(tc.args.swapID) == 0 {
|
||||||
|
claimSwapID = realSwapID
|
||||||
|
} else {
|
||||||
|
claimSwapID = tc.args.swapID
|
||||||
|
}
|
||||||
|
|
||||||
|
// If args contains an invalid random number claim attempt will use it instead of the real random number
|
||||||
|
var claimRandomNumber []byte
|
||||||
|
if len(tc.args.randomNumber) == 0 {
|
||||||
|
claimRandomNumber = suite.randomNumbers[i]
|
||||||
|
} else {
|
||||||
|
claimRandomNumber = tc.args.randomNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the beginblocker before attempting claim
|
||||||
|
bep3.BeginBlocker(tc.claimCtx, suite.keeper)
|
||||||
|
|
||||||
|
// Load expected recipient's account prior to claim attempt
|
||||||
|
bk := suite.app.GetBankKeeper()
|
||||||
|
expectedRecipientBalancePre := bk.GetBalance(suite.ctx, expectedRecipient, tc.args.coins[0].Denom)
|
||||||
|
// Load asset supplies prior to claim attempt
|
||||||
|
assetSupplyPre, _ := suite.keeper.GetAssetSupply(tc.claimCtx, tc.args.coins[0].Denom)
|
||||||
|
|
||||||
|
// Attempt to claim atomic swap
|
||||||
|
err = suite.keeper.ClaimAtomicSwap(tc.claimCtx, expectedRecipient, claimSwapID, claimRandomNumber)
|
||||||
|
|
||||||
|
// Load expected recipient's account after the claim attempt
|
||||||
|
expectedRecipientBalancePost := bk.GetBalance(suite.ctx, expectedRecipient, tc.args.coins[0].Denom)
|
||||||
|
// Load asset supplies after the claim attempt
|
||||||
|
assetSupplyPost, _ := suite.keeper.GetAssetSupply(tc.claimCtx, tc.args.coins[0].Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Check asset supply changes
|
||||||
|
switch tc.args.direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
// Check coins moved
|
||||||
|
suite.Equal(expectedRecipientBalancePre.Add(tc.args.coins[0]), expectedRecipientBalancePost)
|
||||||
|
// Check incoming supply decreased
|
||||||
|
suite.True(assetSupplyPre.IncomingSupply.Amount.Sub(tc.args.coins[0].Amount).Equal(assetSupplyPost.IncomingSupply.Amount))
|
||||||
|
// Check current supply increased
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply.Add(tc.args.coins[0]), assetSupplyPost.CurrentSupply)
|
||||||
|
// Check outgoing supply not changed
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
// Check incoming supply not changed
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||||
|
// Check current supply decreased
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply.Sub(tc.args.coins[0]), assetSupplyPost.CurrentSupply)
|
||||||
|
// Check outgoing supply decreased
|
||||||
|
suite.True(assetSupplyPre.OutgoingSupply.Sub(tc.args.coins[0]).IsEqual(assetSupplyPost.OutgoingSupply))
|
||||||
|
default:
|
||||||
|
suite.Fail("should not have invalid direction")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
// Check coins not moved
|
||||||
|
suite.Equal(expectedRecipientBalancePre, expectedRecipientBalancePost)
|
||||||
|
|
||||||
|
// Check asset supply has not changed
|
||||||
|
switch tc.args.direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||||
|
default:
|
||||||
|
suite.Fail("should not have invalid direction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AtomicSwapTestSuite) TestRefundAtomicSwap() {
|
||||||
|
suite.SetupTest()
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
swapID []byte
|
||||||
|
direction types.SwapDirection
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
refundCtx sdk.Context
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"normal incoming swap",
|
||||||
|
suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
|
||||||
|
args{
|
||||||
|
swapID: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normal outgoing swap",
|
||||||
|
suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
|
||||||
|
args{
|
||||||
|
swapID: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before expiration",
|
||||||
|
suite.ctx,
|
||||||
|
args{
|
||||||
|
swapID: []byte{},
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wrong swapID",
|
||||||
|
suite.ctx,
|
||||||
|
args{
|
||||||
|
swapID: types.CalculateSwapID(suite.randomNumberHashes[6], suite.addrs[1], TestRecipientOtherChain),
|
||||||
|
direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
suite.GenerateSwapDetails()
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
// Create atomic swap
|
||||||
|
expectedRefundAmount := cs(c(BNB_DENOM, 50000))
|
||||||
|
sender := suite.deputy
|
||||||
|
expectedRecipient := suite.addrs[9]
|
||||||
|
|
||||||
|
// Set sender to other and increment current asset supply for outgoing swap
|
||||||
|
if tc.args.direction == types.SWAP_DIRECTION_OUTGOING {
|
||||||
|
sender = suite.addrs[6]
|
||||||
|
expectedRecipient = suite.deputy
|
||||||
|
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, expectedRefundAmount[0])
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := suite.keeper.CreateAtomicSwap(suite.ctx, suite.randomNumberHashes[i], suite.timestamps[i],
|
||||||
|
types.DefaultMinBlockLock, sender, expectedRecipient, TestSenderOtherChain, TestRecipientOtherChain,
|
||||||
|
expectedRefundAmount, true)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
realSwapID := types.CalculateSwapID(suite.randomNumberHashes[i], sender, TestSenderOtherChain)
|
||||||
|
|
||||||
|
// If args contains an invalid swap ID refund attempt will use it instead of the real swap ID
|
||||||
|
var refundSwapID []byte
|
||||||
|
if len(tc.args.swapID) == 0 {
|
||||||
|
refundSwapID = realSwapID
|
||||||
|
} else {
|
||||||
|
refundSwapID = tc.args.swapID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the beginblocker before attempting refund
|
||||||
|
bep3.BeginBlocker(tc.refundCtx, suite.keeper)
|
||||||
|
|
||||||
|
// Load sender's account prior to swap refund
|
||||||
|
bk := suite.app.GetBankKeeper()
|
||||||
|
originalSenderBalancePre := bk.GetBalance(suite.ctx, sender, expectedRefundAmount[0].Denom)
|
||||||
|
// Load asset supply prior to swap refund
|
||||||
|
assetSupplyPre, _ := suite.keeper.GetAssetSupply(tc.refundCtx, expectedRefundAmount[0].Denom)
|
||||||
|
|
||||||
|
// Attempt to refund atomic swap
|
||||||
|
err = suite.keeper.RefundAtomicSwap(tc.refundCtx, sender, refundSwapID)
|
||||||
|
|
||||||
|
// Load sender's account after refund
|
||||||
|
originalSenderBalancePost := bk.GetBalance(suite.ctx, sender, expectedRefundAmount[0].Denom)
|
||||||
|
// Load asset supply after to swap refund
|
||||||
|
assetSupplyPost, _ := suite.keeper.GetAssetSupply(tc.refundCtx, expectedRefundAmount[0].Denom)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Check asset supply changes
|
||||||
|
switch tc.args.direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
// Check incoming supply decreased
|
||||||
|
suite.True(assetSupplyPre.IncomingSupply.Sub(expectedRefundAmount[0]).IsEqual(assetSupplyPost.IncomingSupply))
|
||||||
|
// Check current, outgoing supply not changed
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
// Check coins moved
|
||||||
|
suite.Equal(originalSenderBalancePre.Add(expectedRefundAmount[0]), originalSenderBalancePost)
|
||||||
|
// Check incoming, current supply not changed
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||||
|
// Check outgoing supply decreased
|
||||||
|
suite.True(assetSupplyPre.OutgoingSupply.Sub(expectedRefundAmount[0]).IsEqual(assetSupplyPost.OutgoingSupply))
|
||||||
|
default:
|
||||||
|
suite.Fail("should not have invalid direction")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
// Check coins not moved
|
||||||
|
suite.Equal(originalSenderBalancePre, originalSenderBalancePost)
|
||||||
|
|
||||||
|
// Check asset supply has not changed
|
||||||
|
switch tc.args.direction {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||||
|
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||||
|
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||||
|
default:
|
||||||
|
suite.Fail("should not have invalid direction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAtomicSwapTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(AtomicSwapTestSuite))
|
||||||
|
}
|
57
x/bep3/legacy/v0_17/migrate.go
Normal file
57
x/bep3/legacy/v0_17/migrate.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package v0_16
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resetSwapForZeroHeight updates swap expiry/close heights to work when the chain height is reset to zero.
|
||||||
|
func resetSwapForZeroHeight(swap types.AtomicSwap) types.AtomicSwap {
|
||||||
|
switch status := swap.Status; status {
|
||||||
|
case types.SWAP_STATUS_COMPLETED:
|
||||||
|
// Reset closed block to one so completed swaps are not held in long term storage too long.
|
||||||
|
swap.ClosedBlock = 1
|
||||||
|
case types.SWAP_STATUS_OPEN:
|
||||||
|
switch dir := swap.Direction; dir {
|
||||||
|
case types.SWAP_DIRECTION_INCOMING:
|
||||||
|
// Open incoming swaps can be expired safely. They haven't been claimed yet, so the outgoing swap on bnb will just timeout.
|
||||||
|
// The chain downtime cannot be accurately predicted, so it's easier to expire than to recalculate a correct expire height.
|
||||||
|
swap.ExpireHeight = 1
|
||||||
|
swap.Status = types.SWAP_STATUS_EXPIRED
|
||||||
|
case types.SWAP_DIRECTION_OUTGOING:
|
||||||
|
// Open outgoing swaps should be extended to allow enough time to claim after the chain launches.
|
||||||
|
// They cannot be expired as there could be an open/claimed bnb swap.
|
||||||
|
swap.ExpireHeight = 1 + 24686 // default timeout used when sending swaps from 0g
|
||||||
|
case types.SWAP_DIRECTION_UNSPECIFIED:
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown bep3 swap direction '%s'", dir))
|
||||||
|
}
|
||||||
|
case types.SWAP_STATUS_EXPIRED:
|
||||||
|
// Once a swap is marked expired the expire height is ignored. However reset to 1 to be sure.
|
||||||
|
swap.ExpireHeight = 1
|
||||||
|
case types.SWAP_STATUS_UNSPECIFIED:
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown bep3 swap status '%s'", status))
|
||||||
|
}
|
||||||
|
|
||||||
|
return swap
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetSwapsForZeroHeight(oldSwaps types.AtomicSwaps) types.AtomicSwaps {
|
||||||
|
newSwaps := make(types.AtomicSwaps, len(oldSwaps))
|
||||||
|
for i, oldSwap := range oldSwaps {
|
||||||
|
swap := resetSwapForZeroHeight(oldSwap)
|
||||||
|
newSwaps[i] = swap
|
||||||
|
}
|
||||||
|
return newSwaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func Migrate(oldState types.GenesisState) *types.GenesisState {
|
||||||
|
return &types.GenesisState{
|
||||||
|
PreviousBlockTime: oldState.PreviousBlockTime,
|
||||||
|
Params: oldState.Params,
|
||||||
|
AtomicSwaps: resetSwapsForZeroHeight(oldState.AtomicSwaps),
|
||||||
|
Supplies: oldState.Supplies,
|
||||||
|
}
|
||||||
|
}
|
175
x/bep3/legacy/v0_17/migrate_test.go
Normal file
175
x/bep3/legacy/v0_17/migrate_test.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package v0_16
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
"github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
app "github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type migrateTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
addresses []sdk.AccAddress
|
||||||
|
v16genstate types.GenesisState
|
||||||
|
cdc codec.Codec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *migrateTestSuite) SetupTest() {
|
||||||
|
app.SetSDKConfig()
|
||||||
|
|
||||||
|
s.v16genstate = types.GenesisState{
|
||||||
|
PreviousBlockTime: time.Date(2021, 4, 8, 15, 0, 0, 0, time.UTC),
|
||||||
|
Params: types.Params{},
|
||||||
|
Supplies: types.AssetSupplies{},
|
||||||
|
AtomicSwaps: types.AtomicSwaps{},
|
||||||
|
}
|
||||||
|
|
||||||
|
config := app.MakeEncodingConfig()
|
||||||
|
s.cdc = config.Marshaler
|
||||||
|
|
||||||
|
_, accAddresses := app.GeneratePrivKeyAddressPairs(10)
|
||||||
|
s.addresses = accAddresses
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *migrateTestSuite) TestMigrate_JSON() {
|
||||||
|
// Migrate v16 bep3 to v17
|
||||||
|
file := filepath.Join("testdata", "v16-bep3.json")
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
err = s.cdc.UnmarshalJSON(data, &s.v16genstate)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
genstate := Migrate(s.v16genstate)
|
||||||
|
|
||||||
|
// Compare expect v16 bep3 json with migrated json
|
||||||
|
actual := s.cdc.MustMarshalJSON(genstate)
|
||||||
|
file = filepath.Join("testdata", "v17-bep3.json")
|
||||||
|
expected, err := ioutil.ReadFile(file)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().JSONEq(string(expected), string(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *migrateTestSuite) TestMigrate_Swaps() {
|
||||||
|
type swap struct {
|
||||||
|
ExpireHeight uint64
|
||||||
|
CloseBlock int64
|
||||||
|
Status types.SwapStatus
|
||||||
|
Direction types.SwapDirection
|
||||||
|
}
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
oldSwap swap
|
||||||
|
newSwap swap
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "incoming open swap",
|
||||||
|
oldSwap: swap{
|
||||||
|
// expire and close not set in open swaps
|
||||||
|
Status: types.SWAP_STATUS_OPEN,
|
||||||
|
Direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
newSwap: swap{
|
||||||
|
ExpireHeight: 1,
|
||||||
|
Status: types.SWAP_STATUS_EXPIRED,
|
||||||
|
Direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "outgoing open swap",
|
||||||
|
oldSwap: swap{
|
||||||
|
// expire and close not set in open swaps
|
||||||
|
Status: types.SWAP_STATUS_OPEN,
|
||||||
|
Direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
newSwap: swap{
|
||||||
|
ExpireHeight: 24687,
|
||||||
|
Status: types.SWAP_STATUS_OPEN,
|
||||||
|
Direction: types.SWAP_DIRECTION_OUTGOING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "completed swap",
|
||||||
|
oldSwap: swap{
|
||||||
|
ExpireHeight: 1000,
|
||||||
|
CloseBlock: 900,
|
||||||
|
Status: types.SWAP_STATUS_COMPLETED,
|
||||||
|
Direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
newSwap: swap{
|
||||||
|
ExpireHeight: 1000,
|
||||||
|
CloseBlock: 1,
|
||||||
|
Status: types.SWAP_STATUS_COMPLETED,
|
||||||
|
Direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expired swap",
|
||||||
|
oldSwap: swap{
|
||||||
|
ExpireHeight: 1000,
|
||||||
|
CloseBlock: 900,
|
||||||
|
Status: types.SWAP_STATUS_EXPIRED,
|
||||||
|
Direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
newSwap: swap{
|
||||||
|
ExpireHeight: 1,
|
||||||
|
CloseBlock: 900,
|
||||||
|
Status: types.SWAP_STATUS_EXPIRED,
|
||||||
|
Direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
s.Run(tc.name, func() {
|
||||||
|
oldSwaps := types.AtomicSwaps{
|
||||||
|
{
|
||||||
|
Amount: sdk.NewCoins(sdk.NewCoin("bnb", sdkmath.NewInt(12))),
|
||||||
|
RandomNumberHash: bytes.HexBytes{},
|
||||||
|
ExpireHeight: tc.oldSwap.ExpireHeight,
|
||||||
|
Timestamp: 1110,
|
||||||
|
Sender: s.addresses[0],
|
||||||
|
Recipient: s.addresses[1],
|
||||||
|
RecipientOtherChain: s.addresses[0].String(),
|
||||||
|
SenderOtherChain: s.addresses[1].String(),
|
||||||
|
ClosedBlock: tc.oldSwap.CloseBlock,
|
||||||
|
Status: tc.oldSwap.Status,
|
||||||
|
CrossChain: true,
|
||||||
|
Direction: tc.oldSwap.Direction,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expectedSwaps := types.AtomicSwaps{
|
||||||
|
{
|
||||||
|
Amount: sdk.NewCoins(sdk.NewCoin("bnb", sdkmath.NewInt(12))),
|
||||||
|
RandomNumberHash: bytes.HexBytes{},
|
||||||
|
ExpireHeight: tc.newSwap.ExpireHeight,
|
||||||
|
Timestamp: 1110,
|
||||||
|
Sender: s.addresses[0],
|
||||||
|
Recipient: s.addresses[1],
|
||||||
|
RecipientOtherChain: s.addresses[0].String(),
|
||||||
|
SenderOtherChain: s.addresses[1].String(),
|
||||||
|
ClosedBlock: tc.newSwap.CloseBlock,
|
||||||
|
Status: tc.newSwap.Status,
|
||||||
|
CrossChain: true,
|
||||||
|
Direction: tc.newSwap.Direction,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s.v16genstate.AtomicSwaps = oldSwaps
|
||||||
|
genState := Migrate(s.v16genstate)
|
||||||
|
s.Require().Len(genState.AtomicSwaps, 1)
|
||||||
|
s.Equal(expectedSwaps, genState.AtomicSwaps)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(migrateTestSuite))
|
||||||
|
}
|
212
x/bep3/legacy/v0_17/testdata/v16-bep3.json
vendored
Normal file
212
x/bep3/legacy/v0_17/testdata/v16-bep3.json
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
{
|
||||||
|
"atomic_swaps": [
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "1999955998",
|
||||||
|
"denom": "btcb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "838115",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_INCOMING",
|
||||||
|
"expire_height": "838627",
|
||||||
|
"random_number_hash": "6F1CF8F2E13A0C0F0A359F54E47E4E265D766B8E006D2F00BDF994ABDEF1E9E4",
|
||||||
|
"recipient": "kava1fl2hs6y9vz986g5v52pdan9ga923n9mn5cxxkw",
|
||||||
|
"recipient_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
|
||||||
|
"sender": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
|
||||||
|
"sender_other_chain": "bnb19k9wuv2j7c7ck8tmc7kav0r0cnt3esmkrpf25x",
|
||||||
|
"status": "SWAP_STATUS_COMPLETED",
|
||||||
|
"timestamp": "1636034914"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "19000000000",
|
||||||
|
"denom": "bnb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "1712118",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_OUTGOING",
|
||||||
|
"expire_height": "1736797",
|
||||||
|
"random_number_hash": "280EB832A37F2265CC82F3957CE603AAD57BAD7038B876A1F28953AFA29FA1C3",
|
||||||
|
"recipient": "kava1r4v2zdhdalfj2ydazallqvrus9fkphmglhn6u6",
|
||||||
|
"recipient_other_chain": "bnb18nsgj50zvc4uq93w4j0ltz5gaxhwv7aq4qnq0p",
|
||||||
|
"sender": "kava1zw6gg4ztvly7zf25pa33mclav3spvj3ympxxna",
|
||||||
|
"sender_other_chain": "bnb1jh7uv2rm6339yue8k4mj9406k3509kr4wt5nxn",
|
||||||
|
"status": "SWAP_STATUS_COMPLETED",
|
||||||
|
"timestamp": "1641976566"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "999595462080",
|
||||||
|
"denom": "busd"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "787122",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_INCOMING",
|
||||||
|
"expire_height": "811799",
|
||||||
|
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
|
||||||
|
"recipient": "kava1eufgf0w9d7hf5mgtek4zr2upkxag9stmzx6unl",
|
||||||
|
"recipient_other_chain": "bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8",
|
||||||
|
"sender": "kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu",
|
||||||
|
"sender_other_chain": "bnb1vl3wn4x8kqajg2j9wxa5y5amgzdxchutkxr6at",
|
||||||
|
"status": "SWAP_STATUS_EXPIRED",
|
||||||
|
"timestamp": "1635694492"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "999595462080",
|
||||||
|
"denom": "busd"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "787122",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_OUTGOING",
|
||||||
|
"expire_height": "811799",
|
||||||
|
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
|
||||||
|
"recipient": "kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu",
|
||||||
|
"recipient_other_chain": "bnb1vl3wn4x8kqajg2j9wxa5y5amgzdxchutkxr6at",
|
||||||
|
"sender": "kava1eufgf0w9d7hf5mgtek4zr2upkxag9stmzx6unl",
|
||||||
|
"sender_other_chain": "bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8",
|
||||||
|
"status": "SWAP_STATUS_EXPIRED",
|
||||||
|
"timestamp": "1635694492"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "1000000",
|
||||||
|
"denom": "btcb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "0",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_OUTGOING",
|
||||||
|
"expire_height": "1730589",
|
||||||
|
"random_number_hash": "A74EA1AB58D312FDF1E872D18583CACCF294E639DDA4F303939E9ADCEC081D93",
|
||||||
|
"recipient": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
|
||||||
|
"recipient_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
|
||||||
|
"sender": "kava1d2u28azje7rhqyjtxc2ex8q0cxxpw7dfm7ltq5",
|
||||||
|
"sender_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
|
||||||
|
"status": "SWAP_STATUS_OPEN",
|
||||||
|
"timestamp": "1641934114"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "1000000",
|
||||||
|
"denom": "btcb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "0",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_INCOMING",
|
||||||
|
"expire_height": "1740000",
|
||||||
|
"random_number_hash": "39E9ADCEC081D93A74EA1A83CACCF294E639DDA4F3039B58D312FDF1E872D185",
|
||||||
|
"recipient": "kava1d2u28azje7rhqyjtxc2ex8q0cxxpw7dfm7ltq5",
|
||||||
|
"recipient_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
|
||||||
|
"sender": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
|
||||||
|
"sender_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
|
||||||
|
"status": "SWAP_STATUS_OPEN",
|
||||||
|
"timestamp": "1641934114"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"params": {
|
||||||
|
"asset_params": [
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "0",
|
||||||
|
"denom": "btcb",
|
||||||
|
"deputy_address": "kava1kla4wl0ccv7u85cemvs3y987hqk0afcv7vue84",
|
||||||
|
"fixed_fee": "2",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "2000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "3",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "100000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "144",
|
||||||
|
"denom": "xrpb",
|
||||||
|
"deputy_address": "kava14q5sawxdxtpap5x5sgzj7v4sp3ucncjlpuk3hs",
|
||||||
|
"fixed_fee": "100000",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "250000000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "100001",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "2000000000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "714",
|
||||||
|
"denom": "bnb",
|
||||||
|
"deputy_address": "kava1agcvt07tcw0tglu0hmwdecsnuxp2yd45f3avgm",
|
||||||
|
"fixed_fee": "1000",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "500000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "1001",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "100000000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "727",
|
||||||
|
"denom": "busd",
|
||||||
|
"deputy_address": "kava1j9je7f6s0v6k7dmgv6u5k5ru202f5ffsc7af04",
|
||||||
|
"fixed_fee": "20000",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "100000000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "20001",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "2000000000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"previous_block_time": "1970-01-01T00:00:00Z",
|
||||||
|
"supplies": [
|
||||||
|
{
|
||||||
|
"current_supply": {
|
||||||
|
"amount": "30467559434006",
|
||||||
|
"denom": "bnb"
|
||||||
|
},
|
||||||
|
"incoming_supply": {
|
||||||
|
"amount": "0",
|
||||||
|
"denom": "bnb"
|
||||||
|
},
|
||||||
|
"outgoing_supply": {
|
||||||
|
"amount": "0",
|
||||||
|
"denom": "bnb"
|
||||||
|
},
|
||||||
|
"time_elapsed": "0s",
|
||||||
|
"time_limited_current_supply": {
|
||||||
|
"amount": "0",
|
||||||
|
"denom": "bnb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
212
x/bep3/legacy/v0_17/testdata/v17-bep3.json
vendored
Normal file
212
x/bep3/legacy/v0_17/testdata/v17-bep3.json
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
{
|
||||||
|
"atomic_swaps": [
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "1999955998",
|
||||||
|
"denom": "btcb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "1",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_INCOMING",
|
||||||
|
"expire_height": "838627",
|
||||||
|
"random_number_hash": "6F1CF8F2E13A0C0F0A359F54E47E4E265D766B8E006D2F00BDF994ABDEF1E9E4",
|
||||||
|
"recipient": "kava1fl2hs6y9vz986g5v52pdan9ga923n9mn5cxxkw",
|
||||||
|
"recipient_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
|
||||||
|
"sender": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
|
||||||
|
"sender_other_chain": "bnb19k9wuv2j7c7ck8tmc7kav0r0cnt3esmkrpf25x",
|
||||||
|
"status": "SWAP_STATUS_COMPLETED",
|
||||||
|
"timestamp": "1636034914"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "19000000000",
|
||||||
|
"denom": "bnb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "1",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_OUTGOING",
|
||||||
|
"expire_height": "1736797",
|
||||||
|
"random_number_hash": "280EB832A37F2265CC82F3957CE603AAD57BAD7038B876A1F28953AFA29FA1C3",
|
||||||
|
"recipient": "kava1r4v2zdhdalfj2ydazallqvrus9fkphmglhn6u6",
|
||||||
|
"recipient_other_chain": "bnb18nsgj50zvc4uq93w4j0ltz5gaxhwv7aq4qnq0p",
|
||||||
|
"sender": "kava1zw6gg4ztvly7zf25pa33mclav3spvj3ympxxna",
|
||||||
|
"sender_other_chain": "bnb1jh7uv2rm6339yue8k4mj9406k3509kr4wt5nxn",
|
||||||
|
"status": "SWAP_STATUS_COMPLETED",
|
||||||
|
"timestamp": "1641976566"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "999595462080",
|
||||||
|
"denom": "busd"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "787122",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_INCOMING",
|
||||||
|
"expire_height": "1",
|
||||||
|
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
|
||||||
|
"recipient": "kava1eufgf0w9d7hf5mgtek4zr2upkxag9stmzx6unl",
|
||||||
|
"recipient_other_chain": "bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8",
|
||||||
|
"sender": "kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu",
|
||||||
|
"sender_other_chain": "bnb1vl3wn4x8kqajg2j9wxa5y5amgzdxchutkxr6at",
|
||||||
|
"status": "SWAP_STATUS_EXPIRED",
|
||||||
|
"timestamp": "1635694492"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "999595462080",
|
||||||
|
"denom": "busd"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "787122",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_OUTGOING",
|
||||||
|
"expire_height": "1",
|
||||||
|
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
|
||||||
|
"recipient": "kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu",
|
||||||
|
"recipient_other_chain": "bnb1vl3wn4x8kqajg2j9wxa5y5amgzdxchutkxr6at",
|
||||||
|
"sender": "kava1eufgf0w9d7hf5mgtek4zr2upkxag9stmzx6unl",
|
||||||
|
"sender_other_chain": "bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8",
|
||||||
|
"status": "SWAP_STATUS_EXPIRED",
|
||||||
|
"timestamp": "1635694492"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "1000000",
|
||||||
|
"denom": "btcb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "0",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_OUTGOING",
|
||||||
|
"expire_height": "24687",
|
||||||
|
"random_number_hash": "A74EA1AB58D312FDF1E872D18583CACCF294E639DDA4F303939E9ADCEC081D93",
|
||||||
|
"recipient": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
|
||||||
|
"recipient_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
|
||||||
|
"sender": "kava1d2u28azje7rhqyjtxc2ex8q0cxxpw7dfm7ltq5",
|
||||||
|
"sender_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
|
||||||
|
"status": "SWAP_STATUS_OPEN",
|
||||||
|
"timestamp": "1641934114"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"amount": "1000000",
|
||||||
|
"denom": "btcb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"closed_block": "0",
|
||||||
|
"cross_chain": true,
|
||||||
|
"direction": "SWAP_DIRECTION_INCOMING",
|
||||||
|
"expire_height": "1",
|
||||||
|
"random_number_hash": "39E9ADCEC081D93A74EA1A83CACCF294E639DDA4F3039B58D312FDF1E872D185",
|
||||||
|
"recipient": "kava1d2u28azje7rhqyjtxc2ex8q0cxxpw7dfm7ltq5",
|
||||||
|
"recipient_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
|
||||||
|
"sender": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
|
||||||
|
"sender_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
|
||||||
|
"status": "SWAP_STATUS_EXPIRED",
|
||||||
|
"timestamp": "1641934114"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"params": {
|
||||||
|
"asset_params": [
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "0",
|
||||||
|
"denom": "btcb",
|
||||||
|
"deputy_address": "kava1kla4wl0ccv7u85cemvs3y987hqk0afcv7vue84",
|
||||||
|
"fixed_fee": "2",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "2000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "3",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "100000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "144",
|
||||||
|
"denom": "xrpb",
|
||||||
|
"deputy_address": "kava14q5sawxdxtpap5x5sgzj7v4sp3ucncjlpuk3hs",
|
||||||
|
"fixed_fee": "100000",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "250000000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "100001",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "2000000000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "714",
|
||||||
|
"denom": "bnb",
|
||||||
|
"deputy_address": "kava1agcvt07tcw0tglu0hmwdecsnuxp2yd45f3avgm",
|
||||||
|
"fixed_fee": "1000",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "500000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "1001",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "100000000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"coin_id": "727",
|
||||||
|
"denom": "busd",
|
||||||
|
"deputy_address": "kava1j9je7f6s0v6k7dmgv6u5k5ru202f5ffsc7af04",
|
||||||
|
"fixed_fee": "20000",
|
||||||
|
"max_block_lock": "86400",
|
||||||
|
"max_swap_amount": "100000000000000",
|
||||||
|
"min_block_lock": "24686",
|
||||||
|
"min_swap_amount": "20001",
|
||||||
|
"supply_limit": {
|
||||||
|
"limit": "2000000000000000",
|
||||||
|
"time_based_limit": "0",
|
||||||
|
"time_limited": false,
|
||||||
|
"time_period": "0s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"previous_block_time": "1970-01-01T00:00:00Z",
|
||||||
|
"supplies": [
|
||||||
|
{
|
||||||
|
"current_supply": {
|
||||||
|
"amount": "30467559434006",
|
||||||
|
"denom": "bnb"
|
||||||
|
},
|
||||||
|
"incoming_supply": {
|
||||||
|
"amount": "0",
|
||||||
|
"denom": "bnb"
|
||||||
|
},
|
||||||
|
"outgoing_supply": {
|
||||||
|
"amount": "0",
|
||||||
|
"denom": "bnb"
|
||||||
|
},
|
||||||
|
"time_elapsed": "0s",
|
||||||
|
"time_limited_current_supply": {
|
||||||
|
"amount": "0",
|
||||||
|
"denom": "bnb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
144
x/bep3/module.go
Normal file
144
x/bep3/module.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package bep3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
|
||||||
|
abci "github.com/cometbft/cometbft/abci/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/client/cli"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ module.AppModule = AppModule{}
|
||||||
|
_ module.AppModuleBasic = AppModuleBasic{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppModuleBasic defines the basic application module used by the bep3 module.
|
||||||
|
type AppModuleBasic struct{}
|
||||||
|
|
||||||
|
// Name returns the bep3 module's name.
|
||||||
|
func (AppModuleBasic) Name() string {
|
||||||
|
return types.ModuleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterLegacyAminoCodec register module codec
|
||||||
|
func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
||||||
|
types.RegisterLegacyAminoCodec(cdc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultGenesis returns default genesis state as raw bytes for the bep3
|
||||||
|
// module.
|
||||||
|
func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
|
||||||
|
gs := types.DefaultGenesisState()
|
||||||
|
return cdc.MustMarshalJSON(&gs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateGenesis performs genesis state validation for the bep3 module.
|
||||||
|
func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error {
|
||||||
|
var gs types.GenesisState
|
||||||
|
err := cdc.UnmarshalJSON(bz, &gs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gs.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInterfaces implements InterfaceModule.RegisterInterfaces
|
||||||
|
func (a AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
|
||||||
|
types.RegisterInterfaces(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the gov module.
|
||||||
|
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsensusVersion implements AppModule/ConsensusVersion.
|
||||||
|
func (AppModule) ConsensusVersion() uint64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxCmd returns the root tx command for the bep3 module.
|
||||||
|
func (AppModuleBasic) GetTxCmd() *cobra.Command {
|
||||||
|
return cli.GetTxCmd()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryCmd returns no root query command for the bep3 module.
|
||||||
|
func (AppModuleBasic) GetQueryCmd() *cobra.Command {
|
||||||
|
return cli.GetQueryCmd(types.StoreKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
//____________________________________________________________________________
|
||||||
|
|
||||||
|
// AppModule implements the sdk.AppModule interface.
|
||||||
|
type AppModule struct {
|
||||||
|
AppModuleBasic
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
accountKeeper types.AccountKeeper
|
||||||
|
bankKeeper types.BankKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppModule creates a new AppModule object
|
||||||
|
func NewAppModule(keeper keeper.Keeper, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper) AppModule {
|
||||||
|
return AppModule{
|
||||||
|
AppModuleBasic: AppModuleBasic{},
|
||||||
|
keeper: keeper,
|
||||||
|
accountKeeper: accountKeeper,
|
||||||
|
bankKeeper: bankKeeper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the bep3 module's name.
|
||||||
|
func (AppModule) Name() string {
|
||||||
|
return types.ModuleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInvariants registers the bep3 module invariants.
|
||||||
|
func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
|
||||||
|
|
||||||
|
// RegisterServices registers module services.
|
||||||
|
func (am AppModule) RegisterServices(cfg module.Configurator) {
|
||||||
|
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
|
||||||
|
types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitGenesis performs genesis initialization for the bep3 module. It returns
|
||||||
|
// no validator updates.
|
||||||
|
func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate {
|
||||||
|
var genState types.GenesisState
|
||||||
|
// Initialize global index to index in genesis state
|
||||||
|
cdc.MustUnmarshalJSON(gs, &genState)
|
||||||
|
|
||||||
|
InitGenesis(ctx, am.keeper, am.accountKeeper, &genState)
|
||||||
|
return []abci.ValidatorUpdate{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportGenesis returns the exported genesis state as raw bytes for the bep3
|
||||||
|
// module.
|
||||||
|
func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
|
||||||
|
gs := ExportGenesis(ctx, am.keeper)
|
||||||
|
return cdc.MustMarshalJSON(&gs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginBlock returns the begin blocker for the bep3 module.
|
||||||
|
func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
|
||||||
|
BeginBlocker(ctx, am.keeper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndBlock returns the end blocker for the bep3 module. It returns no validator updates.
|
||||||
|
func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
|
||||||
|
return []abci.ValidatorUpdate{}
|
||||||
|
}
|
40
x/bep3/spec/01_concepts.md
Normal file
40
x/bep3/spec/01_concepts.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!--
|
||||||
|
order: 1
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Concepts
|
||||||
|
|
||||||
|
The BEP3 module implements the [BEP3 protocol](https://github.com/binance-chain/BEPs/blob/master/BEP3.md) for secure cross-chain asset transfers between Kava and other BEP3 compatible chains, such as Binance Chain. Transactions are witnessed and relayed between the two blockchains by Binance's BEP3 deputy process. The deputy maintains an address on both chains and is responsible for delivering tokens upon the successful completion of an Atomic Swap. Learn more about the BEP3 deputy process [here](https://github.com/binance-chain/bep3-deputy).
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
Kava
|
||||||
|
- The deputy’s Kava address on mainnet is **kava1r4v2zdhdalfj2ydazallqvrus9fkphmglhn6u6**.
|
||||||
|
- Kava's official API endpoint is https://kava3.data.kava.io.
|
||||||
|
|
||||||
|
Binance Chain
|
||||||
|
- The deputy’s Binance Chain address on mainnet is **bnb1jh7uv2rm6339yue8k4mj9406k3509kr4wt5nxn**.
|
||||||
|
- We recommend using https://testnet-dex.binance.org/ as Binance Chain’s API endpoint.
|
||||||
|
|
||||||
|
Kava's [JavaScript SDK](https://github.com/Kava-Labs/javascript-sdk) and Binance Chain’s [JavaScript SDK](https://github.com/binance-chain/javascript-sdk) can be used to create, claim, and refund swaps.
|
||||||
|
|
||||||
|
## Binance Chain to Kava
|
||||||
|
|
||||||
|
When a user wants to transfer tokens from Binance Chain to Kava, the following steps are taken:
|
||||||
|
1. User’s tokens are locked on Binance Chain along with the hash of a secret only known to the user. If the secret is not revealed before the deadline, the tokens are refundable.
|
||||||
|
2. The deputy sends a message to Kava saying “a user has locked X tokens, if their secret is revealed before the deadline issue them an equivalent amount of pegged tokens”.
|
||||||
|
3. The user reveals the secret on Kava and receives the pegged tokens.
|
||||||
|
4. The deputy relays the secret to Binance Chain and the original tokens are locked permanently.
|
||||||
|
|
||||||
|
|
||||||
|
![Binance Chain to Kava Diagram](./diagrams/BEP3_binance_chain_to_kava.jpg)
|
||||||
|
|
||||||
|
## Kava to Binance Chain
|
||||||
|
1. When a user wants to transfer tokens from Kava to Binance Chain by redeeming pegged tokens, the following steps are taken:
|
||||||
|
User’s pegged tokens are locked on Kava along with the hash of a secret only known to the user. If the secret is not revealed before the deadline, the tokens are refundable.
|
||||||
|
2. The deputy sends a message to Binance Chain saying “a user has locked X pegged tokens, if their secret is revealed before the deadline issue them an equivalent amount of tokens”.
|
||||||
|
3. The user reveals the secret on Binance Chain and receives the tokens.
|
||||||
|
4. The deputy relays the secret to Kava and the pegged tokens are locked permanently.
|
||||||
|
|
||||||
|
|
||||||
|
![Kava to Binance Chain Diagram](./diagrams/BEP3_kava_to_binance_chain.jpg)
|
||||||
|
|
100
x/bep3/spec/02_state.md
Normal file
100
x/bep3/spec/02_state.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<!--
|
||||||
|
order: 2
|
||||||
|
-->
|
||||||
|
|
||||||
|
# State
|
||||||
|
|
||||||
|
## Parameters and genesis state
|
||||||
|
|
||||||
|
`parameters` define the rules according to which swaps are executed. Parameter updates can be made via on-chain parameter update proposals.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Params governance parameters for bep3 module
|
||||||
|
type Params struct {
|
||||||
|
BnbDeputyAddress sdk.AccAddress `json:"bnb_deputy_address" yaml:"bnb_deputy_address"` // Bnbchain deputy address
|
||||||
|
BnbDeputyFixedFee sdkmath.Int `json:"bnb_deputy_fixed_fee" yaml:"bnb_deputy_fixed_fee"` // Deputy fixed fee in BNB
|
||||||
|
MinAmount sdkmath.Int `json:"min_amount" yaml:"min_amount"` // Minimum swap amount
|
||||||
|
MaxAmount sdkmath.Int `json:"max_amount" yaml:"max_amount"` // Maximum swap amount
|
||||||
|
MinBlockLock uint64 `json:"min_block_lock" yaml:"min_block_lock"` // Minimum swap block lock
|
||||||
|
MaxBlockLock uint64 `json:"max_block_lock" yaml:"max_block_lock"` // Maximum swap block lock
|
||||||
|
SupportedAssets AssetParams `json:"supported_assets" yaml:"supported_assets"` // Supported assets
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetParam governance parameters for each asset within a supported chain
|
||||||
|
type AssetParam struct {
|
||||||
|
Denom string `json:"denom" yaml:"denom"` // name of the asset
|
||||||
|
CoinID int `json:"coin_id" yaml:"coin_id"` // internationally recognized coin ID
|
||||||
|
Limit sdkmath.Int `json:"limit" yaml:"limit"` // asset supply limit
|
||||||
|
Active bool `json:"active" yaml:"active"` // denotes if asset is active or paused
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the bep3 module to resume.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// GenesisState - all bep3 state that must be provided at genesis
|
||||||
|
type GenesisState struct {
|
||||||
|
Params Params `json:"params" yaml:"params"`
|
||||||
|
AtomicSwaps AtomicSwaps `json:"atomic_swaps" yaml:"atomic_swaps"`
|
||||||
|
AssetSupplies AssetSupplies `json:"assets_supplies" yaml:"assets_supplies"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
AtomicSwap stores information about an individual atomic swap, including the sender, recipient, amount, random number hash (used to validate the secret and unlock funds), the status (open, completed, or expired). There are two types of atomic swaps:
|
||||||
|
- Incoming: assets are being sent to Kava from another blockchain.
|
||||||
|
- Outgoing: assets are being send to another blockchain from Kava.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// AtomicSwap contains the information for an atomic swap
|
||||||
|
type AtomicSwap struct {
|
||||||
|
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||||
|
RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"`
|
||||||
|
ExpireHeight int64 `json:"expire_height" yaml:"expire_height"`
|
||||||
|
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
|
||||||
|
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||||
|
Recipient sdk.AccAddress `json:"recipient" yaml:"recipient"`
|
||||||
|
SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"`
|
||||||
|
RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"`
|
||||||
|
ClosedBlock int64 `json:"closed_block" yaml:"closed_block"`
|
||||||
|
Status SwapStatus `json:"status" yaml:"status"`
|
||||||
|
Direction SwapDirection `json:"direction" yaml:"direction"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapStatus is the status of an AtomicSwap
|
||||||
|
type SwapStatus byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
NULL SwapStatus = 0x00
|
||||||
|
Open SwapStatus = 0x01
|
||||||
|
Completed SwapStatus = 0x02
|
||||||
|
Expired SwapStatus = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
|
// SwapDirection is the direction of an AtomicSwap
|
||||||
|
type SwapDirection byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
INVALID SwapDirection = 0x00
|
||||||
|
Incoming SwapDirection = 0x01
|
||||||
|
Outgoing SwapDirection = 0x02
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
AssetSupply stores information about an individual asset's BEP3 supply:
|
||||||
|
- Incoming supply: total amount in incoming swaps (being sent to the chain).
|
||||||
|
- Outgoing supply: total amount in outgoing swaps (being sent off the chain). It cannot be greater than the current supply.
|
||||||
|
- Current supply: the amount that the deputy has released - it is the active supply on Kava. It is equal to the total amount successfully claimed from incoming swaps minus the total amount claimed from outgoing swaps.
|
||||||
|
- Supply limit: the maximum amount currently allowed on Kava. The supply limit can be increased by Kava's stability committee, subject to an on-chain proposal vote.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// AssetSupply contains information about an asset's supply
|
||||||
|
type AssetSupply struct {
|
||||||
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
|
IncomingSupply sdk.Coin `json:"incoming_supply" yaml:"incoming_supply"`
|
||||||
|
OutgoingSupply sdk.Coin `json:"outgoing_supply" yaml:"outgoing_supply"`
|
||||||
|
CurrentSupply sdk.Coin `json:"current_supply" yaml:"current_supply"`
|
||||||
|
SupplyLimit sdk.Coin `json:"supply_limit" yaml:"supply_limit"`
|
||||||
|
}
|
||||||
|
```
|
48
x/bep3/spec/03_messages.md
Normal file
48
x/bep3/spec/03_messages.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!--
|
||||||
|
order: 3
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Messages
|
||||||
|
|
||||||
|
## Create swap
|
||||||
|
|
||||||
|
Swaps are created using the `MsgCreateAtomicSwap` message type.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// MsgCreateAtomicSwap contains an AtomicSwap struct
|
||||||
|
type MsgCreateAtomicSwap struct {
|
||||||
|
From sdk.AccAddress `json:"from" yaml:"from"`
|
||||||
|
To sdk.AccAddress `json:"to" yaml:"to"`
|
||||||
|
RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"`
|
||||||
|
SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"`
|
||||||
|
RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"`
|
||||||
|
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
|
||||||
|
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||||
|
HeightSpan int64 `json:"height_span" yaml:"height_span"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Claim swap
|
||||||
|
|
||||||
|
Active swaps are claimed using the `MsgClaimAtomicSwap` message type.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// MsgClaimAtomicSwap defines a AtomicSwap claim
|
||||||
|
type MsgClaimAtomicSwap struct {
|
||||||
|
From sdk.AccAddress `json:"from" yaml:"from"`
|
||||||
|
SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"`
|
||||||
|
RandomNumber tmbytes.HexBytes `json:"random_number" yaml:"random_number"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Refund swap
|
||||||
|
|
||||||
|
Expired swaps are refunded using the `MsgRefundAtomicSwap` message type.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// MsgRefundAtomicSwap defines a refund msg
|
||||||
|
type MsgRefundAtomicSwap struct {
|
||||||
|
From sdk.AccAddress `json:"from" yaml:"from"`
|
||||||
|
SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"`
|
||||||
|
}
|
||||||
|
```
|
55
x/bep3/spec/04_events.md
Normal file
55
x/bep3/spec/04_events.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!--
|
||||||
|
order: 4
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Events
|
||||||
|
|
||||||
|
The `x/bep3` module emits the following events:
|
||||||
|
|
||||||
|
## Handlers
|
||||||
|
|
||||||
|
### MsgCreateAtomicSwap
|
||||||
|
|
||||||
|
| Type | Attribute Key | Attribute Value |
|
||||||
|
|--------------------|--------------------|---------------------------|
|
||||||
|
| create_atomic_swap | sender | `{sender address}` |
|
||||||
|
| create_atomic_swap | recipient | `{recipient address}` |
|
||||||
|
| create_atomic_swap | atomic_swap_id | `{swap ID}` |
|
||||||
|
| create_atomic_swap | random_number_hash | `{random number hash}` |
|
||||||
|
| create_atomic_swap | timestamp | `{timestamp}` |
|
||||||
|
| create_atomic_swap | sender_other_chain | `{sender other chain}` |
|
||||||
|
| create_atomic_swap | expire_height | `{swap expiration block}` |
|
||||||
|
| create_atomic_swap | amount | `{coin amount}` |
|
||||||
|
| create_atomic_swap | direction | `{incoming or outgoing}` |
|
||||||
|
| message | module | bep3 |
|
||||||
|
| message | sender | `{sender address}` |
|
||||||
|
|
||||||
|
### MsgClaimAtomicSwap
|
||||||
|
|
||||||
|
| Type | Attribute Key | Attribute Value |
|
||||||
|
|--------------------|--------------------|---------------------------|
|
||||||
|
| claim_atomic_swap | claim_sender | `{sender address}` |
|
||||||
|
| claim_atomic_swap | recipient | `{recipient address}` |
|
||||||
|
| claim_atomic_swap | atomic_swap_id | `{swap ID}` |
|
||||||
|
| claim_atomic_swap | random_number_hash | `{random number hash}` |
|
||||||
|
| claim_atomic_swap | random_number | `{secret random number}` |
|
||||||
|
| message | module | bep3 |
|
||||||
|
| message | sender | `{sender address}` |
|
||||||
|
|
||||||
|
## MsgRefundAtomicSwap
|
||||||
|
|
||||||
|
| Type | Attribute Key | Attribute Value |
|
||||||
|
|--------------------|--------------------|---------------------------|
|
||||||
|
| refund_atomic_swap | refund_sender | `{sender address}` |
|
||||||
|
| refund_atomic_swap | sender | `{swap creator address}` |
|
||||||
|
| refund_atomic_swap | atomic_swap_id | `{swap ID}` |
|
||||||
|
| refund_atomic_swap | random_number_hash | `{random number hash}` |
|
||||||
|
| message | module | bep3 |
|
||||||
|
| message | sender | `{sender address}` |
|
||||||
|
|
||||||
|
## BeginBlock
|
||||||
|
|
||||||
|
| Type | Attribute Key | Attribute Value |
|
||||||
|
|---------------|------------------|----------------------------------|
|
||||||
|
| swaps_expired | atomic_swap_ids | `{array of swap IDs}` |
|
||||||
|
| swaps_expired | expiration_block | `{block height at expiration}` |
|
26
x/bep3/spec/05_params.md
Normal file
26
x/bep3/spec/05_params.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!--
|
||||||
|
order: 5
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
|
||||||
|
The bep3 module contains the following parameters:
|
||||||
|
|
||||||
|
| Key | Type | Example | Description |
|
||||||
|
| ----------------- | -------------- | --------------------------------------------- | -------------------------- |
|
||||||
|
| BnbDeputyAddress | sdk.AccAddress | "kava1r4v2zdhdalfj2ydazallqvrus9fkphmglhn6u6" | deputy's Kava address |
|
||||||
|
| BnbDeputyFixedFee | sdkmath.Int | sdkmath.NewInt(1000) | deputy's fixed bnb fee |
|
||||||
|
| MinAmount | sdkmath.Int | sdkmath.NewInt(0) | minimum swap amount |
|
||||||
|
| MaxAmount | sdkmath.Int | sdkmath.NewInt(1000000000000) | maximum swap amount |
|
||||||
|
| MinBlockLock | uint64 | 220 | minimum swap expire height |
|
||||||
|
| MaxBlockLock | uint64 | 270 | maximum swap expire height |
|
||||||
|
| SupportedAssets | AssetParams | []AssetParam | array of supported assets |
|
||||||
|
|
||||||
|
Each AssetParam has the following parameters:
|
||||||
|
|
||||||
|
| Key | Type | Example | Description |
|
||||||
|
| ----------------- | ----------- | ------------------- | ----------------------------- |
|
||||||
|
| AssetParam.Denom | string | "bnb" | asset's name |
|
||||||
|
| AssetParam.CoinID | int64 | 714 | asset's international coin ID |
|
||||||
|
| AssetParam.Limit | sdkmath.Int | sdkmath.NewInt(100) | asset's supply limit |
|
||||||
|
| AssetParam.Active | boolean | true | asset's state: live or paused |
|
50
x/bep3/spec/06_begin_block.md
Normal file
50
x/bep3/spec/06_begin_block.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<!--
|
||||||
|
order: 6
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Begin Block
|
||||||
|
|
||||||
|
At the start of each block, atomic swaps that meet certain criteria are expired or deleted.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func BeginBlocker(ctx sdk.Context, k Keeper) {
|
||||||
|
k.UpdateExpiredAtomicSwaps(ctx)
|
||||||
|
k.DeleteClosedAtomicSwapsFromLongtermStorage(ctx)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expiration
|
||||||
|
|
||||||
|
If an atomic swap's `ExpireHeight` is greater than the current block height, it will be expired. The logic to expire atomic swaps is as follows:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var expiredSwapIDs []string
|
||||||
|
k.IterateAtomicSwapsByBlock(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool {
|
||||||
|
atomicSwap, found := k.GetAtomicSwap(ctx, id)
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Expire the uncompleted swap and update both indexes
|
||||||
|
atomicSwap.Status = types.Expired
|
||||||
|
k.RemoveFromByBlockIndex(ctx, atomicSwap)
|
||||||
|
k.SetAtomicSwap(ctx, atomicSwap)
|
||||||
|
expiredSwapIDs = append(expiredSwapIDs, hex.EncodeToString(atomicSwap.GetSwapID()))
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deletion
|
||||||
|
|
||||||
|
Atomic swaps are deleted 86400 blocks (one week, assuming a block time of 7 seconds) after being completed. The logic to delete atomic swaps is as follows:
|
||||||
|
|
||||||
|
```go
|
||||||
|
k.IterateAtomicSwapsLongtermStorage(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool {
|
||||||
|
swap, found := k.GetAtomicSwap(ctx, id)
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k.RemoveAtomicSwap(ctx, swap.GetSwapID())
|
||||||
|
k.RemoveFromLongtermStorage(ctx, swap)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
```
|
27
x/bep3/spec/README.md
Normal file
27
x/bep3/spec/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!--
|
||||||
|
order: 0
|
||||||
|
title: "BEP3 Overview"
|
||||||
|
parent:
|
||||||
|
title: "bep3"
|
||||||
|
-->
|
||||||
|
|
||||||
|
# `bep3`
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
|
1. **[Concepts](01_concepts.md)**
|
||||||
|
2. **[State](02_state.md)**
|
||||||
|
3. **[Messages](03_messages.md)**
|
||||||
|
4. **[Events](04_events.md)**
|
||||||
|
5. **[Params](05_params.md)**
|
||||||
|
6. **[BeginBlock](06_begin_block.md)**
|
||||||
|
|
||||||
|
## Abstract
|
||||||
|
|
||||||
|
`x/bep3` is a module that handles cross-chain atomic swaps between Kava and blockchains that implement the BEP3 protocol. Atomic swaps are created, then either claimed before their expiration block or refunded after they've expired.
|
||||||
|
|
||||||
|
Several user interfaces support Kava BEP3 swaps:
|
||||||
|
- [Trust Wallet](https://trustwallet.com/)
|
||||||
|
- [Cosmostation](https://wallet.cosmostation.io/?network=kava)
|
||||||
|
- [Frontier Wallet](https://frontierwallet.com/)
|
||||||
|
|
||||||
|
Swaps can also be created, claimed, and refunded using Kava's [Javascript SDK](https://github.com/Kava-Labs/javascript-sdk) or CLI.
|
BIN
x/bep3/spec/diagrams/BEP3_binance_chain_to_kava.jpg
Normal file
BIN
x/bep3/spec/diagrams/BEP3_binance_chain_to_kava.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
x/bep3/spec/diagrams/BEP3_kava_to_binance_chain.jpg
Normal file
BIN
x/bep3/spec/diagrams/BEP3_kava_to_binance_chain.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
2411
x/bep3/types/bep3.pb.go
Normal file
2411
x/bep3/types/bep3.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
49
x/bep3/types/codec.go
Normal file
49
x/bep3/types/codec.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
|
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/msgservice"
|
||||||
|
authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterLegacyAminoCodec registers all the necessary types and interfaces for the
|
||||||
|
// bep3 module.
|
||||||
|
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
||||||
|
cdc.RegisterConcrete(&MsgCreateAtomicSwap{}, "bep3/MsgCreateAtomicSwap", nil)
|
||||||
|
cdc.RegisterConcrete(&MsgRefundAtomicSwap{}, "bep3/MsgRefundAtomicSwap", nil)
|
||||||
|
cdc.RegisterConcrete(&MsgClaimAtomicSwap{}, "bep3/MsgClaimAtomicSwap", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterInterfaces(registry types.InterfaceRegistry) {
|
||||||
|
registry.RegisterImplementations((*sdk.Msg)(nil),
|
||||||
|
&MsgCreateAtomicSwap{},
|
||||||
|
&MsgRefundAtomicSwap{},
|
||||||
|
&MsgClaimAtomicSwap{},
|
||||||
|
)
|
||||||
|
|
||||||
|
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
amino = codec.NewLegacyAmino()
|
||||||
|
|
||||||
|
// ModuleCdc references the global x/bep3 module codec. Note, the codec should
|
||||||
|
// ONLY be used in certain instances of tests and for JSON encoding as Amino is
|
||||||
|
// still used for that purpose.
|
||||||
|
//
|
||||||
|
// The actual codec used for serialization should be provided to x/bep3 and
|
||||||
|
// defined at the application level.
|
||||||
|
ModuleCdc = codec.NewAminoCodec(amino)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterLegacyAminoCodec(amino)
|
||||||
|
cryptocodec.RegisterCrypto(amino)
|
||||||
|
|
||||||
|
// Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be
|
||||||
|
// used to properly serialize MsgGrant and MsgExec instances
|
||||||
|
RegisterLegacyAminoCodec(authzcodec.Amino)
|
||||||
|
}
|
38
x/bep3/types/common_test.go
Normal file
38
x/bep3/types/common_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func i(in int64) sdkmath.Int { return sdkmath.NewInt(in) }
|
||||||
|
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
||||||
|
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
||||||
|
func ts(minOffset int) int64 { return tmtime.Now().Add(time.Duration(minOffset) * time.Minute).Unix() }
|
||||||
|
|
||||||
|
func atomicSwaps(count int) types.AtomicSwaps {
|
||||||
|
var swaps types.AtomicSwaps
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
swap := atomicSwap(i)
|
||||||
|
swaps = append(swaps, swap)
|
||||||
|
}
|
||||||
|
return swaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func atomicSwap(index int) types.AtomicSwap {
|
||||||
|
expireOffset := uint64((index * 15) + 360) // Default expire height + offet to match timestamp
|
||||||
|
timestamp := ts(index) // One minute apart
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
|
||||||
|
swap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash, expireOffset, timestamp, zgAddrs[0],
|
||||||
|
zgAddrs[1], binanceAddrs[0].String(), binanceAddrs[1].String(), 1, types.SWAP_STATUS_OPEN, true, types.SWAP_DIRECTION_INCOMING)
|
||||||
|
|
||||||
|
return swap
|
||||||
|
}
|
46
x/bep3/types/errors.go
Normal file
46
x/bep3/types/errors.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import errorsmod "cosmossdk.io/errors"
|
||||||
|
|
||||||
|
// DONTCOVER
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidTimestamp error for when an timestamp is outside of bounds. Assumes block time of 10 seconds.
|
||||||
|
ErrInvalidTimestamp = errorsmod.Register(ModuleName, 2, "timestamp can neither be 15 minutes ahead of the current time, nor 30 minutes later")
|
||||||
|
// ErrInvalidHeightSpan error for when a proposed height span is outside of lock time range
|
||||||
|
ErrInvalidHeightSpan = errorsmod.Register(ModuleName, 3, "height span is outside acceptable range")
|
||||||
|
// ErrInsufficientAmount error for when a swap's amount cannot cover the deputy's fixed fee
|
||||||
|
ErrInsufficientAmount = errorsmod.Register(ModuleName, 4, "amount cannot cover the deputy fixed fee")
|
||||||
|
// ErrAssetNotSupported error for when an asset is not supported
|
||||||
|
ErrAssetNotSupported = errorsmod.Register(ModuleName, 5, "asset not found")
|
||||||
|
// ErrAssetNotActive error for when an asset is currently inactive
|
||||||
|
ErrAssetNotActive = errorsmod.Register(ModuleName, 6, "asset is currently inactive")
|
||||||
|
// ErrAssetSupplyNotFound error for when an asset's supply is not found in the store
|
||||||
|
ErrAssetSupplyNotFound = errorsmod.Register(ModuleName, 7, "asset supply not found in store")
|
||||||
|
// ErrExceedsSupplyLimit error for when the proposed supply increase would put the supply above limit
|
||||||
|
ErrExceedsSupplyLimit = errorsmod.Register(ModuleName, 8, "asset supply over limit")
|
||||||
|
// ErrExceedsAvailableSupply error for when the proposed outgoing amount exceeds the total available supply
|
||||||
|
ErrExceedsAvailableSupply = errorsmod.Register(ModuleName, 9, "outgoing swap exceeds total available supply")
|
||||||
|
// ErrInvalidCurrentSupply error for when the proposed decrease would result in a negative current supplyx
|
||||||
|
ErrInvalidCurrentSupply = errorsmod.Register(ModuleName, 10, "supply decrease puts current asset supply below 0")
|
||||||
|
// ErrInvalidIncomingSupply error for when the proposed decrease would result in a negative incoming supply
|
||||||
|
ErrInvalidIncomingSupply = errorsmod.Register(ModuleName, 11, "supply decrease puts incoming asset supply below 0")
|
||||||
|
// ErrInvalidOutgoingSupply error for when the proposed decrease would result in a negative outgoing supply
|
||||||
|
ErrInvalidOutgoingSupply = errorsmod.Register(ModuleName, 12, "supply decrease puts outgoing asset supply below 0")
|
||||||
|
// ErrInvalidClaimSecret error when a submitted secret doesn't match an AtomicSwap's swapID
|
||||||
|
ErrInvalidClaimSecret = errorsmod.Register(ModuleName, 13, "hashed claim attempt does not match")
|
||||||
|
// ErrAtomicSwapAlreadyExists error for when an AtomicSwap with this swapID already exists
|
||||||
|
ErrAtomicSwapAlreadyExists = errorsmod.Register(ModuleName, 14, "atomic swap already exists")
|
||||||
|
// ErrAtomicSwapNotFound error for when an atomic swap is not found
|
||||||
|
ErrAtomicSwapNotFound = errorsmod.Register(ModuleName, 15, "atomic swap not found")
|
||||||
|
// ErrSwapNotRefundable error for when an AtomicSwap has not expired and cannot be refunded
|
||||||
|
ErrSwapNotRefundable = errorsmod.Register(ModuleName, 16, "atomic swap is still active and cannot be refunded")
|
||||||
|
// ErrSwapNotClaimable error for when an atomic swap is not open and cannot be claimed
|
||||||
|
ErrSwapNotClaimable = errorsmod.Register(ModuleName, 17, "atomic swap is not claimable")
|
||||||
|
// ErrInvalidAmount error for when a swap's amount is outside acceptable range
|
||||||
|
ErrInvalidAmount = errorsmod.Register(ModuleName, 18, "amount is outside acceptable range")
|
||||||
|
// ErrInvalidSwapAccount error for when a swap involves an invalid account
|
||||||
|
ErrInvalidSwapAccount = errorsmod.Register(ModuleName, 19, "atomic swap has invalid account")
|
||||||
|
// ErrExceedsTimeBasedSupplyLimit error for when the proposed supply increase would put the supply above limit for the current time period
|
||||||
|
ErrExceedsTimeBasedSupplyLimit = errorsmod.Register(ModuleName, 20, "asset supply over limit for current time period")
|
||||||
|
)
|
25
x/bep3/types/events.go
Normal file
25
x/bep3/types/events.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// Events for bep3 module
|
||||||
|
const (
|
||||||
|
EventTypeCreateAtomicSwap = "create_atomic_swap"
|
||||||
|
EventTypeClaimAtomicSwap = "claim_atomic_swap"
|
||||||
|
EventTypeRefundAtomicSwap = "refund_atomic_swap"
|
||||||
|
EventTypeSwapsExpired = "swaps_expired"
|
||||||
|
|
||||||
|
AttributeValueCategory = ModuleName
|
||||||
|
AttributeKeySender = "sender"
|
||||||
|
AttributeKeyRecipient = "recipient"
|
||||||
|
AttributeKeyAtomicSwapID = "atomic_swap_id"
|
||||||
|
AttributeKeyRandomNumberHash = "random_number_hash"
|
||||||
|
AttributeKeyTimestamp = "timestamp"
|
||||||
|
AttributeKeySenderOtherChain = "sender_other_chain"
|
||||||
|
AttributeKeyExpireHeight = "expire_height"
|
||||||
|
AttributeKeyAmount = "amount"
|
||||||
|
AttributeKeyDirection = "direction"
|
||||||
|
AttributeKeyClaimSender = "claim_sender"
|
||||||
|
AttributeKeyRandomNumber = "random_number"
|
||||||
|
AttributeKeyRefundSender = "refund_sender"
|
||||||
|
AttributeKeyAtomicSwapIDs = "atomic_swap_ids"
|
||||||
|
AttributeExpirationBlock = "expiration_block"
|
||||||
|
)
|
26
x/bep3/types/expected_keepers.go
Normal file
26
x/bep3/types/expected_keepers.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BankKeeper defines the expected interface needed to retrieve account balances.
|
||||||
|
type BankKeeper interface {
|
||||||
|
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
||||||
|
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
||||||
|
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error
|
||||||
|
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountKeeper defines the expected account keeper
|
||||||
|
type AccountKeeper interface {
|
||||||
|
GetModuleAddress(name string) sdk.AccAddress
|
||||||
|
GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI
|
||||||
|
GetModuleAddressAndPermissions(moduleName string) (sdk.AccAddress, []string)
|
||||||
|
SetModuleAccount(ctx sdk.Context, macc authtypes.ModuleAccountI)
|
||||||
|
|
||||||
|
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
|
||||||
|
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
|
||||||
|
SetAccount(ctx sdk.Context, acc authtypes.AccountI)
|
||||||
|
}
|
59
x/bep3/types/genesis.go
Normal file
59
x/bep3/types/genesis.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGenesisState creates a new GenesisState object
|
||||||
|
func NewGenesisState(params Params, swaps AtomicSwaps, supplies AssetSupplies, previousBlockTime time.Time) GenesisState {
|
||||||
|
return GenesisState{
|
||||||
|
Params: params,
|
||||||
|
AtomicSwaps: swaps,
|
||||||
|
Supplies: supplies,
|
||||||
|
PreviousBlockTime: previousBlockTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultGenesisState - default GenesisState used by Cosmos Hub
|
||||||
|
func DefaultGenesisState() GenesisState {
|
||||||
|
return NewGenesisState(
|
||||||
|
DefaultParams(),
|
||||||
|
AtomicSwaps{},
|
||||||
|
AssetSupplies{},
|
||||||
|
DefaultPreviousBlockTime,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates genesis inputs. It returns error if validation of any input fails.
|
||||||
|
func (gs GenesisState) Validate() error {
|
||||||
|
if err := gs.Params.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := map[string]bool{}
|
||||||
|
for _, swap := range gs.AtomicSwaps {
|
||||||
|
if ids[hex.EncodeToString(swap.GetSwapID())] {
|
||||||
|
return fmt.Errorf("found duplicate atomic swap ID %s", hex.EncodeToString(swap.GetSwapID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := swap.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids[hex.EncodeToString(swap.GetSwapID())] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
supplyDenoms := map[string]bool{}
|
||||||
|
for _, supply := range gs.Supplies {
|
||||||
|
if err := supply.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if supplyDenoms[supply.GetDenom()] {
|
||||||
|
return fmt.Errorf("found duplicate denom in asset supplies %s", supply.GetDenom())
|
||||||
|
}
|
||||||
|
supplyDenoms[supply.GetDenom()] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
514
x/bep3/types/genesis.pb.go
Normal file
514
x/bep3/types/genesis.pb.go
Normal file
@ -0,0 +1,514 @@
|
|||||||
|
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||||
|
// source: zgc/bep3/v1beta1/genesis.proto
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
_ "github.com/cosmos/gogoproto/gogoproto"
|
||||||
|
proto "github.com/cosmos/gogoproto/proto"
|
||||||
|
github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types"
|
||||||
|
_ "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
io "io"
|
||||||
|
math "math"
|
||||||
|
math_bits "math/bits"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
var _ = time.Kitchen
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||||
|
|
||||||
|
// GenesisState defines the pricefeed module's genesis state.
|
||||||
|
type GenesisState struct {
|
||||||
|
// params defines all the parameters of the module.
|
||||||
|
Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"`
|
||||||
|
// atomic_swaps represents the state of stored atomic swaps
|
||||||
|
AtomicSwaps AtomicSwaps `protobuf:"bytes,2,rep,name=atomic_swaps,json=atomicSwaps,proto3,castrepeated=AtomicSwaps" json:"atomic_swaps"`
|
||||||
|
// supplies represents the supply information of each atomic swap
|
||||||
|
Supplies AssetSupplies `protobuf:"bytes,3,rep,name=supplies,proto3,castrepeated=AssetSupplies" json:"supplies"`
|
||||||
|
// previous_block_time represents the time of the previous block
|
||||||
|
PreviousBlockTime time.Time `protobuf:"bytes,4,opt,name=previous_block_time,json=previousBlockTime,proto3,stdtime" json:"previous_block_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenesisState) Reset() { *m = GenesisState{} }
|
||||||
|
func (m *GenesisState) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GenesisState) ProtoMessage() {}
|
||||||
|
func (*GenesisState) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_887bb27f177aae40, []int{0}
|
||||||
|
}
|
||||||
|
func (m *GenesisState) XXX_Unmarshal(b []byte) error {
|
||||||
|
return m.Unmarshal(b)
|
||||||
|
}
|
||||||
|
func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
if deterministic {
|
||||||
|
return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic)
|
||||||
|
} else {
|
||||||
|
b = b[:cap(b)]
|
||||||
|
n, err := m.MarshalToSizedBuffer(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b[:n], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (m *GenesisState) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_GenesisState.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *GenesisState) XXX_Size() int {
|
||||||
|
return m.Size()
|
||||||
|
}
|
||||||
|
func (m *GenesisState) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_GenesisState.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_GenesisState proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *GenesisState) GetParams() Params {
|
||||||
|
if m != nil {
|
||||||
|
return m.Params
|
||||||
|
}
|
||||||
|
return Params{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenesisState) GetAtomicSwaps() AtomicSwaps {
|
||||||
|
if m != nil {
|
||||||
|
return m.AtomicSwaps
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenesisState) GetSupplies() AssetSupplies {
|
||||||
|
if m != nil {
|
||||||
|
return m.Supplies
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenesisState) GetPreviousBlockTime() time.Time {
|
||||||
|
if m != nil {
|
||||||
|
return m.PreviousBlockTime
|
||||||
|
}
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*GenesisState)(nil), "zgc.bep3.v1beta1.GenesisState")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("zgc/bep3/v1beta1/genesis.proto", fileDescriptor_887bb27f177aae40) }
|
||||||
|
|
||||||
|
var fileDescriptor_887bb27f177aae40 = []byte{
|
||||||
|
// 363 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xcd, 0x6e, 0xe2, 0x30,
|
||||||
|
0x14, 0x85, 0x13, 0x40, 0x08, 0x25, 0x8c, 0x34, 0x13, 0x66, 0xa4, 0x88, 0x99, 0x49, 0x50, 0x37,
|
||||||
|
0x65, 0x53, 0x9b, 0x1f, 0xa9, 0x7b, 0xb2, 0xe9, 0x16, 0x05, 0x56, 0xdd, 0x20, 0x27, 0x72, 0x8d,
|
||||||
|
0xd5, 0x04, 0x5b, 0xd8, 0x81, 0xc2, 0x53, 0xf0, 0x1c, 0x7d, 0x8e, 0x2e, 0x58, 0xb2, 0xec, 0xaa,
|
||||||
|
0x54, 0xf0, 0x22, 0x95, 0x9d, 0x50, 0xa4, 0xd2, 0x9d, 0xef, 0x3d, 0xe7, 0x7e, 0xd7, 0x3e, 0xb6,
|
||||||
|
0xbc, 0x35, 0x89, 0x61, 0x84, 0x79, 0x1f, 0x2e, 0xba, 0x11, 0x96, 0xa8, 0x0b, 0x09, 0x9e, 0x61,
|
||||||
|
0x41, 0x05, 0xe0, 0x73, 0x26, 0x99, 0xf3, 0x73, 0x4d, 0x62, 0xa0, 0x74, 0x50, 0xe8, 0xcd, 0xdf,
|
||||||
|
0x84, 0x11, 0xa6, 0x45, 0xa8, 0x4e, 0xb9, 0xaf, 0xe9, 0x13, 0xc6, 0x48, 0x82, 0xa1, 0xae, 0xa2,
|
||||||
|
0xec, 0x01, 0x4a, 0x9a, 0x62, 0x21, 0x51, 0xca, 0x0b, 0xc3, 0xdf, 0x8b, 0x45, 0x9a, 0xaa, 0xc5,
|
||||||
|
0xab, 0x97, 0x92, 0x55, 0xbf, 0xcb, 0xf7, 0x8e, 0x24, 0x92, 0xd8, 0xb9, 0xb5, 0xaa, 0x1c, 0xcd,
|
||||||
|
0x51, 0x2a, 0x5c, 0xb3, 0x65, 0xb6, 0xed, 0x9e, 0x0b, 0xbe, 0xde, 0x03, 0x0c, 0xb5, 0x1e, 0x54,
|
||||||
|
0xb6, 0x6f, 0xbe, 0x11, 0x16, 0x6e, 0x67, 0x6c, 0xd5, 0x91, 0x64, 0x29, 0x8d, 0x27, 0x62, 0x89,
|
||||||
|
0xb8, 0x70, 0x4b, 0xad, 0x72, 0xdb, 0xee, 0xfd, 0xbb, 0x9c, 0x1e, 0x68, 0xd7, 0x68, 0x89, 0x78,
|
||||||
|
0xd0, 0x50, 0x84, 0xe7, 0xbd, 0x6f, 0x9f, 0x7b, 0x22, 0xb4, 0xd1, 0xb9, 0x70, 0x86, 0x56, 0x4d,
|
||||||
|
0x64, 0x9c, 0x27, 0x14, 0x0b, 0xb7, 0xac, 0x89, 0xff, 0xbf, 0x21, 0x0a, 0x81, 0xe5, 0x48, 0xd9,
|
||||||
|
0x56, 0xc1, 0x9f, 0x02, 0xf9, 0xe3, 0xdc, 0xa4, 0x58, 0x84, 0x9f, 0x14, 0x67, 0x6c, 0x35, 0xf8,
|
||||||
|
0x1c, 0x2f, 0x28, 0xcb, 0xc4, 0x24, 0x4a, 0x58, 0xfc, 0x38, 0x51, 0x79, 0xb9, 0x15, 0xfd, 0xd8,
|
||||||
|
0x26, 0xc8, 0xc3, 0x04, 0xa7, 0x30, 0xc1, 0xf8, 0x14, 0x66, 0x50, 0x53, 0xe4, 0xcd, 0xde, 0x37,
|
||||||
|
0xc3, 0x5f, 0x27, 0x40, 0xa0, 0xe6, 0x95, 0x23, 0x18, 0x6c, 0x0f, 0x9e, 0xb9, 0x3b, 0x78, 0xe6,
|
||||||
|
0xfb, 0xc1, 0x33, 0x37, 0x47, 0xcf, 0xd8, 0x1d, 0x3d, 0xe3, 0xf5, 0xe8, 0x19, 0xf7, 0xd7, 0x84,
|
||||||
|
0xca, 0x69, 0x16, 0x81, 0x98, 0xa5, 0xb0, 0x43, 0x12, 0x14, 0x09, 0xd8, 0x21, 0x37, 0xf1, 0x14,
|
||||||
|
0xd1, 0x19, 0x7c, 0xca, 0xbf, 0x45, 0xae, 0x38, 0x16, 0x51, 0x55, 0xef, 0xec, 0x7f, 0x04, 0x00,
|
||||||
|
0x00, 0xff, 0xff, 0xa9, 0xa5, 0x69, 0xc0, 0x18, 0x02, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenesisState) Marshal() (dAtA []byte, err error) {
|
||||||
|
size := m.Size()
|
||||||
|
dAtA = make([]byte, size)
|
||||||
|
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dAtA[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
size := m.Size()
|
||||||
|
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
|
i := len(dAtA)
|
||||||
|
_ = i
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.PreviousBlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.PreviousBlockTime):])
|
||||||
|
if err1 != nil {
|
||||||
|
return 0, err1
|
||||||
|
}
|
||||||
|
i -= n1
|
||||||
|
i = encodeVarintGenesis(dAtA, i, uint64(n1))
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x22
|
||||||
|
if len(m.Supplies) > 0 {
|
||||||
|
for iNdEx := len(m.Supplies) - 1; iNdEx >= 0; iNdEx-- {
|
||||||
|
{
|
||||||
|
size, err := m.Supplies[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
i -= size
|
||||||
|
i = encodeVarintGenesis(dAtA, i, uint64(size))
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x1a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m.AtomicSwaps) > 0 {
|
||||||
|
for iNdEx := len(m.AtomicSwaps) - 1; iNdEx >= 0; iNdEx-- {
|
||||||
|
{
|
||||||
|
size, err := m.AtomicSwaps[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
i -= size
|
||||||
|
i = encodeVarintGenesis(dAtA, i, uint64(size))
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
size, err := m.Params.MarshalToSizedBuffer(dAtA[:i])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
i -= size
|
||||||
|
i = encodeVarintGenesis(dAtA, i, uint64(size))
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0xa
|
||||||
|
return len(dAtA) - i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int {
|
||||||
|
offset -= sovGenesis(v)
|
||||||
|
base := offset
|
||||||
|
for v >= 1<<7 {
|
||||||
|
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||||
|
v >>= 7
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
dAtA[offset] = uint8(v)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
func (m *GenesisState) Size() (n int) {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
l = m.Params.Size()
|
||||||
|
n += 1 + l + sovGenesis(uint64(l))
|
||||||
|
if len(m.AtomicSwaps) > 0 {
|
||||||
|
for _, e := range m.AtomicSwaps {
|
||||||
|
l = e.Size()
|
||||||
|
n += 1 + l + sovGenesis(uint64(l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m.Supplies) > 0 {
|
||||||
|
for _, e := range m.Supplies {
|
||||||
|
l = e.Size()
|
||||||
|
n += 1 + l + sovGenesis(uint64(l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.PreviousBlockTime)
|
||||||
|
n += 1 + l + sovGenesis(uint64(l))
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func sovGenesis(x uint64) (n int) {
|
||||||
|
return (math_bits.Len64(x|1) + 6) / 7
|
||||||
|
}
|
||||||
|
func sozGenesis(x uint64) (n int) {
|
||||||
|
return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||||
|
}
|
||||||
|
func (m *GenesisState) Unmarshal(dAtA []byte) error {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
preIndex := iNdEx
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= uint64(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldNum := int32(wire >> 3)
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
if wireType == 4 {
|
||||||
|
return fmt.Errorf("proto: GenesisState: wiretype end group for non-group")
|
||||||
|
}
|
||||||
|
if fieldNum <= 0 {
|
||||||
|
return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||||
|
}
|
||||||
|
switch fieldNum {
|
||||||
|
case 1:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType)
|
||||||
|
}
|
||||||
|
var msglen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
msglen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msglen < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + msglen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 2:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field AtomicSwaps", wireType)
|
||||||
|
}
|
||||||
|
var msglen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
msglen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msglen < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + msglen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.AtomicSwaps = append(m.AtomicSwaps, AtomicSwap{})
|
||||||
|
if err := m.AtomicSwaps[len(m.AtomicSwaps)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 3:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Supplies", wireType)
|
||||||
|
}
|
||||||
|
var msglen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
msglen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msglen < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + msglen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.Supplies = append(m.Supplies, AssetSupply{})
|
||||||
|
if err := m.Supplies[len(m.Supplies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 4:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field PreviousBlockTime", wireType)
|
||||||
|
}
|
||||||
|
var msglen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
msglen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msglen < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + msglen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.PreviousBlockTime, dAtA[iNdEx:postIndex]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iNdEx = postIndex
|
||||||
|
default:
|
||||||
|
iNdEx = preIndex
|
||||||
|
skippy, err := skipGenesis(dAtA[iNdEx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||||
|
return ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
if (iNdEx + skippy) > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx += skippy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iNdEx > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func skipGenesis(dAtA []byte) (n int, err error) {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
depth := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
switch wireType {
|
||||||
|
case 0:
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx++
|
||||||
|
if dAtA[iNdEx-1] < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
iNdEx += 8
|
||||||
|
case 2:
|
||||||
|
var length int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflowGenesis
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
length |= (int(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if length < 0 {
|
||||||
|
return 0, ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
iNdEx += length
|
||||||
|
case 3:
|
||||||
|
depth++
|
||||||
|
case 4:
|
||||||
|
if depth == 0 {
|
||||||
|
return 0, ErrUnexpectedEndOfGroupGenesis
|
||||||
|
}
|
||||||
|
depth--
|
||||||
|
case 5:
|
||||||
|
iNdEx += 4
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||||
|
}
|
||||||
|
if iNdEx < 0 {
|
||||||
|
return 0, ErrInvalidLengthGenesis
|
||||||
|
}
|
||||||
|
if depth == 0 {
|
||||||
|
return iNdEx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||||
|
ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow")
|
||||||
|
ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group")
|
||||||
|
)
|
123
x/bep3/types/genesis_test.go
Normal file
123
x/bep3/types/genesis_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenesisTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
swaps types.AtomicSwaps
|
||||||
|
supplies types.AssetSupplies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) SetupTest() {
|
||||||
|
coin := sdk.NewCoin("a0gi", sdk.OneInt())
|
||||||
|
suite.swaps = atomicSwaps(10)
|
||||||
|
|
||||||
|
supply := types.NewAssetSupply(coin, coin, coin, coin, time.Duration(0))
|
||||||
|
suite.supplies = types.AssetSupplies{supply}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) TestValidate() {
|
||||||
|
type args struct {
|
||||||
|
swaps types.AtomicSwaps
|
||||||
|
supplies types.AssetSupplies
|
||||||
|
previousBlockTime time.Time
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
args{
|
||||||
|
swaps: types.AtomicSwaps{},
|
||||||
|
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with swaps",
|
||||||
|
args{
|
||||||
|
swaps: suite.swaps,
|
||||||
|
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with supplies",
|
||||||
|
args{
|
||||||
|
swaps: types.AtomicSwaps{},
|
||||||
|
supplies: suite.supplies,
|
||||||
|
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid supply",
|
||||||
|
args{
|
||||||
|
swaps: types.AtomicSwaps{},
|
||||||
|
supplies: types.AssetSupplies{{IncomingSupply: sdk.Coin{Denom: "Invalid", Amount: sdk.ZeroInt()}}},
|
||||||
|
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duplicate swaps",
|
||||||
|
args{
|
||||||
|
swaps: types.AtomicSwaps{suite.swaps[2], suite.swaps[2]},
|
||||||
|
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid swap",
|
||||||
|
args{
|
||||||
|
swaps: types.AtomicSwaps{{Amount: sdk.Coins{sdk.Coin{Denom: "Invalid Denom", Amount: sdkmath.NewInt(-1)}}}},
|
||||||
|
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"blocktime not set",
|
||||||
|
args{
|
||||||
|
swaps: types.AtomicSwaps{},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
var gs types.GenesisState
|
||||||
|
if tc.name == "default" {
|
||||||
|
gs = types.DefaultGenesisState()
|
||||||
|
} else {
|
||||||
|
gs = types.NewGenesisState(types.DefaultParams(), tc.args.swaps, tc.args.supplies, tc.args.previousBlockTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := gs.Validate()
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.Require().NoError(err, tc.name)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err, tc.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(GenesisTestSuite))
|
||||||
|
}
|
36
x/bep3/types/hash.go
Normal file
36
x/bep3/types/hash.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cometbft/cometbft/crypto/tmhash"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateSecureRandomNumber generates cryptographically strong pseudo-random number
|
||||||
|
func GenerateSecureRandomNumber() ([]byte, error) {
|
||||||
|
bytes := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateRandomHash calculates the hash of a number and timestamp
|
||||||
|
func CalculateRandomHash(randomNumber []byte, timestamp int64) []byte {
|
||||||
|
data := make([]byte, RandomNumberLength+Int64Size)
|
||||||
|
copy(data[:RandomNumberLength], randomNumber)
|
||||||
|
binary.BigEndian.PutUint64(data[RandomNumberLength:], uint64(timestamp))
|
||||||
|
return tmhash.Sum(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateSwapID calculates the hash of a RandomNumberHash, sdk.AccAddress, and string
|
||||||
|
func CalculateSwapID(randomNumberHash []byte, sender sdk.AccAddress, senderOtherChain string) []byte {
|
||||||
|
senderOtherChain = strings.ToLower(senderOtherChain)
|
||||||
|
data := randomNumberHash
|
||||||
|
data = append(data, sender.Bytes()...)
|
||||||
|
data = append(data, []byte(senderOtherChain)...)
|
||||||
|
return tmhash.Sum(data)
|
||||||
|
}
|
61
x/bep3/types/hash_test.go
Normal file
61
x/bep3/types/hash_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HashTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
timestamps []int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *HashTestSuite) SetupTest() {
|
||||||
|
// Generate 10 addresses
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(10)
|
||||||
|
|
||||||
|
// Generate 10 timestamps
|
||||||
|
var timestamps []int64
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
timestamps = append(timestamps, ts(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.addrs = addrs
|
||||||
|
suite.timestamps = timestamps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *HashTestSuite) TestGenerateSecureRandomNumber() {
|
||||||
|
secureRandomNumber, err := types.GenerateSecureRandomNumber()
|
||||||
|
suite.Nil(err)
|
||||||
|
suite.NotNil(secureRandomNumber)
|
||||||
|
suite.Equal(32, len(secureRandomNumber))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *HashTestSuite) TestCalculateRandomHash() {
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
hash := types.CalculateRandomHash(randomNumber[:], suite.timestamps[0])
|
||||||
|
suite.NotNil(hash)
|
||||||
|
suite.Equal(32, len(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *HashTestSuite) TestCalculateSwapID() {
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
hash := types.CalculateRandomHash(randomNumber[:], suite.timestamps[3])
|
||||||
|
swapID := types.CalculateSwapID(hash, suite.addrs[3], suite.addrs[5].String())
|
||||||
|
suite.NotNil(swapID)
|
||||||
|
suite.Equal(32, len(swapID))
|
||||||
|
|
||||||
|
diffHash := types.CalculateRandomHash(randomNumber[:], suite.timestamps[2])
|
||||||
|
diffSwapID := types.CalculateSwapID(diffHash, suite.addrs[3], suite.addrs[5].String())
|
||||||
|
suite.NotEqual(swapID, diffSwapID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(HashTestSuite))
|
||||||
|
}
|
36
x/bep3/types/keys.go
Normal file
36
x/bep3/types/keys.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ModuleName is the name of the module
|
||||||
|
ModuleName = "bep3"
|
||||||
|
|
||||||
|
// StoreKey to be used when creating the KVStore
|
||||||
|
StoreKey = ModuleName
|
||||||
|
|
||||||
|
// RouterKey to be used for routing msgs
|
||||||
|
RouterKey = ModuleName
|
||||||
|
|
||||||
|
// DefaultParamspace default namestore
|
||||||
|
DefaultParamspace = ModuleName
|
||||||
|
|
||||||
|
// DefaultLongtermStorageDuration is 1 week (assuming a block time of 7 seconds)
|
||||||
|
DefaultLongtermStorageDuration uint64 = 86400
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key prefixes
|
||||||
|
var (
|
||||||
|
AtomicSwapKeyPrefix = []byte{0x00} // prefix for keys that store AtomicSwaps
|
||||||
|
AtomicSwapByBlockPrefix = []byte{0x01} // prefix for keys of the AtomicSwapsByBlock index
|
||||||
|
AtomicSwapLongtermStoragePrefix = []byte{0x02} // prefix for keys of the AtomicSwapLongtermStorage index
|
||||||
|
AssetSupplyPrefix = []byte{0x03}
|
||||||
|
PreviousBlockTimeKey = []byte{0x04}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetAtomicSwapByHeightKey is used by the AtomicSwapByBlock index and AtomicSwapLongtermStorage index
|
||||||
|
func GetAtomicSwapByHeightKey(height uint64, swapID []byte) []byte {
|
||||||
|
return append(sdk.Uint64ToBigEndian(height), swapID...)
|
||||||
|
}
|
258
x/bep3/types/msg.go
Normal file
258
x/bep3/types/msg.go
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cometbft/cometbft/crypto"
|
||||||
|
tmbytes "github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CreateAtomicSwap = "createAtomicSwap"
|
||||||
|
ClaimAtomicSwap = "claimAtomicSwap"
|
||||||
|
RefundAtomicSwap = "refundAtomicSwap"
|
||||||
|
CalcSwapID = "calcSwapID"
|
||||||
|
|
||||||
|
Int64Size = 8
|
||||||
|
RandomNumberHashLength = 32
|
||||||
|
RandomNumberLength = 32
|
||||||
|
MaxOtherChainAddrLength = 64
|
||||||
|
SwapIDLength = 32
|
||||||
|
MaxExpectedIncomeLength = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensure Msg interface compliance at compile time
|
||||||
|
var (
|
||||||
|
_ sdk.Msg = &MsgCreateAtomicSwap{}
|
||||||
|
_ sdk.Msg = &MsgClaimAtomicSwap{}
|
||||||
|
_ sdk.Msg = &MsgRefundAtomicSwap{}
|
||||||
|
AtomicSwapCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("0gChainAtomicSwapCoins")))
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMsgCreateAtomicSwap initializes a new MsgCreateAtomicSwap
|
||||||
|
func NewMsgCreateAtomicSwap(from, to string, recipientOtherChain,
|
||||||
|
senderOtherChain string, randomNumberHash tmbytes.HexBytes, timestamp int64,
|
||||||
|
amount sdk.Coins, heightSpan uint64,
|
||||||
|
) MsgCreateAtomicSwap {
|
||||||
|
return MsgCreateAtomicSwap{
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
RecipientOtherChain: recipientOtherChain,
|
||||||
|
SenderOtherChain: senderOtherChain,
|
||||||
|
RandomNumberHash: randomNumberHash.String(),
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Amount: amount,
|
||||||
|
HeightSpan: heightSpan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route establishes the route for the MsgCreateAtomicSwap
|
||||||
|
func (msg MsgCreateAtomicSwap) Route() string { return RouterKey }
|
||||||
|
|
||||||
|
// Type is the name of MsgCreateAtomicSwap
|
||||||
|
func (msg MsgCreateAtomicSwap) Type() string { return CreateAtomicSwap }
|
||||||
|
|
||||||
|
// String prints the MsgCreateAtomicSwap
|
||||||
|
func (msg MsgCreateAtomicSwap) String() string {
|
||||||
|
return fmt.Sprintf("AtomicSwap{%v#%v#%v#%v#%v#%v#%v#%v}",
|
||||||
|
msg.From, msg.To, msg.RecipientOtherChain, msg.SenderOtherChain,
|
||||||
|
msg.RandomNumberHash, msg.Timestamp, msg.Amount, msg.HeightSpan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInvolvedAddresses gets the addresses involved in a MsgCreateAtomicSwap
|
||||||
|
func (msg MsgCreateAtomicSwap) GetInvolvedAddresses() []sdk.AccAddress {
|
||||||
|
return append(msg.GetSigners(), AtomicSwapCoinsAccAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigners gets the signers of a MsgCreateAtomicSwap
|
||||||
|
func (msg MsgCreateAtomicSwap) GetSigners() []sdk.AccAddress {
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return []sdk.AccAddress{from}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic validates the MsgCreateAtomicSwap
|
||||||
|
func (msg MsgCreateAtomicSwap) ValidateBasic() error {
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
|
||||||
|
}
|
||||||
|
to, err := sdk.AccAddressFromBech32(msg.To)
|
||||||
|
if err != nil {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
|
||||||
|
}
|
||||||
|
if from.Empty() {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
|
||||||
|
}
|
||||||
|
if to.Empty() {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "recipient address cannot be empty")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(msg.RecipientOtherChain) == "" {
|
||||||
|
return errors.New("missing recipient address on other chain")
|
||||||
|
}
|
||||||
|
if len(msg.RecipientOtherChain) > MaxOtherChainAddrLength {
|
||||||
|
return fmt.Errorf("the length of recipient address on other chain should be less than %d", MaxOtherChainAddrLength)
|
||||||
|
}
|
||||||
|
if len(msg.SenderOtherChain) > MaxOtherChainAddrLength {
|
||||||
|
return fmt.Errorf("the length of sender address on other chain should be less than %d", MaxOtherChainAddrLength)
|
||||||
|
}
|
||||||
|
randomNumberHash, err := hex.DecodeString(msg.RandomNumberHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("random number hash should be valid hex: %v", err)
|
||||||
|
}
|
||||||
|
if len(randomNumberHash) != RandomNumberHashLength {
|
||||||
|
return fmt.Errorf("the length of random number hash should be %d", RandomNumberHashLength)
|
||||||
|
}
|
||||||
|
if msg.Timestamp <= 0 {
|
||||||
|
return errors.New("timestamp must be positive")
|
||||||
|
}
|
||||||
|
if len(msg.Amount) == 0 {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, "amount cannot be empty")
|
||||||
|
}
|
||||||
|
if !msg.Amount.IsValid() {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String())
|
||||||
|
}
|
||||||
|
if msg.HeightSpan <= 0 {
|
||||||
|
return errors.New("height span must be positive")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignBytes gets the sign bytes of a MsgCreateAtomicSwap
|
||||||
|
func (msg MsgCreateAtomicSwap) GetSignBytes() []byte {
|
||||||
|
bz := ModuleCdc.MustMarshalJSON(&msg)
|
||||||
|
return sdk.MustSortJSON(bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgClaimAtomicSwap initializes a new MsgClaimAtomicSwap
|
||||||
|
func NewMsgClaimAtomicSwap(from string, swapID, randomNumber tmbytes.HexBytes) MsgClaimAtomicSwap {
|
||||||
|
return MsgClaimAtomicSwap{
|
||||||
|
From: from,
|
||||||
|
SwapID: swapID.String(),
|
||||||
|
RandomNumber: randomNumber.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route establishes the route for the MsgClaimAtomicSwap
|
||||||
|
func (msg MsgClaimAtomicSwap) Route() string { return RouterKey }
|
||||||
|
|
||||||
|
// Type is the name of MsgClaimAtomicSwap
|
||||||
|
func (msg MsgClaimAtomicSwap) Type() string { return ClaimAtomicSwap }
|
||||||
|
|
||||||
|
// String prints the MsgClaimAtomicSwap
|
||||||
|
func (msg MsgClaimAtomicSwap) String() string {
|
||||||
|
return fmt.Sprintf("claimAtomicSwap{%v#%v#%v}", msg.From, msg.SwapID, msg.RandomNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInvolvedAddresses gets the addresses involved in a MsgClaimAtomicSwap
|
||||||
|
func (msg MsgClaimAtomicSwap) GetInvolvedAddresses() []sdk.AccAddress {
|
||||||
|
return append(msg.GetSigners(), AtomicSwapCoinsAccAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigners gets the signers of a MsgClaimAtomicSwap
|
||||||
|
func (msg MsgClaimAtomicSwap) GetSigners() []sdk.AccAddress {
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return []sdk.AccAddress{from}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic validates the MsgClaimAtomicSwap
|
||||||
|
func (msg MsgClaimAtomicSwap) ValidateBasic() error {
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
|
||||||
|
}
|
||||||
|
if from.Empty() {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
|
||||||
|
}
|
||||||
|
swapID, err := hex.DecodeString(msg.SwapID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("swap id should be valid hex: %v", err)
|
||||||
|
}
|
||||||
|
if len(swapID) != SwapIDLength {
|
||||||
|
return fmt.Errorf("the length of swapID should be %d", SwapIDLength)
|
||||||
|
}
|
||||||
|
randomNumber, err := hex.DecodeString(msg.RandomNumber)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("random number should be valid hex: %v", err)
|
||||||
|
}
|
||||||
|
if len(randomNumber) != RandomNumberLength {
|
||||||
|
return fmt.Errorf("the length of random number should be %d", RandomNumberLength)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignBytes gets the sign bytes of a MsgClaimAtomicSwap
|
||||||
|
func (msg MsgClaimAtomicSwap) GetSignBytes() []byte {
|
||||||
|
bz := ModuleCdc.MustMarshalJSON(&msg)
|
||||||
|
return sdk.MustSortJSON(bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgRefundAtomicSwap initializes a new MsgRefundAtomicSwap
|
||||||
|
func NewMsgRefundAtomicSwap(from string, swapID tmbytes.HexBytes) MsgRefundAtomicSwap {
|
||||||
|
return MsgRefundAtomicSwap{
|
||||||
|
From: from,
|
||||||
|
SwapID: swapID.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route establishes the route for the MsgRefundAtomicSwap
|
||||||
|
func (msg MsgRefundAtomicSwap) Route() string { return RouterKey }
|
||||||
|
|
||||||
|
// Type is the name of MsgRefundAtomicSwap
|
||||||
|
func (msg MsgRefundAtomicSwap) Type() string { return RefundAtomicSwap }
|
||||||
|
|
||||||
|
// String prints the MsgRefundAtomicSwap
|
||||||
|
func (msg MsgRefundAtomicSwap) String() string {
|
||||||
|
return fmt.Sprintf("refundAtomicSwap{%v#%v}", msg.From, msg.SwapID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInvolvedAddresses gets the addresses involved in a MsgRefundAtomicSwap
|
||||||
|
func (msg MsgRefundAtomicSwap) GetInvolvedAddresses() []sdk.AccAddress {
|
||||||
|
return append(msg.GetSigners(), AtomicSwapCoinsAccAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigners gets the signers of a MsgRefundAtomicSwap
|
||||||
|
func (msg MsgRefundAtomicSwap) GetSigners() []sdk.AccAddress {
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return []sdk.AccAddress{from}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBasic validates the MsgRefundAtomicSwap
|
||||||
|
func (msg MsgRefundAtomicSwap) ValidateBasic() error {
|
||||||
|
from, err := sdk.AccAddressFromBech32(msg.From)
|
||||||
|
if err != nil {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
|
||||||
|
}
|
||||||
|
if from.Empty() {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
|
||||||
|
}
|
||||||
|
swapID, err := hex.DecodeString(msg.SwapID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("swap id should be valid hex: %v", err)
|
||||||
|
}
|
||||||
|
if len(swapID) != SwapIDLength {
|
||||||
|
return fmt.Errorf("the length of swapID should be %d", SwapIDLength)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignBytes gets the sign bytes of a MsgRefundAtomicSwap
|
||||||
|
func (msg MsgRefundAtomicSwap) GetSignBytes() []byte {
|
||||||
|
bz := ModuleCdc.MustMarshalJSON(&msg)
|
||||||
|
return sdk.MustSortJSON(bz)
|
||||||
|
}
|
143
x/bep3/types/msg_test.go
Normal file
143
x/bep3/types/msg_test.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cometbft/cometbft/crypto"
|
||||||
|
tmbytes "github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
coinsSingle = sdk.NewCoins(sdk.NewInt64Coin("bnb", 50000))
|
||||||
|
binanceAddrs = []sdk.AccAddress{}
|
||||||
|
zgAddrs = []sdk.AccAddress{}
|
||||||
|
randomNumberBytes = []byte{15}
|
||||||
|
timestampInt64 = int64(100)
|
||||||
|
randomNumberHash = tmbytes.HexBytes(types.CalculateRandomHash(randomNumberBytes, timestampInt64))
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
app.SetSDKConfig()
|
||||||
|
|
||||||
|
// Must be set after SetSDKConfig to use 0g Bech32 prefix instead of cosmos
|
||||||
|
binanceAddrs = []sdk.AccAddress{
|
||||||
|
sdk.AccAddress(crypto.AddressHash([]byte("BinanceTest1"))),
|
||||||
|
sdk.AccAddress(crypto.AddressHash([]byte("BinanceTest2"))),
|
||||||
|
}
|
||||||
|
zgAddrs = []sdk.AccAddress{
|
||||||
|
sdk.AccAddress(crypto.AddressHash([]byte("0gTest1"))),
|
||||||
|
sdk.AccAddress(crypto.AddressHash([]byte("0gTest2"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MsgTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgCreateAtomicSwap() {
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
from sdk.AccAddress
|
||||||
|
to sdk.AccAddress
|
||||||
|
recipientOtherChain string
|
||||||
|
senderOtherChain string
|
||||||
|
randomNumberHash string
|
||||||
|
timestamp int64
|
||||||
|
amount sdk.Coins
|
||||||
|
heightSpan uint64
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{"normal cross-chain", binanceAddrs[0], zgAddrs[0], zgAddrs[0].String(), binanceAddrs[0].String(), randomNumberHash.String(), timestampInt64, coinsSingle, 500, true},
|
||||||
|
{"without other chain fields", binanceAddrs[0], zgAddrs[0], "", "", randomNumberHash.String(), timestampInt64, coinsSingle, 500, false},
|
||||||
|
{"invalid amount", binanceAddrs[0], zgAddrs[0], zgAddrs[0].String(), binanceAddrs[0].String(), randomNumberHash.String(), timestampInt64, nil, 500, false},
|
||||||
|
{"invalid from address", sdk.AccAddress{}, zgAddrs[0], zgAddrs[0].String(), binanceAddrs[0].String(), randomNumberHash.String(), timestampInt64, coinsSingle, 500, false},
|
||||||
|
{"invalid to address", binanceAddrs[0], sdk.AccAddress{}, zgAddrs[0].String(), binanceAddrs[0].String(), randomNumberHash.String(), timestampInt64, coinsSingle, 500, false},
|
||||||
|
{"invalid rand hash", binanceAddrs[0], zgAddrs[0], zgAddrs[0].String(), binanceAddrs[0].String(), "ff", timestampInt64, coinsSingle, 500, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range tests {
|
||||||
|
msg := types.MsgCreateAtomicSwap{
|
||||||
|
tc.from.String(),
|
||||||
|
tc.to.String(),
|
||||||
|
tc.recipientOtherChain,
|
||||||
|
tc.senderOtherChain,
|
||||||
|
tc.randomNumberHash,
|
||||||
|
tc.timestamp,
|
||||||
|
tc.amount,
|
||||||
|
tc.heightSpan,
|
||||||
|
}
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.NoError(msg.ValidateBasic(), "test: %v", i)
|
||||||
|
} else {
|
||||||
|
suite.Error(msg.ValidateBasic(), "test: %v", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgClaimAtomicSwap() {
|
||||||
|
swapID := types.CalculateSwapID(randomNumberHash, binanceAddrs[0], "")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
from sdk.AccAddress
|
||||||
|
swapID tmbytes.HexBytes
|
||||||
|
randomNumber tmbytes.HexBytes
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{"normal", binanceAddrs[0], swapID, randomNumberHash, true},
|
||||||
|
{"invalid from address", sdk.AccAddress{}, swapID, randomNumberHash, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range tests {
|
||||||
|
msg := types.NewMsgClaimAtomicSwap(
|
||||||
|
tc.from.String(),
|
||||||
|
tc.swapID,
|
||||||
|
tc.randomNumber,
|
||||||
|
)
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.NoError(msg.ValidateBasic(), "test: %v", i)
|
||||||
|
} else {
|
||||||
|
suite.Error(msg.ValidateBasic(), "test: %v", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgRefundAtomicSwap() {
|
||||||
|
swapID := types.CalculateSwapID(randomNumberHash, binanceAddrs[0], "")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
from sdk.AccAddress
|
||||||
|
swapID tmbytes.HexBytes
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{"normal", binanceAddrs[0], swapID, true},
|
||||||
|
{"invalid from address", sdk.AccAddress{}, swapID, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range tests {
|
||||||
|
msg := types.NewMsgRefundAtomicSwap(
|
||||||
|
tc.from.String(),
|
||||||
|
tc.swapID,
|
||||||
|
)
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.NoError(msg.ValidateBasic(), "test: %v", i)
|
||||||
|
} else {
|
||||||
|
suite.Error(msg.ValidateBasic(), "test: %v", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MsgTestSuite))
|
||||||
|
}
|
150
x/bep3/types/params.go
Normal file
150
x/bep3/types/params.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||||
|
|
||||||
|
tmtime "github.com/cometbft/cometbft/types/time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bech32MainPrefix = "0g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parameter keys
|
||||||
|
var (
|
||||||
|
KeyAssetParams = []byte("AssetParams")
|
||||||
|
|
||||||
|
DefaultBnbDeputyFixedFee sdkmath.Int = sdkmath.NewInt(1000) // 0.00001 BNB
|
||||||
|
DefaultMinAmount sdkmath.Int = sdk.ZeroInt()
|
||||||
|
DefaultMaxAmount sdkmath.Int = sdkmath.NewInt(1000000000000) // 10,000 BNB
|
||||||
|
DefaultMinBlockLock uint64 = 220
|
||||||
|
DefaultMaxBlockLock uint64 = 270
|
||||||
|
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(1, 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewParams returns a new params object
|
||||||
|
func NewParams(ap []AssetParam) Params {
|
||||||
|
return Params{
|
||||||
|
AssetParams: ap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultParams returns default params for bep3 module
|
||||||
|
func DefaultParams() Params {
|
||||||
|
return NewParams(AssetParams{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAssetParam returns a new AssetParam
|
||||||
|
func NewAssetParam(
|
||||||
|
denom string, coinID int64, limit SupplyLimit, active bool,
|
||||||
|
deputyAddr sdk.AccAddress, fixedFee sdkmath.Int, minSwapAmount sdkmath.Int,
|
||||||
|
maxSwapAmount sdkmath.Int, minBlockLock uint64, maxBlockLock uint64,
|
||||||
|
) AssetParam {
|
||||||
|
return AssetParam{
|
||||||
|
Denom: denom,
|
||||||
|
CoinID: coinID,
|
||||||
|
SupplyLimit: limit,
|
||||||
|
Active: active,
|
||||||
|
DeputyAddress: deputyAddr,
|
||||||
|
FixedFee: fixedFee,
|
||||||
|
MinSwapAmount: minSwapAmount,
|
||||||
|
MaxSwapAmount: maxSwapAmount,
|
||||||
|
MinBlockLock: minBlockLock,
|
||||||
|
MaxBlockLock: maxBlockLock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetParams array of AssetParam
|
||||||
|
type AssetParams []AssetParam
|
||||||
|
|
||||||
|
// Equals returns true if two supply limits are equal
|
||||||
|
func (sl SupplyLimit) Equals(sl2 SupplyLimit) bool {
|
||||||
|
return sl.Limit.Equal(sl2.Limit) && sl.TimeLimited == sl2.TimeLimited && sl.TimePeriod == sl2.TimePeriod && sl.TimeBasedLimit.Equal(sl2.TimeBasedLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamKeyTable Key declaration for parameters
|
||||||
|
func ParamKeyTable() paramtypes.KeyTable {
|
||||||
|
return paramtypes.NewKeyTable().RegisterParamSet(&Params{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
|
||||||
|
// pairs of bep3 module's parameters.
|
||||||
|
// nolint
|
||||||
|
func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs {
|
||||||
|
return paramtypes.ParamSetPairs{
|
||||||
|
paramtypes.NewParamSetPair(KeyAssetParams, &p.AssetParams, validateAssetParams),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ensure that params have valid values
|
||||||
|
func (p Params) Validate() error {
|
||||||
|
return validateAssetParams(p.AssetParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAssetParams(i interface{}) error {
|
||||||
|
assetParams, ok := i.(AssetParams)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid parameter type: %T", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
coinDenoms := make(map[string]bool)
|
||||||
|
for _, asset := range assetParams {
|
||||||
|
if err := sdk.ValidateDenom(asset.Denom); err != nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset denom invalid: %s", asset.Denom))
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.CoinID < 0 {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset %s coin id must be a non negative integer", asset.Denom))
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.SupplyLimit.Limit.IsNegative() {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply limit: %s", asset.Denom, asset.SupplyLimit.Limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.SupplyLimit.TimeBasedLimit.IsNegative() {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply time limit: %s", asset.Denom, asset.SupplyLimit.TimeBasedLimit))
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.SupplyLimit.TimeBasedLimit.GT(asset.SupplyLimit.Limit) {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset %s cannot have supply time limit > supply limit: %s>%s", asset.Denom, asset.SupplyLimit.TimeBasedLimit, asset.SupplyLimit.Limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, found := coinDenoms[asset.Denom]
|
||||||
|
if found {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate denom", asset.Denom))
|
||||||
|
}
|
||||||
|
|
||||||
|
coinDenoms[asset.Denom] = true
|
||||||
|
|
||||||
|
if asset.DeputyAddress.Empty() {
|
||||||
|
return fmt.Errorf("deputy address cannot be empty for %s", asset.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.FixedFee.IsNegative() {
|
||||||
|
return fmt.Errorf("asset %s cannot have a negative fixed fee %s", asset.Denom, asset.FixedFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.MinBlockLock > asset.MaxBlockLock {
|
||||||
|
return fmt.Errorf("asset %s has minimum block lock > maximum block lock %d > %d", asset.Denom, asset.MinBlockLock, asset.MaxBlockLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !asset.MinSwapAmount.IsPositive() {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset %s must have a positive minimum swap amount, got %s", asset.Denom, asset.MinSwapAmount))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !asset.MaxSwapAmount.IsPositive() {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("asset %s must have a positive maximum swap amount, got %s", asset.Denom, asset.MaxSwapAmount))
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.MinSwapAmount.GT(asset.MaxSwapAmount) {
|
||||||
|
return fmt.Errorf("asset %s has minimum swap amount > maximum swap amount %s > %s", asset.Denom, asset.MinSwapAmount, asset.MaxSwapAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
249
x/bep3/types/params_test.go
Normal file
249
x/bep3/types/params_test.go
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParamsTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
addr sdk.AccAddress
|
||||||
|
supply []types.SupplyLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||||
|
suite.addr = addrs[0]
|
||||||
|
supply1 := types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(10000000000000),
|
||||||
|
TimeLimited: false,
|
||||||
|
TimeBasedLimit: sdk.ZeroInt(),
|
||||||
|
TimePeriod: time.Hour,
|
||||||
|
}
|
||||||
|
supply2 := types.SupplyLimit{
|
||||||
|
Limit: sdkmath.NewInt(10000000000000),
|
||||||
|
TimeLimited: true,
|
||||||
|
TimeBasedLimit: sdkmath.NewInt(100000000000),
|
||||||
|
TimePeriod: time.Hour * 24,
|
||||||
|
}
|
||||||
|
suite.supply = append(suite.supply, supply1, supply2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamsTestSuite) TestParamValidation() {
|
||||||
|
type args struct {
|
||||||
|
assetParams types.AssetParams
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectPass bool
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{},
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid single asset",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid single asset time limited",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[1], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid multi asset",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{
|
||||||
|
types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock),
|
||||||
|
types.NewAssetParam(
|
||||||
|
"btcb", 0, suite.supply[1], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(10000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid denom - empty",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "denom invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min block lock equal max block lock",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
243, 243)},
|
||||||
|
},
|
||||||
|
expectPass: true,
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min block lock greater max block lock",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
244, 243)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "minimum block lock > maximum block lock",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min swap not positive",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(0), sdkmath.NewInt(10000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "must have a positive minimum swap",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max swap not positive",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(10000), sdkmath.NewInt(0),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "must have a positive maximum swap",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min swap greater max swap",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000000), sdkmath.NewInt(10000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "minimum swap amount > maximum swap amount",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative coin id",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", -714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "coin id must be a non negative",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative asset limit",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714,
|
||||||
|
types.SupplyLimit{sdkmath.NewInt(-10000000000000), false, time.Hour, sdk.ZeroInt()}, true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "invalid (negative) supply limit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative asset time limit",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714,
|
||||||
|
types.SupplyLimit{sdkmath.NewInt(10000000000000), false, time.Hour, sdkmath.NewInt(-10000000000000)}, true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "invalid (negative) supply time limit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "asset time limit greater than overall limit",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{types.NewAssetParam(
|
||||||
|
"bnb", 714,
|
||||||
|
types.SupplyLimit{sdkmath.NewInt(10000000000000), true, time.Hour, sdkmath.NewInt(100000000000000)},
|
||||||
|
true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "supply time limit > supply limit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate denom",
|
||||||
|
args: args{
|
||||||
|
assetParams: types.AssetParams{
|
||||||
|
types.NewAssetParam(
|
||||||
|
"bnb", 714, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(100000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock),
|
||||||
|
types.NewAssetParam(
|
||||||
|
"bnb", 0, suite.supply[0], true,
|
||||||
|
suite.addr, sdkmath.NewInt(1000), sdkmath.NewInt(10000000), sdkmath.NewInt(100000000000),
|
||||||
|
types.DefaultMinBlockLock, types.DefaultMaxBlockLock),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectPass: false,
|
||||||
|
expectedErr: "duplicate denom",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
params := types.NewParams(tc.args.assetParams)
|
||||||
|
err := params.Validate()
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.Require().NoError(err, tc.name)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err, tc.name)
|
||||||
|
suite.Require().Contains(err.Error(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamsTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ParamsTestSuite))
|
||||||
|
}
|
3384
x/bep3/types/query.pb.go
Normal file
3384
x/bep3/types/query.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
503
x/bep3/types/query.pb.gw.go
Normal file
503
x/bep3/types/query.pb.gw.go
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||||
|
// source: zgc/bep3/v1beta1/query.proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package types is a reverse proxy.
|
||||||
|
|
||||||
|
It translates gRPC into RESTful JSON APIs.
|
||||||
|
*/
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/descriptor"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Suppress "imported and not used" errors
|
||||||
|
var _ codes.Code
|
||||||
|
var _ io.Reader
|
||||||
|
var _ status.Status
|
||||||
|
var _ = runtime.String
|
||||||
|
var _ = utilities.NewDoubleArray
|
||||||
|
var _ = descriptor.ForMessage
|
||||||
|
var _ = metadata.Join
|
||||||
|
|
||||||
|
func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryParamsRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryParamsRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := server.Params(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func request_Query_AssetSupply_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAssetSupplyRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["denom"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.Denom, err = runtime.String(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.AssetSupply(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Query_AssetSupply_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAssetSupplyRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["denom"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.Denom, err = runtime.String(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.AssetSupply(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func request_Query_AssetSupplies_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAssetSuppliesRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := client.AssetSupplies(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Query_AssetSupplies_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAssetSuppliesRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := server.AssetSupplies(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func request_Query_AtomicSwap_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAtomicSwapRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["swap_id"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "swap_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.SwapId, err = runtime.String(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "swap_id", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.AtomicSwap(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Query_AtomicSwap_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAtomicSwapRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["swap_id"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "swap_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.SwapId, err = runtime.String(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "swap_id", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.AtomicSwap(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
filter_Query_AtomicSwaps_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||||
|
)
|
||||||
|
|
||||||
|
func request_Query_AtomicSwaps_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAtomicSwapsRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_AtomicSwaps_0); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.AtomicSwaps(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_Query_AtomicSwaps_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq QueryAtomicSwapsRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_AtomicSwaps_0); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.AtomicSwaps(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
|
||||||
|
// UnaryRPC :call QueryServer directly.
|
||||||
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
|
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead.
|
||||||
|
func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error {
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AssetSupply_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Query_AssetSupply_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AssetSupply_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AssetSupplies_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Query_AssetSupplies_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AssetSupplies_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AtomicSwap_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Query_AtomicSwap_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AtomicSwap_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AtomicSwaps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_Query_AtomicSwaps_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AtomicSwaps_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but
|
||||||
|
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||||
|
func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||||
|
conn, err := grpc.Dial(endpoint, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
if cerr := conn.Close(); cerr != nil {
|
||||||
|
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
if cerr := conn.Close(); cerr != nil {
|
||||||
|
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return RegisterQueryHandler(ctx, mux, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterQueryHandler registers the http handlers for service Query to "mux".
|
||||||
|
// The handlers forward requests to the grpc endpoint over "conn".
|
||||||
|
func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||||
|
return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterQueryHandlerClient registers the http handlers for service Query
|
||||||
|
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient".
|
||||||
|
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient"
|
||||||
|
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||||
|
// "QueryClient" to call the correct interceptors.
|
||||||
|
func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error {
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AssetSupply_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Query_AssetSupply_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AssetSupply_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AssetSupplies_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Query_AssetSupplies_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AssetSupplies_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AtomicSwap_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Query_AtomicSwap_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AtomicSwap_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_Query_AtomicSwaps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_Query_AtomicSwaps_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_Query_AtomicSwaps_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"0g-chain", "bep3", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||||
|
|
||||||
|
pattern_Query_AssetSupply_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"0g-chain", "bep3", "v1beta1", "assetsupply", "denom"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||||
|
|
||||||
|
pattern_Query_AssetSupplies_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"0g-chain", "bep3", "v1beta1", "assetsupplies"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||||
|
|
||||||
|
pattern_Query_AtomicSwap_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"0g-chain", "bep3", "v1beta1", "atomicswap", "swap_id"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||||
|
|
||||||
|
pattern_Query_AtomicSwaps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"0g-chain", "bep3", "v1beta1", "atomicswaps"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
forward_Query_Params_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Query_AssetSupply_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Query_AssetSupplies_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Query_AtomicSwap_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Query_AtomicSwaps_0 = runtime.ForwardResponseMessage
|
||||||
|
)
|
64
x/bep3/types/supply.go
Normal file
64
x/bep3/types/supply.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewAssetSupply initializes a new AssetSupply
|
||||||
|
func NewAssetSupply(incomingSupply, outgoingSupply, currentSupply, timeLimitedSupply sdk.Coin, timeElapsed time.Duration) AssetSupply {
|
||||||
|
return AssetSupply{
|
||||||
|
IncomingSupply: incomingSupply,
|
||||||
|
OutgoingSupply: outgoingSupply,
|
||||||
|
CurrentSupply: currentSupply,
|
||||||
|
TimeLimitedCurrentSupply: timeLimitedSupply,
|
||||||
|
TimeElapsed: timeElapsed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic validation of an asset supply fields.
|
||||||
|
func (a AssetSupply) Validate() error {
|
||||||
|
if !a.IncomingSupply.IsValid() {
|
||||||
|
return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "incoming supply %s", a.IncomingSupply)
|
||||||
|
}
|
||||||
|
if !a.OutgoingSupply.IsValid() {
|
||||||
|
return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "outgoing supply %s", a.OutgoingSupply)
|
||||||
|
}
|
||||||
|
if !a.CurrentSupply.IsValid() {
|
||||||
|
return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "current supply %s", a.CurrentSupply)
|
||||||
|
}
|
||||||
|
if !a.TimeLimitedCurrentSupply.IsValid() {
|
||||||
|
return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "time-limited current supply %s", a.TimeLimitedCurrentSupply)
|
||||||
|
}
|
||||||
|
denom := a.CurrentSupply.Denom
|
||||||
|
if (a.IncomingSupply.Denom != denom) ||
|
||||||
|
(a.OutgoingSupply.Denom != denom) ||
|
||||||
|
(a.TimeLimitedCurrentSupply.Denom != denom) {
|
||||||
|
return fmt.Errorf("asset supply denoms do not match %s %s %s %s", a.CurrentSupply.Denom, a.IncomingSupply.Denom, a.OutgoingSupply.Denom, a.TimeLimitedCurrentSupply.Denom)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns if two asset supplies are equal
|
||||||
|
func (a AssetSupply) Equal(b AssetSupply) bool {
|
||||||
|
if a.GetDenom() != b.GetDenom() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (a.IncomingSupply.IsEqual(b.IncomingSupply) &&
|
||||||
|
a.CurrentSupply.IsEqual(b.CurrentSupply) &&
|
||||||
|
a.OutgoingSupply.IsEqual(b.OutgoingSupply) &&
|
||||||
|
a.TimeLimitedCurrentSupply.IsEqual(b.TimeLimitedCurrentSupply) &&
|
||||||
|
a.TimeElapsed == b.TimeElapsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDenom getter method for the denom of the asset supply
|
||||||
|
func (a AssetSupply) GetDenom() string {
|
||||||
|
return a.CurrentSupply.Denom
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetSupplies is a slice of AssetSupply
|
||||||
|
type AssetSupplies []AssetSupply
|
122
x/bep3/types/supply_test.go
Normal file
122
x/bep3/types/supply_test.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAssetSupplyValidate(t *testing.T) {
|
||||||
|
coin := sdk.NewCoin("a0gi", sdk.OneInt())
|
||||||
|
invalidCoin := sdk.Coin{Denom: "Invalid Denom", Amount: sdkmath.NewInt(-1)}
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
asset AssetSupply
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
msg: "valid asset",
|
||||||
|
asset: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||||
|
expPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid incoming supply",
|
||||||
|
AssetSupply{IncomingSupply: invalidCoin},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid outgoing supply",
|
||||||
|
AssetSupply{
|
||||||
|
IncomingSupply: coin,
|
||||||
|
OutgoingSupply: invalidCoin,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid current supply",
|
||||||
|
AssetSupply{
|
||||||
|
IncomingSupply: coin,
|
||||||
|
OutgoingSupply: coin,
|
||||||
|
CurrentSupply: invalidCoin,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid time limitedcurrent supply",
|
||||||
|
AssetSupply{
|
||||||
|
IncomingSupply: coin,
|
||||||
|
OutgoingSupply: coin,
|
||||||
|
CurrentSupply: coin,
|
||||||
|
TimeLimitedCurrentSupply: invalidCoin,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non matching denoms",
|
||||||
|
AssetSupply{
|
||||||
|
IncomingSupply: coin,
|
||||||
|
OutgoingSupply: coin,
|
||||||
|
CurrentSupply: coin,
|
||||||
|
TimeLimitedCurrentSupply: sdk.NewCoin("lol", sdk.ZeroInt()),
|
||||||
|
TimeElapsed: time.Hour,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
err := tc.asset.Validate()
|
||||||
|
if tc.expPass {
|
||||||
|
require.NoError(t, err, tc.msg)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err, tc.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssetSupplyEquality(t *testing.T) {
|
||||||
|
coin := sdk.NewCoin("test", sdk.OneInt())
|
||||||
|
coin2 := sdk.NewCoin("other", sdk.OneInt())
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
asset1 AssetSupply
|
||||||
|
asset2 AssetSupply
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "equal",
|
||||||
|
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||||
|
asset2: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||||
|
expPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not equal duration",
|
||||||
|
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||||
|
asset2: NewAssetSupply(coin, coin, coin, coin, time.Duration(1)),
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not equal coin amount",
|
||||||
|
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||||
|
asset2: NewAssetSupply(sdk.NewCoin("test", sdk.ZeroInt()), coin, coin, coin, time.Duration(1)),
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not equal coin denom",
|
||||||
|
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||||
|
asset2: NewAssetSupply(coin2, coin2, coin2, coin2, time.Duration(1)),
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if tc.expPass {
|
||||||
|
require.True(t, tc.asset1.Equal(tc.asset2), tc.name)
|
||||||
|
} else {
|
||||||
|
require.False(t, tc.asset1.Equal(tc.asset2), tc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
x/bep3/types/swap.go
Normal file
170
x/bep3/types/swap.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
tmbytes "github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewAtomicSwap returns a new AtomicSwap
|
||||||
|
func NewAtomicSwap(amount sdk.Coins, randomNumberHash tmbytes.HexBytes, expireHeight uint64, timestamp int64,
|
||||||
|
sender, recipient sdk.AccAddress, senderOtherChain, recipientOtherChain string, closedBlock int64,
|
||||||
|
status SwapStatus, crossChain bool, direction SwapDirection,
|
||||||
|
) AtomicSwap {
|
||||||
|
return AtomicSwap{
|
||||||
|
Amount: amount,
|
||||||
|
RandomNumberHash: randomNumberHash,
|
||||||
|
ExpireHeight: expireHeight,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Sender: sender,
|
||||||
|
Recipient: recipient,
|
||||||
|
SenderOtherChain: senderOtherChain,
|
||||||
|
RecipientOtherChain: recipientOtherChain,
|
||||||
|
ClosedBlock: closedBlock,
|
||||||
|
Status: status,
|
||||||
|
CrossChain: crossChain,
|
||||||
|
Direction: direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSwapID calculates the ID of an atomic swap
|
||||||
|
func (a AtomicSwap) GetSwapID() tmbytes.HexBytes {
|
||||||
|
return CalculateSwapID(a.RandomNumberHash, a.Sender, a.SenderOtherChain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCoins returns the swap's amount as sdk.Coins
|
||||||
|
func (a AtomicSwap) GetCoins() sdk.Coins {
|
||||||
|
return sdk.NewCoins(a.Amount...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic validation of an atomic swap fields.
|
||||||
|
func (a AtomicSwap) Validate() error {
|
||||||
|
if !a.Amount.IsValid() {
|
||||||
|
return fmt.Errorf("invalid amount: %s", a.Amount)
|
||||||
|
}
|
||||||
|
if !a.Amount.IsAllPositive() {
|
||||||
|
return fmt.Errorf("the swapped out coin must be positive: %s", a.Amount)
|
||||||
|
}
|
||||||
|
if len(a.RandomNumberHash) != RandomNumberHashLength {
|
||||||
|
return fmt.Errorf("the length of random number hash should be %d", RandomNumberHashLength)
|
||||||
|
}
|
||||||
|
if a.ExpireHeight == 0 {
|
||||||
|
return errors.New("expire height cannot be 0")
|
||||||
|
}
|
||||||
|
if a.Timestamp == 0 {
|
||||||
|
return errors.New("timestamp cannot be 0")
|
||||||
|
}
|
||||||
|
if a.Sender.Empty() {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "sender cannot be empty")
|
||||||
|
}
|
||||||
|
if a.Recipient.Empty() {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "recipient cannot be empty")
|
||||||
|
}
|
||||||
|
// NOTE: These adresses may not have a bech32 prefix.
|
||||||
|
if strings.TrimSpace(a.SenderOtherChain) == "" {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "sender other chain cannot be blank")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(a.RecipientOtherChain) == "" {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "recipient other chain cannot be blank")
|
||||||
|
}
|
||||||
|
if a.Status == SWAP_STATUS_COMPLETED && a.ClosedBlock == 0 {
|
||||||
|
return errors.New("closed block cannot be 0")
|
||||||
|
}
|
||||||
|
if a.Status == SWAP_STATUS_UNSPECIFIED || a.Status > 3 {
|
||||||
|
return errors.New("invalid swap status")
|
||||||
|
}
|
||||||
|
if a.Direction == SWAP_DIRECTION_UNSPECIFIED || a.Direction > 2 {
|
||||||
|
return errors.New("invalid swap direction")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtomicSwaps is a slice of AtomicSwap
|
||||||
|
type AtomicSwaps []AtomicSwap
|
||||||
|
|
||||||
|
// NewSwapStatusFromString converts string to SwapStatus type
|
||||||
|
func NewSwapStatusFromString(str string) SwapStatus {
|
||||||
|
switch str {
|
||||||
|
case "Open", "open":
|
||||||
|
return SWAP_STATUS_OPEN
|
||||||
|
case "Completed", "completed":
|
||||||
|
return SWAP_STATUS_COMPLETED
|
||||||
|
case "Expired", "expired":
|
||||||
|
return SWAP_STATUS_EXPIRED
|
||||||
|
default:
|
||||||
|
return SWAP_STATUS_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns true if the swap status is valid and false otherwise.
|
||||||
|
func (status SwapStatus) IsValid() bool {
|
||||||
|
return status == SWAP_STATUS_OPEN ||
|
||||||
|
status == SWAP_STATUS_COMPLETED ||
|
||||||
|
status == SWAP_STATUS_EXPIRED
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSwapDirectionFromString converts string to SwapDirection type
|
||||||
|
func NewSwapDirectionFromString(str string) SwapDirection {
|
||||||
|
switch str {
|
||||||
|
case "Incoming", "incoming", "inc", "I", "i":
|
||||||
|
return SWAP_DIRECTION_INCOMING
|
||||||
|
case "Outgoing", "outgoing", "out", "O", "o":
|
||||||
|
return SWAP_DIRECTION_OUTGOING
|
||||||
|
default:
|
||||||
|
return SWAP_DIRECTION_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns true if the swap direction is valid and false otherwise.
|
||||||
|
func (direction SwapDirection) IsValid() bool {
|
||||||
|
return direction == SWAP_DIRECTION_INCOMING ||
|
||||||
|
direction == SWAP_DIRECTION_OUTGOING
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyAugmentedAtomicSwap defines an ID and AtomicSwap fields on the top level.
|
||||||
|
// This should be removed when legacy REST endpoints are removed.
|
||||||
|
type LegacyAugmentedAtomicSwap struct {
|
||||||
|
ID string `json:"id" yaml:"id"`
|
||||||
|
|
||||||
|
// Embed AtomicSwap fields explicity in order to output as top level JSON fields
|
||||||
|
// This prevents breaking changes for clients using REST API
|
||||||
|
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||||
|
RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"`
|
||||||
|
ExpireHeight uint64 `json:"expire_height" yaml:"expire_height"`
|
||||||
|
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
|
||||||
|
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||||
|
Recipient sdk.AccAddress `json:"recipient" yaml:"recipient"`
|
||||||
|
SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"`
|
||||||
|
RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"`
|
||||||
|
ClosedBlock int64 `json:"closed_block" yaml:"closed_block"`
|
||||||
|
Status SwapStatus `json:"status" yaml:"status"`
|
||||||
|
CrossChain bool `json:"cross_chain" yaml:"cross_chain"`
|
||||||
|
Direction SwapDirection `json:"direction" yaml:"direction"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLegacyAugmentedAtomicSwap(swap AtomicSwap) LegacyAugmentedAtomicSwap {
|
||||||
|
return LegacyAugmentedAtomicSwap{
|
||||||
|
ID: hex.EncodeToString(swap.GetSwapID()),
|
||||||
|
Amount: swap.Amount,
|
||||||
|
RandomNumberHash: swap.RandomNumberHash,
|
||||||
|
ExpireHeight: swap.ExpireHeight,
|
||||||
|
Timestamp: swap.Timestamp,
|
||||||
|
Sender: swap.Sender,
|
||||||
|
Recipient: swap.Recipient,
|
||||||
|
SenderOtherChain: swap.SenderOtherChain,
|
||||||
|
RecipientOtherChain: swap.RecipientOtherChain,
|
||||||
|
ClosedBlock: swap.ClosedBlock,
|
||||||
|
Status: swap.Status,
|
||||||
|
CrossChain: swap.CrossChain,
|
||||||
|
Direction: swap.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyAugmentedAtomicSwaps []LegacyAugmentedAtomicSwap
|
254
x/bep3/types/swap_test.go
Normal file
254
x/bep3/types/swap_test.go
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
tmbytes "github.com/cometbft/cometbft/libs/bytes"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/bep3/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AtomicSwapTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
timestamps []int64
|
||||||
|
randomNumberHashes []tmbytes.HexBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AtomicSwapTestSuite) SetupTest() {
|
||||||
|
// Generate 10 addresses
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(10)
|
||||||
|
|
||||||
|
// Generate 10 timestamps and random number hashes
|
||||||
|
var timestamps []int64
|
||||||
|
var randomNumberHashes []tmbytes.HexBytes
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
timestamp := ts(i)
|
||||||
|
randomNumber, _ := types.GenerateSecureRandomNumber()
|
||||||
|
randomNumberHash := types.CalculateRandomHash(randomNumber[:], timestamp)
|
||||||
|
timestamps = append(timestamps, timestamp)
|
||||||
|
randomNumberHashes = append(randomNumberHashes, randomNumberHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.addrs = addrs
|
||||||
|
suite.timestamps = timestamps
|
||||||
|
suite.randomNumberHashes = randomNumberHashes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AtomicSwapTestSuite) TestNewAtomicSwap() {
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
swap types.AtomicSwap
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid Swap",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 360,
|
||||||
|
Timestamp: suite.timestamps[0],
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: suite.addrs[5],
|
||||||
|
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||||
|
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||||
|
ClosedBlock: 1,
|
||||||
|
Status: types.SWAP_STATUS_OPEN,
|
||||||
|
CrossChain: true,
|
||||||
|
Direction: types.SWAP_DIRECTION_INCOMING,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid amount",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: sdk.Coins{sdk.Coin{Denom: "BNB", Amount: sdkmath.NewInt(10)}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount not positive",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 0)),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid random number hash length",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[1][0:20],
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exp height 0",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 0,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp 0",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 0,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty sender",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: nil,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty recipient",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: nil,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid sender length",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0][:10],
|
||||||
|
Recipient: suite.addrs[5],
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid recipient length",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: suite.addrs[5][:10],
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid sender other chain",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: suite.addrs[5],
|
||||||
|
SenderOtherChain: "",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid recipient other chain",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: suite.addrs[5],
|
||||||
|
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||||
|
RecipientOtherChain: "",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"closed block 0",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: suite.addrs[5],
|
||||||
|
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||||
|
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||||
|
ClosedBlock: 0,
|
||||||
|
Status: types.SWAP_STATUS_COMPLETED,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid status 0",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: suite.addrs[5],
|
||||||
|
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||||
|
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||||
|
ClosedBlock: 1,
|
||||||
|
Status: types.SWAP_STATUS_UNSPECIFIED,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid direction ",
|
||||||
|
types.AtomicSwap{
|
||||||
|
Amount: cs(c("bnb", 50000)),
|
||||||
|
RandomNumberHash: suite.randomNumberHashes[0],
|
||||||
|
ExpireHeight: 10,
|
||||||
|
Timestamp: 10,
|
||||||
|
Sender: suite.addrs[0],
|
||||||
|
Recipient: suite.addrs[5],
|
||||||
|
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||||
|
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||||
|
ClosedBlock: 1,
|
||||||
|
Status: types.SWAP_STATUS_OPEN,
|
||||||
|
Direction: types.SWAP_DIRECTION_UNSPECIFIED,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
|
||||||
|
err := tc.swap.Validate()
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.Require().NoError(err, tc.msg)
|
||||||
|
suite.Require().Equal(tc.swap.Amount, tc.swap.GetCoins())
|
||||||
|
|
||||||
|
expectedSwapID := types.CalculateSwapID(tc.swap.RandomNumberHash, tc.swap.Sender, tc.swap.SenderOtherChain)
|
||||||
|
suite.Require().Equal(tmbytes.HexBytes(expectedSwapID), tc.swap.GetSwapID())
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAtomicSwapTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(AtomicSwapTestSuite))
|
||||||
|
}
|
1607
x/bep3/types/tx.pb.go
Normal file
1607
x/bep3/types/tx.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
20
x/committee/abci.go
Normal file
20
x/committee/abci.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package committee
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/telemetry"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
abci "github.com/cometbft/cometbft/abci/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeginBlocker runs at the start of every block.
|
||||||
|
func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k keeper.Keeper) {
|
||||||
|
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
|
||||||
|
|
||||||
|
k.ProcessProposals(ctx)
|
||||||
|
}
|
228
x/committee/abci_test.go
Normal file
228
x/committee/abci_test.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package committee_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
abci "github.com/cometbft/cometbft/abci/types"
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
// "github.com/0glabs/0g-chain/x/cdp"
|
||||||
|
// cdptypes "github.com/0glabs/0g-chain/x/cdp/types"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/testutil"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModuleTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
|
||||||
|
addresses []sdk.AccAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ModuleTestSuite) SetupTest() {
|
||||||
|
suite.app = app.NewTestApp()
|
||||||
|
suite.keeper = suite.app.GetCommitteeKeeper()
|
||||||
|
suite.ctx = suite.app.NewContext(true, tmproto.Header{})
|
||||||
|
_, suite.addresses = app.GeneratePrivKeyAddressPairs(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ModuleTestSuite) TestBeginBlock_ClosesExpired() {
|
||||||
|
suite.app.InitializeFromGenesisStates()
|
||||||
|
|
||||||
|
memberCom := types.MustNewMemberCommittee(
|
||||||
|
12,
|
||||||
|
"This committee is for testing.",
|
||||||
|
suite.addresses[:2],
|
||||||
|
[]types.Permission{&types.GodPermission{}},
|
||||||
|
testutil.D("0.8"),
|
||||||
|
time.Hour*24*7,
|
||||||
|
types.TALLY_OPTION_DEADLINE,
|
||||||
|
)
|
||||||
|
suite.keeper.SetCommittee(suite.ctx, memberCom)
|
||||||
|
|
||||||
|
pprop1 := govv1beta1.NewTextProposal("Title 1", "A description of this proposal.")
|
||||||
|
id1, err := suite.keeper.SubmitProposal(suite.ctx, memberCom.Members[0], memberCom.ID, pprop1)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
oneHrLaterCtx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour))
|
||||||
|
pprop2 := govv1beta1.NewTextProposal("Title 2", "A description of this proposal.")
|
||||||
|
id2, err := suite.keeper.SubmitProposal(oneHrLaterCtx, memberCom.Members[0], memberCom.ID, pprop2)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Run BeginBlocker
|
||||||
|
proposalDurationLaterCtx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(memberCom.ProposalDuration))
|
||||||
|
suite.NotPanics(func() {
|
||||||
|
committee.BeginBlocker(proposalDurationLaterCtx, abci.RequestBeginBlock{}, suite.keeper)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check expired proposals are gone
|
||||||
|
_, found := suite.keeper.GetProposal(suite.ctx, id1)
|
||||||
|
suite.False(found, "expected expired proposal to be closed")
|
||||||
|
_, found = suite.keeper.GetProposal(suite.ctx, id2)
|
||||||
|
suite.True(found, "expected non expired proposal to be not closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (suite *ModuleTestSuite) TestBeginBlock_EnactsPassed() {
|
||||||
|
// suite.app.InitializeFromGenesisStates()
|
||||||
|
|
||||||
|
// // setup committee
|
||||||
|
// normalCom := types.MustNewMemberCommittee(12, "committee description", suite.addresses[:2],
|
||||||
|
// []types.Permission{&types.GodPermission{}}, testutil.D("0.8"), time.Hour*24*7, types.TALLY_OPTION_FIRST_PAST_THE_POST)
|
||||||
|
|
||||||
|
// suite.keeper.SetCommittee(suite.ctx, normalCom)
|
||||||
|
|
||||||
|
// // setup 2 proposals
|
||||||
|
// previousCDPDebtThreshold := suite.app.GetCDPKeeper().GetParams(suite.ctx).DebtAuctionThreshold
|
||||||
|
// newDebtThreshold := previousCDPDebtThreshold.Add(i(1000000))
|
||||||
|
// evenNewerDebtThreshold := newDebtThreshold.Add(i(1000000))
|
||||||
|
|
||||||
|
// pprop1 := params.NewParameterChangeProposal("Title 1", "A description of this proposal.",
|
||||||
|
// []params.ParamChange{{
|
||||||
|
// Subspace: cdptypes.ModuleName,
|
||||||
|
// Key: string(cdp.KeyDebtThreshold),
|
||||||
|
// Value: string(cdp.ModuleCdc.MustMarshalJSON(newDebtThreshold)),
|
||||||
|
// }},
|
||||||
|
// )
|
||||||
|
// id1, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop1)
|
||||||
|
// suite.NoError(err)
|
||||||
|
|
||||||
|
// pprop2 := params.NewParameterChangeProposal("Title 2", "A description of this proposal.",
|
||||||
|
// []params.ParamChange{{
|
||||||
|
// Subspace: cdptypes.ModuleName,
|
||||||
|
// Key: string(cdp.KeyDebtThreshold),
|
||||||
|
// Value: string(cdp.ModuleCdc.MustMarshalJSON(evenNewerDebtThreshold)),
|
||||||
|
// }},
|
||||||
|
// )
|
||||||
|
// id2, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop2)
|
||||||
|
// suite.NoError(err)
|
||||||
|
|
||||||
|
// // add enough votes to make the first proposal pass, but not the second
|
||||||
|
// suite.NoError(suite.keeper.AddVote(suite.ctx, id1, suite.addresses[0], types.Yes))
|
||||||
|
// suite.NoError(suite.keeper.AddVote(suite.ctx, id1, suite.addresses[1], types.Yes))
|
||||||
|
// suite.NoError(suite.keeper.AddVote(suite.ctx, id2, suite.addresses[0], types.Yes))
|
||||||
|
|
||||||
|
// // Run BeginBlocker
|
||||||
|
// suite.NotPanics(func() {
|
||||||
|
// committee.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}, suite.keeper)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // Check the param has been updated
|
||||||
|
// suite.Equal(newDebtThreshold, suite.app.GetCDPKeeper().GetParams(suite.ctx).DebtAuctionThreshold)
|
||||||
|
// // Check the passed proposal has gone
|
||||||
|
// _, found := suite.keeper.GetProposal(suite.ctx, id1)
|
||||||
|
// suite.False(found, "expected passed proposal to be enacted and closed")
|
||||||
|
// _, found = suite.keeper.GetProposal(suite.ctx, id2)
|
||||||
|
// suite.True(found, "expected non passed proposal to be not closed")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (suite *ModuleTestSuite) TestBeginBlock_DoesntEnactFailed() {
|
||||||
|
// suite.app.InitializeFromGenesisStates()
|
||||||
|
|
||||||
|
// // setup committee
|
||||||
|
// memberCom := types.MustNewMemberCommittee(12, "committee description", suite.addresses[:1],
|
||||||
|
// []types.Permission{types.SoftwareUpgradePermission{}}, testutil.D("1.0"), time.Hour*24*7, types.TALLY_OPTION_FIRST_PAST_THE_POST)
|
||||||
|
|
||||||
|
// firstBlockTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
// ctx := suite.ctx.WithBlockTime(firstBlockTime)
|
||||||
|
// suite.keeper.SetCommittee(ctx, memberCom)
|
||||||
|
|
||||||
|
// // setup an upgrade proposal
|
||||||
|
// pprop1 := upgradetypes.NewSoftwareUpgradeProposal("Title 1", "A description of this proposal.",
|
||||||
|
// upgradetypes.Plan{
|
||||||
|
// Name: "upgrade-version-v0.23.1",
|
||||||
|
// Time: firstBlockTime.Add(time.Second * 5),
|
||||||
|
// Info: "some information about the upgrade",
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// id1, err := suite.keeper.SubmitProposal(ctx, memberCom.Members[0], memberCom.ID, pprop1)
|
||||||
|
// suite.NoError(err)
|
||||||
|
|
||||||
|
// // add enough votes to make the proposal pass
|
||||||
|
// suite.NoError(suite.keeper.AddVote(ctx, id1, suite.addresses[0], types.Yes))
|
||||||
|
|
||||||
|
// // Run BeginBlocker 10 seconds later (5 seconds after upgrade expires)
|
||||||
|
// tenSecLaterCtx := ctx.WithBlockTime(ctx.BlockTime().Add(time.Second * 10))
|
||||||
|
// suite.NotPanics(func() {
|
||||||
|
// suite.app.BeginBlocker(tenSecLaterCtx, abci.RequestBeginBlock{})
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // Check the plan has not been stored
|
||||||
|
// _, found := suite.app.GetUpgradeKeeper().GetUpgradePlan(tenSecLaterCtx)
|
||||||
|
// suite.False(found)
|
||||||
|
// // Check the passed proposal has gone
|
||||||
|
// _, found = suite.keeper.GetProposal(tenSecLaterCtx, id1)
|
||||||
|
// suite.False(found, "expected failed proposal to be not enacted and closed")
|
||||||
|
|
||||||
|
// // Check the chain doesn't halt
|
||||||
|
// oneMinLaterCtx := ctx.WithBlockTime(ctx.BlockTime().Add(time.Minute).Add(time.Second))
|
||||||
|
// suite.NotPanics(func() {
|
||||||
|
// suite.app.BeginBlocker(oneMinLaterCtx, abci.RequestBeginBlock{})
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (suite *ModuleTestSuite) TestBeginBlock_EnactsPassedUpgrade() {
|
||||||
|
// suite.app.InitializeFromGenesisStates()
|
||||||
|
|
||||||
|
// // setup committee
|
||||||
|
// memberCom := types.MustNewMemberCommittee(
|
||||||
|
// 12,
|
||||||
|
// "committee description",
|
||||||
|
// suite.addresses[:1],
|
||||||
|
// []types.Permission{types.SoftwareUpgradePermission{}},
|
||||||
|
// testutil.D("1.0"),
|
||||||
|
// time.Hour*24*7,
|
||||||
|
// types.TALLY_OPTION_FIRST_PAST_THE_POST,
|
||||||
|
// )
|
||||||
|
|
||||||
|
// firstBlockTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
// ctx := suite.ctx.WithBlockTime(firstBlockTime)
|
||||||
|
// suite.keeper.SetCommittee(ctx, memberCom)
|
||||||
|
|
||||||
|
// // setup an upgrade proposal
|
||||||
|
// pprop1 := upgradetypes.NewSoftwareUpgradeProposal("Title 1", "A description of this proposal.",
|
||||||
|
// upgradetypes.Plan{
|
||||||
|
// Name: "upgrade-version-v0.23.1",
|
||||||
|
// Time: firstBlockTime.Add(time.Minute * 1),
|
||||||
|
// Info: "some information about the upgrade",
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// id1, err := suite.keeper.SubmitProposal(ctx, memberCom.Members[0], memberCom.ID, pprop1)
|
||||||
|
// suite.NoError(err)
|
||||||
|
|
||||||
|
// // add enough votes to make the proposal pass
|
||||||
|
// suite.NoError(suite.keeper.AddVote(ctx, id1, suite.addresses[0], types.Yes))
|
||||||
|
|
||||||
|
// // Run BeginBlocker
|
||||||
|
// fiveSecLaterCtx := ctx.WithBlockTime(ctx.BlockTime().Add(time.Second * 5))
|
||||||
|
// suite.NotPanics(func() {
|
||||||
|
// suite.app.BeginBlocker(fiveSecLaterCtx, abci.RequestBeginBlock{})
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // Check the plan has been stored
|
||||||
|
// _, found := suite.app.GetUpgradeKeeper().GetUpgradePlan(fiveSecLaterCtx)
|
||||||
|
// suite.True(found)
|
||||||
|
// // Check the passed proposal has gone
|
||||||
|
// _, found = suite.keeper.GetProposal(fiveSecLaterCtx, id1)
|
||||||
|
// suite.False(found, "expected passed proposal to be enacted and closed")
|
||||||
|
|
||||||
|
// // Check the chain halts
|
||||||
|
// oneMinLaterCtx := ctx.WithBlockTime(ctx.BlockTime().Add(time.Minute))
|
||||||
|
// suite.Panics(func() {
|
||||||
|
// suite.app.BeginBlocker(oneMinLaterCtx, abci.RequestBeginBlock{})
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestModuleTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ModuleTestSuite))
|
||||||
|
}
|
38
x/committee/client/cli/cli_test.go
Normal file
38
x/committee/client/cli/cli_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/client/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CLITestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
cdc codec.Codec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CLITestSuite) SetupTest() {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
suite.cdc = tApp.AppCodec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CLITestSuite) TestExampleCommitteeChangeProposal_NotPanics() {
|
||||||
|
suite.NotPanics(func() { cli.MustGetExampleCommitteeChangeProposal(suite.cdc) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CLITestSuite) TestExampleCommitteeDeleteProposal_NotPanics() {
|
||||||
|
suite.NotPanics(func() { cli.MustGetExampleCommitteeDeleteProposal(suite.cdc) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CLITestSuite) TestExampleParameterChangeProposal_NotPanics() {
|
||||||
|
suite.NotPanics(func() { cli.MustGetExampleParameterChangeProposal(suite.cdc) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCLITestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(CLITestSuite))
|
||||||
|
}
|
318
x/committee/client/cli/query.go
Normal file
318
x/committee/client/cli/query.go
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/client/common"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetQueryCmd returns the cli query commands for this module
|
||||||
|
func GetQueryCmd() *cobra.Command {
|
||||||
|
queryCmd := &cobra.Command{
|
||||||
|
Use: types.ModuleName,
|
||||||
|
Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
SuggestionsMinimumDistance: 2,
|
||||||
|
RunE: client.ValidateCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := []*cobra.Command{
|
||||||
|
// committees
|
||||||
|
getCmdQueryCommittee(),
|
||||||
|
getCmdQueryCommittees(),
|
||||||
|
// proposals
|
||||||
|
getCmdQueryNextProposalID(),
|
||||||
|
getCmdQueryProposal(),
|
||||||
|
getCmdQueryProposals(),
|
||||||
|
// votes
|
||||||
|
getCmdQueryVotes(),
|
||||||
|
// other
|
||||||
|
getCmdQueryProposer(),
|
||||||
|
getCmdQueryTally(),
|
||||||
|
getCmdQueryRawParams(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
flags.AddQueryFlagsToCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryCmd.AddCommand(cmds...)
|
||||||
|
|
||||||
|
return queryCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Committees
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// getCmdQueryCommittee implements a query committee command.
|
||||||
|
func getCmdQueryCommittee() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "committee [committee-id]",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Short: "Query details of a single committee",
|
||||||
|
Example: fmt.Sprintf("%s query %s committee 1", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate that the committee id is a uint
|
||||||
|
committeeID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("committee-id %s not a valid uint, please input a valid committee-id", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.Committee(context.Background(), &types.QueryCommitteeRequest{CommitteeId: committeeID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCmdQueryCommittees implements a query committees command.
|
||||||
|
func getCmdQueryCommittees() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "committees",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Short: "Query all committees",
|
||||||
|
Example: fmt.Sprintf("%s query %s committees", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.Committees(context.Background(), &types.QueryCommitteesRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Proposals
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// getCmdQueryNextProposalID implements a query next proposal ID command.
|
||||||
|
func getCmdQueryNextProposalID() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "next-proposal-id",
|
||||||
|
Short: "Query the next proposal ID",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
Example: fmt.Sprintf("%s query %s next-proposal-id", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.NextProposalID(context.Background(), &types.QueryNextProposalIDRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCmdQueryProposal implements the query proposal command.
|
||||||
|
func getCmdQueryProposal() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "proposal [proposal-id]",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Short: "Query details of a single proposal",
|
||||||
|
Example: fmt.Sprintf("%s query %s proposal 2", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare params for querier
|
||||||
|
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("proposal-id %s not a valid uint", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.Proposal(context.Background(), &types.QueryProposalRequest{
|
||||||
|
ProposalId: proposalID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCmdQueryProposals implements a query proposals command.
|
||||||
|
func getCmdQueryProposals() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "proposals [committee-id]",
|
||||||
|
Short: "Query all proposals for a committee",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Example: fmt.Sprintf("%s query %s proposals 1", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare params for querier
|
||||||
|
committeeID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("committee-id %s not a valid uint", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.Proposals(context.Background(), &types.QueryProposalsRequest{
|
||||||
|
CommitteeId: committeeID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Votes
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// getCmdQueryVotes implements the command to query for proposal votes.
|
||||||
|
func getCmdQueryVotes() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "votes [proposal-id]",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Short: "Query votes on a proposal",
|
||||||
|
Example: fmt.Sprintf("%s query %s votes 2", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare params for querier
|
||||||
|
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("proposal-id %s not a valid int", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.Votes(context.Background(), &types.QueryVotesRequest{
|
||||||
|
ProposalId: proposalID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Other
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
func getCmdQueryTally() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "tally [proposal-id]",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Short: "Get the current tally of votes on a proposal",
|
||||||
|
Long: "Query the current tally of votes on a proposal to see the progress of the voting.",
|
||||||
|
Example: fmt.Sprintf("%s query %s tally 2", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare params for querier
|
||||||
|
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("proposal-id %s not a valid int", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.Tally(context.Background(), &types.QueryTallyRequest{
|
||||||
|
ProposalId: proposalID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCmdQueryProposer() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "proposer [proposal-id]",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Short: "Query the proposer of a governance proposal",
|
||||||
|
Long: "Query which address proposed a proposal with a given ID.",
|
||||||
|
Example: fmt.Sprintf("%s query %s proposer 2", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate that the proposalID is a uint
|
||||||
|
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("proposal-id %s is not a valid uint", args[0])
|
||||||
|
}
|
||||||
|
prop, err := common.QueryProposer(clientCtx, proposalID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientCtx.PrintObjectLegacy(prop)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCmdQueryRawParams() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "raw-params [subspace] [key]",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
Short: "Query raw parameter values from any module.",
|
||||||
|
Long: "Query the byte value of any module's parameters. Useful in debugging and verifying governance proposals.",
|
||||||
|
Example: fmt.Sprintf("%s query %s raw-params cdp CollateralParams", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient := types.NewQueryClient(clientCtx)
|
||||||
|
res, err := queryClient.RawParams(context.Background(), &types.QueryRawParamsRequest{
|
||||||
|
Subspace: args[0],
|
||||||
|
Key: args[1],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientCtx.PrintProto(res)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
325
x/committee/client/cli/tx.go
Normal file
325
x/committee/client/cli/tx.go
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cometbft/cometbft/crypto"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
|
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
|
paramsproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PARAMS_CHANGE_PROPOSAL_EXAMPLE = `
|
||||||
|
{
|
||||||
|
"@type": "/cosmos.params.v1beta1.ParameterChangeProposal",
|
||||||
|
"title": "title",
|
||||||
|
"description": "description",
|
||||||
|
"changes": [{ "subspace": "subspace", "key": "key", "value": "value" }]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const COMMITTEE_CHANGE_PROPOSAL_EXAMPLE = `
|
||||||
|
{
|
||||||
|
"@type": "/0g-chain.committee.v1beta1.CommitteeChangeProposal",
|
||||||
|
"title": "A Title",
|
||||||
|
"description": "A proposal description.",
|
||||||
|
"new_committee": {
|
||||||
|
"@type": "/0g-chain.committee.v1beta1.MemberCommittee",
|
||||||
|
"base_committee": {
|
||||||
|
"id": "34",
|
||||||
|
"description": "member committee",
|
||||||
|
"members": ["0g1ze7y9qwdddejmy7jlw4cymqqlt2wh05yhwmrv2"],
|
||||||
|
"permissions": [],
|
||||||
|
"vote_threshold": "1.000000000000000000",
|
||||||
|
"proposal_duration": "86400s",
|
||||||
|
"tally_option": "TALLY_OPTION_DEADLINE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const COMMITTEE_DELETE_PROPOSAL_EXAMPLE = `
|
||||||
|
{
|
||||||
|
"@type": "/0g-chain.committee.v1beta1.CommitteeDeleteProposal",
|
||||||
|
"title": "A Title",
|
||||||
|
"description": "A proposal description.",
|
||||||
|
"committee_id": "1"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func GetTxCmd() *cobra.Command {
|
||||||
|
txCmd := &cobra.Command{
|
||||||
|
Use: types.ModuleName,
|
||||||
|
Short: "committee governance transactions subcommands",
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
SuggestionsMinimumDistance: 2,
|
||||||
|
RunE: client.ValidateCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := []*cobra.Command{
|
||||||
|
getCmdVote(),
|
||||||
|
getCmdSubmitProposal(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
flags.AddTxFlagsToCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
txCmd.AddCommand(cmds...)
|
||||||
|
|
||||||
|
return txCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCmdSubmitProposal returns the command to submit a proposal to a committee
|
||||||
|
func getCmdSubmitProposal() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "submit-proposal [committee-id] [proposal-file]",
|
||||||
|
Short: "Submit a governance proposal to a particular committee",
|
||||||
|
Long: fmt.Sprintf(`Submit a proposal to a committee so they can vote on it.
|
||||||
|
|
||||||
|
The proposal file must be the json encoded forms of the proposal type you want to submit.
|
||||||
|
For example:
|
||||||
|
%s
|
||||||
|
`, PARAMS_CHANGE_PROPOSAL_EXAMPLE),
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
Example: fmt.Sprintf("%s tx %s submit-proposal 1 your-proposal.json", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get proposing address
|
||||||
|
proposer := clientCtx.GetFromAddress()
|
||||||
|
|
||||||
|
// Get committee ID
|
||||||
|
committeeID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("committee-id %s not a valid int", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get proposal content
|
||||||
|
var pubProposal types.PubProposal
|
||||||
|
contents, err := ioutil.ReadFile(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := clientCtx.Codec.UnmarshalInterfaceJSON(contents, &pubProposal); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = pubProposal.ValidateBasic(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build message and run basic validation
|
||||||
|
msg, err := types.NewMsgSubmitProposal(pubProposal, proposer, committeeID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = msg.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign and broadcast message
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCmdVote returns the command to vote on a proposal.
|
||||||
|
func getCmdVote() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "vote [proposal-id] [vote]",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
Short: "Vote for an active proposal",
|
||||||
|
Long: "Submit a [yes/no/abstain] vote for the proposal with id [proposal-id].",
|
||||||
|
Example: fmt.Sprintf("%s tx %s vote 2 yes", version.AppName, types.ModuleName),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get voting address
|
||||||
|
from := clientCtx.GetFromAddress()
|
||||||
|
|
||||||
|
// validate that the proposal id is a uint
|
||||||
|
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
rawVote := strings.ToLower(strings.TrimSpace(args[1]))
|
||||||
|
if len(rawVote) == 0 {
|
||||||
|
return fmt.Errorf("must specify a vote")
|
||||||
|
}
|
||||||
|
|
||||||
|
var vote types.VoteType
|
||||||
|
switch rawVote {
|
||||||
|
case "yes", "y":
|
||||||
|
vote = types.VOTE_TYPE_YES
|
||||||
|
case "no", "n":
|
||||||
|
vote = types.VOTE_TYPE_NO
|
||||||
|
case "abstain", "a":
|
||||||
|
vote = types.VOTE_TYPE_ABSTAIN
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("must specify a valid vote type: (yes/y, no/n, abstain/a)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build vote message and run basic validation
|
||||||
|
msg := types.NewMsgVote(from, proposalID, vote)
|
||||||
|
err = msg.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGovCmdSubmitProposal returns a command to submit a proposal to the gov module. It is passed to the gov module for use on its command subtree.
|
||||||
|
func GetGovCmdSubmitProposal() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "committee [proposal-file] [deposit]",
|
||||||
|
Short: "Submit a governance proposal to change a committee.",
|
||||||
|
Long: fmt.Sprintf(`Submit a governance proposal to create, alter, or delete a committee.
|
||||||
|
|
||||||
|
The proposal file must be the json encoded form of the proposal type you want to submit.
|
||||||
|
For example, to create or update a committee:
|
||||||
|
%s
|
||||||
|
|
||||||
|
and to delete a committee:
|
||||||
|
%s
|
||||||
|
`, COMMITTEE_CHANGE_PROPOSAL_EXAMPLE, COMMITTEE_DELETE_PROPOSAL_EXAMPLE),
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get proposing address
|
||||||
|
proposer := clientCtx.GetFromAddress()
|
||||||
|
|
||||||
|
// Get the deposit
|
||||||
|
deposit, err := sdk.ParseCoinsNormalized(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the proposal
|
||||||
|
bz, err := ioutil.ReadFile(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var content govv1beta1.Content
|
||||||
|
if err := clientCtx.Codec.UnmarshalInterfaceJSON(bz, &content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = content.ValidateBasic(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build message and run basic validation
|
||||||
|
msg, err := govv1beta1.NewMsgSubmitProposal(content, deposit, proposer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = msg.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign and broadcast message
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetExampleCommitteeChangeProposal is a helper function to return an example json proposal
|
||||||
|
func MustGetExampleCommitteeChangeProposal(cdc codec.Codec) string {
|
||||||
|
exampleChangeProposal, err := types.NewCommitteeChangeProposal(
|
||||||
|
"A Title",
|
||||||
|
"A description of this proposal.",
|
||||||
|
types.MustNewMemberCommittee(
|
||||||
|
1,
|
||||||
|
"The description of this committee.",
|
||||||
|
[]sdk.AccAddress{sdk.AccAddress(crypto.AddressHash([]byte("exampleAddress")))},
|
||||||
|
[]types.Permission{
|
||||||
|
&types.GodPermission{},
|
||||||
|
},
|
||||||
|
sdk.MustNewDecFromStr("0.8"),
|
||||||
|
time.Hour*24*7,
|
||||||
|
types.TALLY_OPTION_FIRST_PAST_THE_POST,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
exampleChangeProposalBz, err := cdc.MarshalJSON(&exampleChangeProposal)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var out bytes.Buffer
|
||||||
|
if err = json.Indent(&out, exampleChangeProposalBz, "", " "); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetExampleCommitteeDeleteProposal is a helper function to return an example json proposal
|
||||||
|
func MustGetExampleCommitteeDeleteProposal(cdc codec.Codec) string {
|
||||||
|
exampleDeleteProposal := types.NewCommitteeDeleteProposal(
|
||||||
|
"A Title",
|
||||||
|
"A description of this proposal.",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
bz, err := cdc.MarshalJSON(&exampleDeleteProposal)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var out bytes.Buffer
|
||||||
|
if err = json.Indent(&out, bz, "", " "); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetExampleParameterChangeProposal is a helper function to return an example json proposal
|
||||||
|
func MustGetExampleParameterChangeProposal(cdc codec.Codec) string {
|
||||||
|
value := fmt.Sprintf("\"%d\"", 1000000000)
|
||||||
|
exampleParameterChangeProposal := paramsproposal.NewParameterChangeProposal(
|
||||||
|
"A Title",
|
||||||
|
"A description of this proposal.",
|
||||||
|
[]paramsproposal.ParamChange{paramsproposal.NewParamChange("cdp", "SurplusAuctionThreshold", value)},
|
||||||
|
)
|
||||||
|
bz, err := cdc.MarshalJSON(exampleParameterChangeProposal)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var out bytes.Buffer
|
||||||
|
if err = json.Indent(&out, bz, "", " "); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
59
x/committee/client/common/query.go
Normal file
59
x/committee/client/common/query.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note: QueryProposer is copied in from the gov module
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPage = 1
|
||||||
|
defaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
||||||
|
)
|
||||||
|
|
||||||
|
// Proposer contains metadata of a governance proposal used for querying a proposer.
|
||||||
|
type Proposer struct {
|
||||||
|
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
|
||||||
|
Proposer string `json:"proposer" yaml:"proposer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProposer returns a new Proposer given id and proposer
|
||||||
|
func NewProposer(proposalID uint64, proposer string) Proposer {
|
||||||
|
return Proposer{proposalID, proposer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Proposer) String() string {
|
||||||
|
return fmt.Sprintf("Proposal with ID %d was proposed by %s", p.ProposalID, p.Proposer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryProposer will query for a proposer of a governance proposal by ID.
|
||||||
|
func QueryProposer(cliCtx client.Context, proposalID uint64) (Proposer, error) {
|
||||||
|
events := []string{
|
||||||
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal),
|
||||||
|
fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalSubmit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: SearchTxs is used to facilitate the txs query which does not currently
|
||||||
|
// support configurable pagination.
|
||||||
|
searchResult, err := authtx.QueryTxsByEvents(cliCtx, events, defaultPage, defaultLimit, "")
|
||||||
|
if err != nil {
|
||||||
|
return Proposer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, info := range searchResult.Txs {
|
||||||
|
for _, msg := range info.GetTx().GetMsgs() {
|
||||||
|
// there should only be a single proposal under the given conditions
|
||||||
|
if subMsg, ok := msg.(*types.MsgSubmitProposal); ok {
|
||||||
|
return NewProposer(proposalID, subMsg.Proposer), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Proposer{}, fmt.Errorf("failed to find the proposer for proposalID %d", proposalID)
|
||||||
|
}
|
10
x/committee/client/proposal_handler.go
Normal file
10
x/committee/client/proposal_handler.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/client/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProposalHandler is a struct containing handler funcs for submiting CommitteeChange/Delete proposal txs to the gov module through the cli or rest.
|
||||||
|
var ProposalHandler = govclient.NewProposalHandler(cli.GetGovCmdSubmitProposal)
|
47
x/committee/genesis.go
Normal file
47
x/committee/genesis.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package committee
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitGenesis initializes the store state from a genesis state.
|
||||||
|
func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, gs *types.GenesisState) {
|
||||||
|
if err := gs.Validate(); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
keeper.SetNextProposalID(ctx, gs.NextProposalID)
|
||||||
|
|
||||||
|
for _, com := range gs.GetCommittees() {
|
||||||
|
keeper.SetCommittee(ctx, com)
|
||||||
|
}
|
||||||
|
for _, p := range gs.Proposals {
|
||||||
|
keeper.SetProposal(ctx, p)
|
||||||
|
}
|
||||||
|
for _, v := range gs.Votes {
|
||||||
|
keeper.SetVote(ctx, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportGenesis returns a GenesisState for a given context and keeper.
|
||||||
|
func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState {
|
||||||
|
nextID, err := keeper.GetNextProposalID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
committees := keeper.GetCommittees(ctx)
|
||||||
|
proposals := keeper.GetProposals(ctx)
|
||||||
|
votes := keeper.GetVotes(ctx)
|
||||||
|
|
||||||
|
return types.NewGenesisState(
|
||||||
|
nextID,
|
||||||
|
committees,
|
||||||
|
proposals,
|
||||||
|
votes,
|
||||||
|
)
|
||||||
|
}
|
163
x/committee/genesis_test.go
Normal file
163
x/committee/genesis_test.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package committee_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/testutil"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenesisTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
keeper keeper.Keeper
|
||||||
|
addresses []sdk.AccAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) SetupTest() {
|
||||||
|
suite.app = app.NewTestApp()
|
||||||
|
suite.keeper = suite.app.GetCommitteeKeeper()
|
||||||
|
suite.ctx = suite.app.NewContext(true, tmproto.Header{})
|
||||||
|
_, suite.addresses = app.GeneratePrivKeyAddressPairs(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) TestInitGenesis() {
|
||||||
|
memberCom := types.MustNewMemberCommittee(
|
||||||
|
1,
|
||||||
|
"This member committee is for testing.",
|
||||||
|
suite.addresses[:2],
|
||||||
|
[]types.Permission{&types.GodPermission{}},
|
||||||
|
testutil.D("0.667"),
|
||||||
|
time.Hour*24*7,
|
||||||
|
types.TALLY_OPTION_FIRST_PAST_THE_POST,
|
||||||
|
)
|
||||||
|
|
||||||
|
tokenCom := types.MustNewTokenCommittee(
|
||||||
|
1,
|
||||||
|
"This token committee is for testing.",
|
||||||
|
suite.addresses[:2],
|
||||||
|
[]types.Permission{&types.GodPermission{}},
|
||||||
|
testutil.D("0.667"),
|
||||||
|
time.Hour*24*7,
|
||||||
|
types.TALLY_OPTION_FIRST_PAST_THE_POST,
|
||||||
|
testutil.D("0.4"),
|
||||||
|
"hard",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Most genesis validation tests are located in the types directory. The 'invalid' test cases are
|
||||||
|
// randomly selected subset of those tests.
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
genState *types.GenesisState
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "normal",
|
||||||
|
genState: types.DefaultGenesisState(),
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "member committee is correctly validated",
|
||||||
|
genState: types.NewGenesisState(
|
||||||
|
1,
|
||||||
|
[]types.Committee{memberCom},
|
||||||
|
[]types.Proposal{},
|
||||||
|
[]types.Vote{},
|
||||||
|
),
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token committee is correctly validated",
|
||||||
|
genState: types.NewGenesisState(
|
||||||
|
1,
|
||||||
|
[]types.Committee{tokenCom},
|
||||||
|
[]types.Proposal{},
|
||||||
|
[]types.Vote{},
|
||||||
|
),
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid: duplicate committee ID",
|
||||||
|
genState: types.NewGenesisState(
|
||||||
|
1,
|
||||||
|
[]types.Committee{memberCom, memberCom},
|
||||||
|
[]types.Proposal{},
|
||||||
|
[]types.Vote{},
|
||||||
|
),
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid: proposal doesn't have committee",
|
||||||
|
genState: types.NewGenesisState(
|
||||||
|
2,
|
||||||
|
[]types.Committee{},
|
||||||
|
[]types.Proposal{{ID: 1, CommitteeID: 57}},
|
||||||
|
[]types.Vote{},
|
||||||
|
),
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid: vote doesn't have proposal",
|
||||||
|
genState: types.NewGenesisState(
|
||||||
|
1,
|
||||||
|
[]types.Committee{},
|
||||||
|
[]types.Proposal{},
|
||||||
|
[]types.Vote{{Voter: suite.addresses[0], ProposalID: 1, VoteType: types.VOTE_TYPE_YES}},
|
||||||
|
),
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid: next proposal ID isn't greater than proposal ID",
|
||||||
|
genState: types.NewGenesisState(
|
||||||
|
4,
|
||||||
|
[]types.Committee{memberCom},
|
||||||
|
[]types.Proposal{{ID: 3, CommitteeID: 1}, {ID: 4, CommitteeID: 1}},
|
||||||
|
[]types.Vote{},
|
||||||
|
),
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
// Setup (note: suite.SetupTest is not run before every suite.Run)
|
||||||
|
suite.app = app.NewTestApp()
|
||||||
|
suite.keeper = suite.app.GetCommitteeKeeper()
|
||||||
|
suite.ctx = suite.app.NewContext(true, tmproto.Header{})
|
||||||
|
|
||||||
|
// Run
|
||||||
|
var exportedGenState *types.GenesisState
|
||||||
|
run := func() {
|
||||||
|
committee.InitGenesis(suite.ctx, suite.keeper, tc.genState)
|
||||||
|
exportedGenState = committee.ExportGenesis(suite.ctx, suite.keeper)
|
||||||
|
}
|
||||||
|
if tc.expectPass {
|
||||||
|
suite.Require().NotPanics(run)
|
||||||
|
} else {
|
||||||
|
suite.Require().Panics(run)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check
|
||||||
|
if tc.expectPass {
|
||||||
|
expectedJson, err := suite.app.AppCodec().MarshalJSON(tc.genState)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
actualJson, err := suite.app.AppCodec().MarshalJSON(exportedGenState)
|
||||||
|
suite.Equal(expectedJson, actualJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(GenesisTestSuite))
|
||||||
|
}
|
258
x/committee/keeper/_param_permission_test.go
Normal file
258
x/committee/keeper/_param_permission_test.go
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PermissionTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
cdc codec.Codec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PermissionTestSuite) SetupTest() {
|
||||||
|
app := app.NewTestApp()
|
||||||
|
suite.cdc = app.AppCodec()
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (suite *PermissionTestSuite) TestSubParamChangePermission_Allows() {
|
||||||
|
// // cdp CollateralParams
|
||||||
|
// testCPs := cdptypes.CollateralParams{
|
||||||
|
// {
|
||||||
|
// Denom: "bnb",
|
||||||
|
// Type: "bnb-a",
|
||||||
|
// LiquidationRatio: d("2.0"),
|
||||||
|
// DebtLimit: c("usdx", 1000000000000),
|
||||||
|
// StabilityFee: d("1.000000001547125958"),
|
||||||
|
// LiquidationPenalty: d("0.05"),
|
||||||
|
// AuctionSize: i(100),
|
||||||
|
// Prefix: 0x20,
|
||||||
|
// ConversionFactor: i(6),
|
||||||
|
// SpotMarketID: "bnb:usd",
|
||||||
|
// LiquidationMarketID: "bnb:usd",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Denom: "btc",
|
||||||
|
// Type: "btc-a",
|
||||||
|
// LiquidationRatio: d("1.5"),
|
||||||
|
// DebtLimit: c("usdx", 1000000000),
|
||||||
|
// StabilityFee: d("1.000000001547125958"),
|
||||||
|
// LiquidationPenalty: d("0.10"),
|
||||||
|
// AuctionSize: i(1000),
|
||||||
|
// Prefix: 0x30,
|
||||||
|
// ConversionFactor: i(8),
|
||||||
|
// SpotMarketID: "btc:usd",
|
||||||
|
// LiquidationMarketID: "btc:usd",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// testCPUpdatedDebtLimit := make(cdptypes.CollateralParams, len(testCPs))
|
||||||
|
// copy(testCPUpdatedDebtLimit, testCPs)
|
||||||
|
// testCPUpdatedDebtLimit[0].DebtLimit = c("usdx", 5000000)
|
||||||
|
|
||||||
|
// // cdp DebtParam
|
||||||
|
// testDP := cdptypes.DebtParam{
|
||||||
|
// Denom: "usdx",
|
||||||
|
// ReferenceAsset: "usd",
|
||||||
|
// ConversionFactor: i(6),
|
||||||
|
// DebtFloor: i(10000000),
|
||||||
|
// }
|
||||||
|
// testDPUpdatedDebtFloor := testDP
|
||||||
|
// testDPUpdatedDebtFloor.DebtFloor = i(1000)
|
||||||
|
|
||||||
|
// // cdp Genesis
|
||||||
|
// testCDPParams := cdptypes.DefaultParams()
|
||||||
|
// testCDPParams.CollateralParams = testCPs
|
||||||
|
// testCDPParams.DebtParam = testDP
|
||||||
|
// testCDPParams.GlobalDebtLimit = testCPs[0].DebtLimit.Add(testCPs[0].DebtLimit) // correct global debt limit to pass genesis validation
|
||||||
|
|
||||||
|
// testDeputy, err := sdk.AccAddressFromBech32("0g1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj")
|
||||||
|
// suite.Require().NoError(err)
|
||||||
|
// // bep3 Asset Params
|
||||||
|
// testAPs := bep3types.AssetParams{
|
||||||
|
// bep3types.AssetParam{
|
||||||
|
// Denom: "bnb",
|
||||||
|
// CoinID: 714,
|
||||||
|
// SupplyLimit: bep3types.SupplyLimit{
|
||||||
|
// Limit: sdkmath.NewInt(350000000000000),
|
||||||
|
// TimeLimited: false,
|
||||||
|
// TimeBasedLimit: sdk.ZeroInt(),
|
||||||
|
// TimePeriod: time.Hour,
|
||||||
|
// },
|
||||||
|
// Active: true,
|
||||||
|
// DeputyAddress: testDeputy,
|
||||||
|
// FixedFee: sdkmath.NewInt(1000),
|
||||||
|
// MinSwapAmount: sdk.OneInt(),
|
||||||
|
// MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
// MinBlockLock: bep3types.DefaultMinBlockLock,
|
||||||
|
// MaxBlockLock: bep3types.DefaultMaxBlockLock,
|
||||||
|
// },
|
||||||
|
// bep3types.AssetParam{
|
||||||
|
// Denom: "inc",
|
||||||
|
// CoinID: 9999,
|
||||||
|
// SupplyLimit: bep3types.SupplyLimit{
|
||||||
|
// Limit: sdkmath.NewInt(100000000000000),
|
||||||
|
// TimeLimited: true,
|
||||||
|
// TimeBasedLimit: sdkmath.NewInt(50000000000),
|
||||||
|
// TimePeriod: time.Hour,
|
||||||
|
// },
|
||||||
|
// Active: false,
|
||||||
|
// DeputyAddress: testDeputy,
|
||||||
|
// FixedFee: sdkmath.NewInt(1000),
|
||||||
|
// MinSwapAmount: sdk.OneInt(),
|
||||||
|
// MaxSwapAmount: sdkmath.NewInt(1000000000000),
|
||||||
|
// MinBlockLock: bep3types.DefaultMinBlockLock,
|
||||||
|
// MaxBlockLock: bep3types.DefaultMaxBlockLock,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// testAPsUpdatedActive := make(bep3types.AssetParams, len(testAPs))
|
||||||
|
// copy(testAPsUpdatedActive, testAPs)
|
||||||
|
// testAPsUpdatedActive[1].Active = true
|
||||||
|
|
||||||
|
// // bep3 Genesis
|
||||||
|
// testBep3Params := bep3types.DefaultParams()
|
||||||
|
// testBep3Params.AssetParams = testAPs
|
||||||
|
|
||||||
|
// // pricefeed Markets
|
||||||
|
// testMs := pricefeedtypes.Markets{
|
||||||
|
// {
|
||||||
|
// MarketID: "bnb:usd",
|
||||||
|
// BaseAsset: "bnb",
|
||||||
|
// QuoteAsset: "usd",
|
||||||
|
// Oracles: []sdk.AccAddress{},
|
||||||
|
// Active: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// MarketID: "btc:usd",
|
||||||
|
// BaseAsset: "btc",
|
||||||
|
// QuoteAsset: "usd",
|
||||||
|
// Oracles: []sdk.AccAddress{},
|
||||||
|
// Active: true,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// testMsUpdatedActive := make(pricefeedtypes.Markets, len(testMs))
|
||||||
|
// copy(testMsUpdatedActive, testMs)
|
||||||
|
// testMsUpdatedActive[1].Active = true
|
||||||
|
|
||||||
|
// testcases := []struct {
|
||||||
|
// name string
|
||||||
|
// genState []app.GenesisState
|
||||||
|
// permission types.SubParamChangePermission
|
||||||
|
// pubProposal types.PubProposal
|
||||||
|
// expectAllowed bool
|
||||||
|
// }{
|
||||||
|
// {
|
||||||
|
// name: "normal",
|
||||||
|
// genState: []app.GenesisState{
|
||||||
|
// newPricefeedGenState([]string{"bnb", "btc"}, []sdk.Dec{d("15.01"), d("9500")}),
|
||||||
|
// newCDPGenesisState(testCDPParams),
|
||||||
|
// newBep3GenesisState(testBep3Params),
|
||||||
|
// },
|
||||||
|
// permission: types.SubParamChangePermission{
|
||||||
|
// AllowedParams: types.AllowedParams{
|
||||||
|
// {Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyDebtThreshold)},
|
||||||
|
// {Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyCollateralParams)},
|
||||||
|
// {Subspace: cdptypes.ModuleName, Key: string(cdptypes.KeyDebtParam)},
|
||||||
|
// {Subspace: bep3types.ModuleName, Key: string(bep3types.KeyAssetParams)},
|
||||||
|
// {Subspace: pricefeedtypes.ModuleName, Key: string(pricefeedtypes.KeyMarkets)},
|
||||||
|
// },
|
||||||
|
// AllowedCollateralParams: types.AllowedCollateralParams{
|
||||||
|
// {
|
||||||
|
// Type: "bnb-a",
|
||||||
|
// DebtLimit: true,
|
||||||
|
// StabilityFee: true,
|
||||||
|
// },
|
||||||
|
// { // TODO currently even if a perm doesn't allow a change in one element it must still be present in list
|
||||||
|
// Type: "btc-a",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// AllowedDebtParam: types.AllowedDebtParam{
|
||||||
|
// DebtFloor: true,
|
||||||
|
// },
|
||||||
|
// AllowedAssetParams: types.AllowedAssetParams{
|
||||||
|
// {
|
||||||
|
// Denom: "bnb",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Denom: "inc",
|
||||||
|
// Active: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// AllowedMarkets: types.AllowedMarkets{
|
||||||
|
// {
|
||||||
|
// MarketID: "bnb:usd",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// MarketID: "btc:usd",
|
||||||
|
// Active: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// pubProposal: paramstypes.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description for this proposal.",
|
||||||
|
// []paramstypes.ParamChange{
|
||||||
|
// {
|
||||||
|
// Subspace: cdptypes.ModuleName,
|
||||||
|
// Key: string(cdptypes.KeyDebtThreshold),
|
||||||
|
// Value: string(suite.cdc.MustMarshalJSON(i(1234))),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Subspace: cdptypes.ModuleName,
|
||||||
|
// Key: string(cdptypes.KeyCollateralParams),
|
||||||
|
// Value: string(suite.cdc.MustMarshalJSON(testCPUpdatedDebtLimit)),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Subspace: cdptypes.ModuleName,
|
||||||
|
// Key: string(cdptypes.KeyDebtParam),
|
||||||
|
// Value: string(suite.cdc.MustMarshalJSON(testDPUpdatedDebtFloor)),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Subspace: bep3types.ModuleName,
|
||||||
|
// Key: string(bep3types.KeyAssetParams),
|
||||||
|
// Value: string(suite.cdc.MustMarshalJSON(testAPsUpdatedActive)),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Subspace: pricefeedtypes.ModuleName,
|
||||||
|
// Key: string(pricefeedtypes.KeyMarkets),
|
||||||
|
// Value: string(suite.cdc.MustMarshalJSON(testMsUpdatedActive)),
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// expectAllowed: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "not allowed (wrong pubproposal type)",
|
||||||
|
// permission: types.SubParamChangePermission{},
|
||||||
|
// pubProposal: govtypes.NewTextProposal("A Title", "A description for this proposal."),
|
||||||
|
// expectAllowed: false,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "not allowed (nil pubproposal)",
|
||||||
|
// permission: types.SubParamChangePermission{},
|
||||||
|
// pubProposal: nil,
|
||||||
|
// expectAllowed: false,
|
||||||
|
// },
|
||||||
|
// // TODO more cases
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, tc := range testcases {
|
||||||
|
// suite.Run(tc.name, func() {
|
||||||
|
// tApp := app.NewTestApp()
|
||||||
|
// ctx := tApp.NewContext(true, abci.Header{})
|
||||||
|
// tApp.InitializeFromGenesisStates(tc.genState...)
|
||||||
|
|
||||||
|
// suite.Equal(
|
||||||
|
// tc.expectAllowed,
|
||||||
|
// tc.permission.Allows(ctx, tApp.Codec(), tApp.GetParamsKeeper(), tc.pubProposal),
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestPermissionTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(PermissionTestSuite))
|
||||||
|
}
|
189
x/committee/keeper/committee_test.go
Normal file
189
x/committee/keeper/committee_test.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "time"
|
||||||
|
|
||||||
|
// "github.com/stretchr/testify/suite"
|
||||||
|
// abci "github.com/cometbft/cometbft/abci/types"
|
||||||
|
|
||||||
|
// govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||||
|
// paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||||
|
|
||||||
|
// "github.com/0glabs/0g-chain/app"
|
||||||
|
// "github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// type TypesTestSuite struct {
|
||||||
|
// suite.Suite
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (suite *TypesTestSuite) TestCommittee_HasPermissionsFor() {
|
||||||
|
|
||||||
|
// testcases := []struct {
|
||||||
|
// name string
|
||||||
|
// permissions []types.Permission
|
||||||
|
// pubProposal types.PubProposal
|
||||||
|
// expectHasPermissions bool
|
||||||
|
// }{
|
||||||
|
// {
|
||||||
|
// name: "normal (single permission)",
|
||||||
|
// permissions: []types.Permission{types.SimpleParamChangePermission{
|
||||||
|
// AllowedParams: types.AllowedParams{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtThreshold",
|
||||||
|
// },
|
||||||
|
// }}},
|
||||||
|
// pubProposal: paramstypes.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description of this proposal.",
|
||||||
|
// []paramstypes.ParamChange{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtThreshold",
|
||||||
|
|
||||||
|
// Value: `{"denom": "usdx", "amount": "1000000"}`,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// expectHasPermissions: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "normal (multiple permissions)",
|
||||||
|
// permissions: []types.Permission{
|
||||||
|
// types.SimpleParamChangePermission{
|
||||||
|
// AllowedParams: types.AllowedParams{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtThreshold",
|
||||||
|
// },
|
||||||
|
// }},
|
||||||
|
// types.TextPermission{},
|
||||||
|
// },
|
||||||
|
// pubProposal: govtypes.NewTextProposal("A Proposal Title", "A description of this proposal"),
|
||||||
|
// expectHasPermissions: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "overruling permission",
|
||||||
|
// permissions: []types.Permission{
|
||||||
|
// types.SimpleParamChangePermission{
|
||||||
|
// AllowedParams: types.AllowedParams{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtThreshold",
|
||||||
|
// },
|
||||||
|
// }},
|
||||||
|
// types.GodPermission{},
|
||||||
|
// },
|
||||||
|
// pubProposal: paramstypes.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description of this proposal.",
|
||||||
|
// []paramstypes.ParamChange{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "CollateralParams",
|
||||||
|
|
||||||
|
// Value: `[]`,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// expectHasPermissions: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "no permissions",
|
||||||
|
// permissions: nil,
|
||||||
|
// pubProposal: paramstypes.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description of this proposal.",
|
||||||
|
// []paramstypes.ParamChange{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "CollateralParams",
|
||||||
|
|
||||||
|
// Value: `[]`,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// expectHasPermissions: false,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "split permissions",
|
||||||
|
// // These permissions looks like they allow the param change proposal, however a proposal must pass a single permission independently of others.
|
||||||
|
// permissions: []types.Permission{
|
||||||
|
// types.SimpleParamChangePermission{
|
||||||
|
// AllowedParams: types.AllowedParams{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtThreshold",
|
||||||
|
// },
|
||||||
|
// }},
|
||||||
|
// types.SimpleParamChangePermission{
|
||||||
|
// AllowedParams: types.AllowedParams{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtParams",
|
||||||
|
// },
|
||||||
|
// }},
|
||||||
|
// },
|
||||||
|
// pubProposal: paramstypes.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description of this proposal.",
|
||||||
|
// []paramstypes.ParamChange{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtThreshold",
|
||||||
|
|
||||||
|
// Value: `{"denom": "usdx", "amount": "1000000"}`,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtParams",
|
||||||
|
|
||||||
|
// Value: `[]`,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// expectHasPermissions: false,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "unregistered proposal",
|
||||||
|
// permissions: []types.Permission{
|
||||||
|
// types.SimpleParamChangePermission{
|
||||||
|
// AllowedParams: types.AllowedParams{
|
||||||
|
// {
|
||||||
|
// Subspace: "cdp",
|
||||||
|
// Key: "DebtThreshold",
|
||||||
|
// },
|
||||||
|
// }},
|
||||||
|
// },
|
||||||
|
// pubProposal: UnregisteredPubProposal{govtypes.TextProposal{Title: "A Title", Description: "A description."}},
|
||||||
|
// expectHasPermissions: false,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, tc := range testcases {
|
||||||
|
// suite.Run(tc.name, func() {
|
||||||
|
// tApp := app.NewTestApp()
|
||||||
|
// ctx := tApp.NewContext(true, abci.Header{})
|
||||||
|
// tApp.InitializeFromGenesisStates()
|
||||||
|
// com := types.NewMemberCommittee(
|
||||||
|
// 12,
|
||||||
|
// "a description of this committee",
|
||||||
|
// nil,
|
||||||
|
// tc.permissions,
|
||||||
|
// d("0.5"),
|
||||||
|
// 24*time.Hour,
|
||||||
|
// types.FirstPastThePost,
|
||||||
|
// )
|
||||||
|
// suite.Equal(
|
||||||
|
// tc.expectHasPermissions,
|
||||||
|
// com.HasPermissionsFor(ctx, tApp.Codec(), tApp.GetParamsKeeper(), tc.pubProposal),
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func TestTypesTestSuite(t *testing.T) {
|
||||||
|
// suite.Run(t, new(TypesTestSuite))
|
||||||
|
// }
|
53
x/committee/keeper/gprc_query_test.go
Normal file
53
x/committee/keeper/gprc_query_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/testutil"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type grpcQueryTestSuite struct {
|
||||||
|
testutil.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *grpcQueryTestSuite) SetupTest() {
|
||||||
|
suite.Suite.SetupTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *grpcQueryTestSuite) TestVote() {
|
||||||
|
ctx, keeper, queryClient := suite.Ctx, suite.Keeper, suite.QueryClient
|
||||||
|
vote := types.Vote{
|
||||||
|
ProposalID: 1,
|
||||||
|
Voter: suite.Addresses[0],
|
||||||
|
VoteType: types.VOTE_TYPE_ABSTAIN,
|
||||||
|
}
|
||||||
|
keeper.SetVote(ctx, vote)
|
||||||
|
|
||||||
|
req := types.QueryVoteRequest{
|
||||||
|
ProposalId: vote.ProposalID,
|
||||||
|
Voter: vote.Voter.String(),
|
||||||
|
}
|
||||||
|
res, err := queryClient.Vote(context.Background(), &req)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().Equal(vote.ProposalID, res.ProposalID)
|
||||||
|
suite.Require().Equal(vote.VoteType, res.VoteType)
|
||||||
|
suite.Require().Equal(vote.Voter.String(), res.Voter)
|
||||||
|
|
||||||
|
queryRes, err := queryClient.Votes(context.Background(), &types.QueryVotesRequest{
|
||||||
|
ProposalId: vote.ProposalID,
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().Len(queryRes.Votes, 1)
|
||||||
|
suite.Require().Equal(vote.ProposalID, queryRes.Votes[0].ProposalID)
|
||||||
|
suite.Require().Equal(vote.VoteType, queryRes.Votes[0].VoteType)
|
||||||
|
suite.Require().Equal(vote.Voter.String(), queryRes.Votes[0].Voter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGrpcQueryTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(grpcQueryTestSuite))
|
||||||
|
}
|
204
x/committee/keeper/grpc_query.go
Normal file
204
x/committee/keeper/grpc_query.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type queryServer struct {
|
||||||
|
keeper Keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueryServerImpl creates a new server for handling gRPC queries.
|
||||||
|
func NewQueryServerImpl(k Keeper) types.QueryServer {
|
||||||
|
return &queryServer{keeper: k}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.QueryServer = queryServer{}
|
||||||
|
|
||||||
|
// Committees implements the gRPC service handler for querying committees.
|
||||||
|
func (s queryServer) Committees(ctx context.Context, req *types.QueryCommitteesRequest) (*types.QueryCommitteesResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
|
committees := s.keeper.GetCommittees(sdkCtx)
|
||||||
|
committeesAny, err := types.PackCommittees(committees)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, "could not pack committees: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.QueryCommitteesResponse{Committees: committeesAny}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Committee implements the Query/Committee gRPC method.
|
||||||
|
func (s queryServer) Committee(c context.Context, req *types.QueryCommitteeRequest) (*types.QueryCommitteeResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
committee, found := s.keeper.GetCommittee(ctx, req.CommitteeId)
|
||||||
|
if !found {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "could not find committee for id: %v", req.CommitteeId)
|
||||||
|
}
|
||||||
|
committeeAny, err := types.PackCommittee(committee)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "could not pack committees: %v", err)
|
||||||
|
}
|
||||||
|
return &types.QueryCommitteeResponse{Committee: committeeAny}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proposals implements the Query/Proposals gRPC method
|
||||||
|
func (s queryServer) Proposals(c context.Context, req *types.QueryProposalsRequest) (*types.QueryProposalsResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
proposals := s.keeper.GetProposalsByCommittee(ctx, req.CommitteeId)
|
||||||
|
var proposalsResp []types.QueryProposalResponse
|
||||||
|
|
||||||
|
for _, proposal := range proposals {
|
||||||
|
proposalsResp = append(proposalsResp, s.proposalResponseFromProposal(proposal))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.QueryProposalsResponse{
|
||||||
|
Proposals: proposalsResp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proposal implements the Query/Proposal gRPC method
|
||||||
|
func (s queryServer) Proposal(c context.Context, req *types.QueryProposalRequest) (*types.QueryProposalResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
proposal, found := s.keeper.GetProposal(ctx, req.ProposalId)
|
||||||
|
if !found {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "cannot find proposal: %v", req.ProposalId)
|
||||||
|
}
|
||||||
|
proposalResp := s.proposalResponseFromProposal(proposal)
|
||||||
|
return &proposalResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextProposalID implements the Query/NextProposalID gRPC method
|
||||||
|
func (s queryServer) NextProposalID(c context.Context, req *types.QueryNextProposalIDRequest) (*types.QueryNextProposalIDResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
proposalID, err := s.keeper.GetNextProposalID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "cannot find next proposal id: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.QueryNextProposalIDResponse{NextProposalID: proposalID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Votes implements the Query/Votes gRPC method
|
||||||
|
func (s queryServer) Votes(c context.Context, req *types.QueryVotesRequest) (*types.QueryVotesResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
|
var queryResults []types.QueryVoteResponse
|
||||||
|
store := ctx.KVStore(s.keeper.storeKey)
|
||||||
|
votesStore := prefix.NewStore(store, append(types.VoteKeyPrefix, types.GetKeyFromID(req.ProposalId)...))
|
||||||
|
pageRes, err := query.Paginate(votesStore, req.Pagination, func(key []byte, value []byte) error {
|
||||||
|
var vote types.Vote
|
||||||
|
if err := s.keeper.cdc.Unmarshal(value, &vote); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
queryResults = append(queryResults, s.votesResponseFromVote(vote))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.QueryVotesResponse{
|
||||||
|
Votes: queryResults,
|
||||||
|
Pagination: pageRes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vote implements the Query/Vote gRPC method
|
||||||
|
func (s queryServer) Vote(c context.Context, req *types.QueryVoteRequest) (*types.QueryVoteResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
|
voter, err := sdk.AccAddressFromBech32(req.Voter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid voter address: %v", err)
|
||||||
|
}
|
||||||
|
vote, found := s.keeper.GetVote(ctx, req.ProposalId, voter)
|
||||||
|
if !found {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "proposal id: %d, voter: %s", req.ProposalId, req.Voter)
|
||||||
|
}
|
||||||
|
voteResp := s.votesResponseFromVote(vote)
|
||||||
|
return &voteResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tally implements the Query/Tally gRPC method
|
||||||
|
func (s queryServer) Tally(c context.Context, req *types.QueryTallyRequest) (*types.QueryTallyResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
tally, found := s.keeper.GetProposalTallyResponse(ctx, req.ProposalId)
|
||||||
|
if !found {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "proposal id: %d", req.ProposalId)
|
||||||
|
}
|
||||||
|
return tally, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawParams implements the Query/RawParams gRPC method
|
||||||
|
func (s queryServer) RawParams(c context.Context, req *types.QueryRawParamsRequest) (*types.QueryRawParamsResponse, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
subspace, found := s.keeper.paramKeeper.GetSubspace(req.Subspace)
|
||||||
|
if !found {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "subspace not found: %s", req.Subspace)
|
||||||
|
}
|
||||||
|
rawParams := subspace.GetRaw(ctx, []byte(req.Key))
|
||||||
|
return &types.QueryRawParamsResponse{RawData: string(rawParams)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s queryServer) proposalResponseFromProposal(proposal types.Proposal) types.QueryProposalResponse {
|
||||||
|
return types.QueryProposalResponse{
|
||||||
|
PubProposal: proposal.Content,
|
||||||
|
ID: proposal.ID,
|
||||||
|
CommitteeID: proposal.CommitteeID,
|
||||||
|
Deadline: proposal.Deadline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s queryServer) votesResponseFromVote(vote types.Vote) types.QueryVoteResponse {
|
||||||
|
return types.QueryVoteResponse{
|
||||||
|
ProposalID: vote.ProposalID,
|
||||||
|
Voter: vote.Voter.String(),
|
||||||
|
VoteType: vote.VoteType,
|
||||||
|
}
|
||||||
|
}
|
64
x/committee/keeper/integration_test.go
Normal file
64
x/committee/keeper/integration_test.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/testutil"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getProposalVoteMap collects up votes into a map indexed by proposalID
|
||||||
|
func getProposalVoteMap(k keeper.Keeper, ctx sdk.Context) map[uint64]([]types.Vote) {
|
||||||
|
proposalVoteMap := map[uint64]([]types.Vote){}
|
||||||
|
|
||||||
|
k.IterateProposals(ctx, func(p types.Proposal) bool {
|
||||||
|
proposalVoteMap[p.ID] = k.GetVotesByProposal(ctx, p.ID)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return proposalVoteMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) getAccount(addr sdk.AccAddress) authtypes.AccountI {
|
||||||
|
ak := suite.App.GetAccountKeeper()
|
||||||
|
return ak.GetAccount(suite.Ctx, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustNewTestMemberCommittee(addresses []sdk.AccAddress) *types.MemberCommittee {
|
||||||
|
com, err := types.NewMemberCommittee(
|
||||||
|
12,
|
||||||
|
"This committee is for testing.",
|
||||||
|
addresses,
|
||||||
|
[]types.Permission{&types.GodPermission{}},
|
||||||
|
testutil.D("0.667"),
|
||||||
|
time.Hour*24*7,
|
||||||
|
types.TALLY_OPTION_FIRST_PAST_THE_POST,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return com
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustNewTestProposal returns a new test proposal.
|
||||||
|
func mustNewTestProposal() types.Proposal {
|
||||||
|
proposal, err := types.NewProposal(
|
||||||
|
govv1beta1.NewTextProposal("A Title", "A description of this proposal."),
|
||||||
|
1, 1, time.Date(2010, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return proposal
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommitteeGenesisState marshals a committee genesis state into json for use in initializing test apps.
|
||||||
|
func NewCommitteeGenesisState(cdc codec.Codec, gs *types.GenesisState) app.GenesisState {
|
||||||
|
return app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(gs)}
|
||||||
|
}
|
301
x/committee/keeper/keeper.go
Normal file
301
x/committee/keeper/keeper.go
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Keeper struct {
|
||||||
|
cdc codec.Codec
|
||||||
|
storeKey storetypes.StoreKey
|
||||||
|
|
||||||
|
paramKeeper types.ParamKeeper
|
||||||
|
accountKeeper types.AccountKeeper
|
||||||
|
bankKeeper types.BankKeeper
|
||||||
|
|
||||||
|
// Proposal router
|
||||||
|
router govv1beta1.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, router govv1beta1.Router,
|
||||||
|
paramKeeper types.ParamKeeper, ak types.AccountKeeper, sk types.BankKeeper,
|
||||||
|
) Keeper {
|
||||||
|
// Logic in the keeper methods assume the set of gov handlers is fixed.
|
||||||
|
// So the gov router must be sealed so no handlers can be added or removed after the keeper is created.
|
||||||
|
router.Seal()
|
||||||
|
|
||||||
|
return Keeper{
|
||||||
|
cdc: cdc,
|
||||||
|
storeKey: storeKey,
|
||||||
|
paramKeeper: paramKeeper,
|
||||||
|
accountKeeper: ak,
|
||||||
|
bankKeeper: sk,
|
||||||
|
router: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Committees
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// GetCommittee gets a committee from the store.
|
||||||
|
func (k Keeper) GetCommittee(ctx sdk.Context, committeeID uint64) (types.Committee, bool) {
|
||||||
|
var committee types.Committee
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.CommitteeKeyPrefix)
|
||||||
|
bz := store.Get(types.GetKeyFromID(committeeID))
|
||||||
|
if bz == nil {
|
||||||
|
return committee, false
|
||||||
|
}
|
||||||
|
err := k.cdc.UnmarshalInterface(bz, &committee)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return committee, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCommittee puts a committee into the store.
|
||||||
|
func (k Keeper) SetCommittee(ctx sdk.Context, committee types.Committee) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.CommitteeKeyPrefix)
|
||||||
|
bz, err := k.cdc.MarshalInterface(committee)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
store.Set(types.GetKeyFromID(committee.GetID()), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCommittee removes a committee from the store.
|
||||||
|
func (k Keeper) DeleteCommittee(ctx sdk.Context, committeeID uint64) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.CommitteeKeyPrefix)
|
||||||
|
store.Delete(types.GetKeyFromID(committeeID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateCommittees provides an iterator over all stored committees.
|
||||||
|
// For each committee, cb will be called. If cb returns true, the iterator will close and stop.
|
||||||
|
func (k Keeper) IterateCommittees(ctx sdk.Context, cb func(committee types.Committee) (stop bool)) {
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.CommitteeKeyPrefix)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var committee types.Committee
|
||||||
|
if err := k.cdc.UnmarshalInterface(iterator.Value(), &committee); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if cb(committee) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommittees returns all stored committees.
|
||||||
|
func (k Keeper) GetCommittees(ctx sdk.Context) types.Committees {
|
||||||
|
results := types.Committees{}
|
||||||
|
k.IterateCommittees(ctx, func(com types.Committee) bool {
|
||||||
|
results = append(results, com)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Proposals
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// SetNextProposalID stores an ID to be used for the next created proposal
|
||||||
|
func (k Keeper) SetNextProposalID(ctx sdk.Context, id uint64) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
store.Set(types.NextProposalIDKey, types.GetKeyFromID(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextProposalID reads the next available global ID from store
|
||||||
|
func (k Keeper) GetNextProposalID(ctx sdk.Context) (uint64, error) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
bz := store.Get(types.NextProposalIDKey)
|
||||||
|
if bz == nil {
|
||||||
|
return 0, errorsmod.Wrap(types.ErrInvalidGenesis, "next proposal ID not set at genesis")
|
||||||
|
}
|
||||||
|
return types.Uint64FromBytes(bz), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementNextProposalID increments the next proposal ID in the store by 1.
|
||||||
|
func (k Keeper) IncrementNextProposalID(ctx sdk.Context) error {
|
||||||
|
id, err := k.GetNextProposalID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
k.SetNextProposalID(ctx, id+1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreNewProposal stores a proposal, adding a new ID
|
||||||
|
func (k Keeper) StoreNewProposal(ctx sdk.Context, pubProposal types.PubProposal, committeeID uint64, deadline time.Time) (uint64, error) {
|
||||||
|
newProposalID, err := k.GetNextProposalID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
proposal, err := types.NewProposal(
|
||||||
|
pubProposal,
|
||||||
|
newProposalID,
|
||||||
|
committeeID,
|
||||||
|
deadline,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k.SetProposal(ctx, proposal)
|
||||||
|
|
||||||
|
err = k.IncrementNextProposalID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return newProposalID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProposal gets a proposal from the store.
|
||||||
|
func (k Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (types.Proposal, bool) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ProposalKeyPrefix)
|
||||||
|
bz := store.Get(types.GetKeyFromID(proposalID))
|
||||||
|
if bz == nil {
|
||||||
|
return types.Proposal{}, false
|
||||||
|
}
|
||||||
|
var proposal types.Proposal
|
||||||
|
k.cdc.MustUnmarshal(bz, &proposal)
|
||||||
|
return proposal, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProposal puts a proposal into the store.
|
||||||
|
func (k Keeper) SetProposal(ctx sdk.Context, proposal types.Proposal) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ProposalKeyPrefix)
|
||||||
|
bz := k.cdc.MustMarshal(&proposal)
|
||||||
|
store.Set(types.GetKeyFromID(proposal.ID), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteProposal removes a proposal from the store.
|
||||||
|
func (k Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ProposalKeyPrefix)
|
||||||
|
store.Delete(types.GetKeyFromID(proposalID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateProposals provides an iterator over all stored proposals.
|
||||||
|
// For each proposal, cb will be called. If cb returns true, the iterator will close and stop.
|
||||||
|
func (k Keeper) IterateProposals(ctx sdk.Context, cb func(proposal types.Proposal) (stop bool)) {
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.ProposalKeyPrefix)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var proposal types.Proposal
|
||||||
|
k.cdc.MustUnmarshal(iterator.Value(), &proposal)
|
||||||
|
if cb(proposal) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProposals returns all stored proposals.
|
||||||
|
func (k Keeper) GetProposals(ctx sdk.Context) types.Proposals {
|
||||||
|
results := types.Proposals{}
|
||||||
|
k.IterateProposals(ctx, func(prop types.Proposal) bool {
|
||||||
|
results = append(results, prop)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProposalsByCommittee returns all proposals for one committee.
|
||||||
|
func (k Keeper) GetProposalsByCommittee(ctx sdk.Context, committeeID uint64) types.Proposals {
|
||||||
|
results := types.Proposals{}
|
||||||
|
k.IterateProposals(ctx, func(prop types.Proposal) bool {
|
||||||
|
if prop.CommitteeID == committeeID {
|
||||||
|
results = append(results, prop)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteProposalAndVotes removes a proposal and its associated votes.
|
||||||
|
func (k Keeper) DeleteProposalAndVotes(ctx sdk.Context, proposalID uint64) {
|
||||||
|
votes := k.GetVotesByProposal(ctx, proposalID)
|
||||||
|
k.DeleteProposal(ctx, proposalID)
|
||||||
|
for _, v := range votes {
|
||||||
|
k.DeleteVote(ctx, v.ProposalID, v.Voter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// Votes
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
// GetVote gets a vote from the store.
|
||||||
|
func (k Keeper) GetVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress) (types.Vote, bool) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.VoteKeyPrefix)
|
||||||
|
bz := store.Get(types.GetVoteKey(proposalID, voter))
|
||||||
|
if bz == nil {
|
||||||
|
return types.Vote{}, false
|
||||||
|
}
|
||||||
|
var vote types.Vote
|
||||||
|
k.cdc.MustUnmarshal(bz, &vote)
|
||||||
|
return vote, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVote puts a vote into the store.
|
||||||
|
func (k Keeper) SetVote(ctx sdk.Context, vote types.Vote) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.VoteKeyPrefix)
|
||||||
|
bz := k.cdc.MustMarshal(&vote)
|
||||||
|
store.Set(types.GetVoteKey(vote.ProposalID, vote.Voter), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVote removes a Vote from the store.
|
||||||
|
func (k Keeper) DeleteVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.VoteKeyPrefix)
|
||||||
|
store.Delete(types.GetVoteKey(proposalID, voter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateVotes provides an iterator over all stored votes.
|
||||||
|
// For each vote, cb will be called. If cb returns true, the iterator will close and stop.
|
||||||
|
func (k Keeper) IterateVotes(ctx sdk.Context, cb func(vote types.Vote) (stop bool)) {
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.VoteKeyPrefix)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var vote types.Vote
|
||||||
|
k.cdc.MustUnmarshal(iterator.Value(), &vote)
|
||||||
|
|
||||||
|
if cb(vote) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVotes returns all stored votes.
|
||||||
|
func (k Keeper) GetVotes(ctx sdk.Context) []types.Vote {
|
||||||
|
results := []types.Vote{}
|
||||||
|
k.IterateVotes(ctx, func(vote types.Vote) bool {
|
||||||
|
results = append(results, vote)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVotesByProposal returns all votes for one proposal.
|
||||||
|
func (k Keeper) GetVotesByProposal(ctx sdk.Context, proposalID uint64) []types.Vote {
|
||||||
|
results := []types.Vote{}
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), append(types.VoteKeyPrefix, types.GetKeyFromID(proposalID)...))
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var vote types.Vote
|
||||||
|
k.cdc.MustUnmarshal(iterator.Value(), &vote)
|
||||||
|
results = append(results, vote)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
171
x/committee/keeper/keeper_test.go
Normal file
171
x/committee/keeper/keeper_test.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/testutil"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type keeperTestSuite struct {
|
||||||
|
testutil.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) SetupTest() {
|
||||||
|
suite.Suite.SetupTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestGetSetDeleteCommittee() {
|
||||||
|
cdc := suite.App.AppCodec()
|
||||||
|
|
||||||
|
// setup test
|
||||||
|
com := mustNewTestMemberCommittee(suite.Addresses)
|
||||||
|
|
||||||
|
// write and read from store
|
||||||
|
suite.Keeper.SetCommittee(suite.Ctx, com)
|
||||||
|
readCommittee, found := suite.Keeper.GetCommittee(suite.Ctx, com.ID)
|
||||||
|
|
||||||
|
// check before and after match
|
||||||
|
suite.Require().True(found)
|
||||||
|
expectedJson, err := cdc.MarshalJSON(com)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
actualJson, err := cdc.MarshalJSON(readCommittee)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Equal(expectedJson, actualJson)
|
||||||
|
suite.Require().Equal(com.GetPermissions(), readCommittee.GetPermissions())
|
||||||
|
|
||||||
|
// delete from store
|
||||||
|
suite.Keeper.DeleteCommittee(suite.Ctx, com.ID)
|
||||||
|
|
||||||
|
// check does not exist
|
||||||
|
_, found = suite.Keeper.GetCommittee(suite.Ctx, com.ID)
|
||||||
|
suite.Require().False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestGetSetDeleteProposal() {
|
||||||
|
// test setup
|
||||||
|
prop, err := types.NewProposal(
|
||||||
|
govv1beta1.NewTextProposal("A Title", "A description of this proposal."),
|
||||||
|
12,
|
||||||
|
0,
|
||||||
|
time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// write and read from store
|
||||||
|
suite.Keeper.SetProposal(suite.Ctx, prop)
|
||||||
|
readProposal, found := suite.Keeper.GetProposal(suite.Ctx, prop.ID)
|
||||||
|
|
||||||
|
// check before and after match
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(prop, readProposal)
|
||||||
|
|
||||||
|
// delete from store
|
||||||
|
suite.Keeper.DeleteProposal(suite.Ctx, prop.ID)
|
||||||
|
|
||||||
|
// check does not exist
|
||||||
|
_, found = suite.Keeper.GetProposal(suite.Ctx, prop.ID)
|
||||||
|
suite.False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestGetSetDeleteVote() {
|
||||||
|
// test setup
|
||||||
|
vote := types.Vote{
|
||||||
|
ProposalID: 12,
|
||||||
|
Voter: suite.Addresses[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
// write and read from store
|
||||||
|
suite.Keeper.SetVote(suite.Ctx, vote)
|
||||||
|
readVote, found := suite.Keeper.GetVote(suite.Ctx, vote.ProposalID, vote.Voter)
|
||||||
|
|
||||||
|
// check before and after match
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(vote, readVote)
|
||||||
|
|
||||||
|
// delete from store
|
||||||
|
suite.Keeper.DeleteVote(suite.Ctx, vote.ProposalID, vote.Voter)
|
||||||
|
|
||||||
|
// check does not exist
|
||||||
|
_, found = suite.Keeper.GetVote(suite.Ctx, vote.ProposalID, vote.Voter)
|
||||||
|
suite.False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestGetCommittees() {
|
||||||
|
committeesCount := 10
|
||||||
|
for i := 0; i < committeesCount; i++ {
|
||||||
|
com := mustNewTestMemberCommittee(suite.Addresses)
|
||||||
|
com.ID = uint64(i)
|
||||||
|
suite.Keeper.SetCommittee(suite.Ctx, com)
|
||||||
|
}
|
||||||
|
committees := suite.Keeper.GetCommittees(suite.Ctx)
|
||||||
|
suite.Require().Len(committees, committeesCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestGetAndSetProposal() {
|
||||||
|
proposal := mustNewTestProposal()
|
||||||
|
|
||||||
|
// Get no proposal
|
||||||
|
actualProposal, found := suite.Keeper.GetProposal(suite.Ctx, proposal.ID)
|
||||||
|
suite.Require().False(found)
|
||||||
|
suite.Require().Equal(types.Proposal{}, actualProposal)
|
||||||
|
|
||||||
|
// Set and get new proposal
|
||||||
|
suite.Keeper.SetProposal(suite.Ctx, proposal)
|
||||||
|
actualProposal, found = suite.Keeper.GetProposal(suite.Ctx, proposal.ID)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(proposal, actualProposal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestGetProposalsByCommittee() {
|
||||||
|
committee := mustNewTestMemberCommittee(suite.Addresses)
|
||||||
|
proposalsCount := 4
|
||||||
|
for i := 0; i < proposalsCount; i++ {
|
||||||
|
proposal := mustNewTestProposal()
|
||||||
|
proposal.ID = uint64(i)
|
||||||
|
proposal.CommitteeID = committee.ID
|
||||||
|
suite.Keeper.SetProposal(suite.Ctx, proposal)
|
||||||
|
}
|
||||||
|
proposal := mustNewTestProposal()
|
||||||
|
proposal.ID = uint64(proposalsCount)
|
||||||
|
proposal.CommitteeID = committee.ID + 1
|
||||||
|
suite.Keeper.SetProposal(suite.Ctx, proposal)
|
||||||
|
|
||||||
|
// No proposals
|
||||||
|
actualProposals := suite.Keeper.GetProposalsByCommittee(suite.Ctx, committee.ID+2)
|
||||||
|
suite.Require().Len(actualProposals, 0)
|
||||||
|
|
||||||
|
// Proposals for existing committees
|
||||||
|
actualProposals = suite.Keeper.GetProposalsByCommittee(suite.Ctx, committee.ID)
|
||||||
|
suite.Require().Len(actualProposals, proposalsCount)
|
||||||
|
actualProposals = suite.Keeper.GetProposalsByCommittee(suite.Ctx, committee.ID+1)
|
||||||
|
suite.Require().Len(actualProposals, 1)
|
||||||
|
|
||||||
|
// Make sure proposals have expected data
|
||||||
|
suite.Require().Equal(proposal, actualProposals[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *keeperTestSuite) TestGetVotesByProposal() {
|
||||||
|
proposal := mustNewTestProposal()
|
||||||
|
suite.Keeper.SetProposal(suite.Ctx, proposal)
|
||||||
|
votes := []types.Vote{
|
||||||
|
types.NewVote(proposal.ID, suite.Addresses[0], types.VOTE_TYPE_NO),
|
||||||
|
types.NewVote(proposal.ID, suite.Addresses[1], types.VOTE_TYPE_ABSTAIN),
|
||||||
|
types.NewVote(proposal.ID, suite.Addresses[1], types.VOTE_TYPE_YES),
|
||||||
|
}
|
||||||
|
expectedVotes := []types.Vote{votes[0], votes[2]}
|
||||||
|
for _, vote := range votes {
|
||||||
|
suite.Keeper.SetVote(suite.Ctx, vote)
|
||||||
|
}
|
||||||
|
actualVotes := suite.Keeper.GetVotesByProposal(suite.Ctx, proposal.ID)
|
||||||
|
suite.Require().Len(actualVotes, len(expectedVotes))
|
||||||
|
suite.Require().ElementsMatch(expectedVotes, actualVotes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeeperTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(keeperTestSuite))
|
||||||
|
}
|
70
x/committee/keeper/msg_server.go
Normal file
70
x/committee/keeper/msg_server.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgServer struct {
|
||||||
|
keeper Keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgServerImpl returns an implementation of the committee MsgServer interface
|
||||||
|
// for the provided Keeper.
|
||||||
|
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
|
||||||
|
return &msgServer{keeper: keeper}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.MsgServer = msgServer{}
|
||||||
|
|
||||||
|
// SubmitProposal handles MsgSubmitProposal messages
|
||||||
|
func (m msgServer) SubmitProposal(goCtx context.Context, msg *types.MsgSubmitProposal) (*types.MsgSubmitProposalResponse, error) {
|
||||||
|
ctx := sdk.UnwrapSDKContext(goCtx)
|
||||||
|
|
||||||
|
proposer, err := sdk.AccAddressFromBech32(msg.Proposer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
proposalID, err := m.keeper.SubmitProposal(ctx, proposer, msg.CommitteeID, msg.GetPubProposal())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
sdk.EventTypeMessage,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySender, msg.Proposer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &types.MsgSubmitProposalResponse{ProposalID: proposalID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vote handles MsgVote messages
|
||||||
|
func (m msgServer) Vote(goCtx context.Context, msg *types.MsgVote) (*types.MsgVoteResponse, error) {
|
||||||
|
ctx := sdk.UnwrapSDKContext(goCtx)
|
||||||
|
|
||||||
|
voter, err := sdk.AccAddressFromBech32(msg.Voter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.keeper.AddVote(ctx, msg.ProposalID, voter, msg.VoteType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
sdk.EventTypeMessage,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySender, msg.Voter),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &types.MsgVoteResponse{}, nil
|
||||||
|
}
|
194
x/committee/keeper/msg_server_test.go
Normal file
194
x/committee/keeper/msg_server_test.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/app"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
// swaptypes "github.com/0glabs/0g-chain/x/swap/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
//NewDistributionGenesisWithPool creates a default distribution genesis state with some coins in the community pool.
|
||||||
|
//func NewDistributionGenesisWithPool(communityPoolCoins sdk.Coins) app.GenesisState {
|
||||||
|
//gs := distribution.DefaultGenesisState()
|
||||||
|
//gs.FeePool = distribution.FeePool{CommunityPool: sdk.NewDecCoinsFromCoins(communityPoolCoins...)}
|
||||||
|
//return app.GenesisState{distribution.ModuleName: distribution.ModuleCdc.MustMarshalJSON(gs)}
|
||||||
|
//}
|
||||||
|
|
||||||
|
type MsgServerTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
app app.TestApp
|
||||||
|
keeper keeper.Keeper
|
||||||
|
msgServer types.MsgServer
|
||||||
|
ctx sdk.Context
|
||||||
|
addresses []sdk.AccAddress
|
||||||
|
|
||||||
|
communityPoolAmt sdk.Coins
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgServerTestSuite) SetupTest() {
|
||||||
|
_, suite.addresses = app.GeneratePrivKeyAddressPairs(5)
|
||||||
|
suite.app = app.NewTestApp()
|
||||||
|
suite.keeper = suite.app.GetCommitteeKeeper()
|
||||||
|
suite.msgServer = keeper.NewMsgServerImpl(suite.keeper)
|
||||||
|
encodingCfg := app.MakeEncodingConfig()
|
||||||
|
cdc := encodingCfg.Marshaler
|
||||||
|
|
||||||
|
memberCommittee, err := types.NewMemberCommittee(
|
||||||
|
1,
|
||||||
|
"This committee is for testing.",
|
||||||
|
suite.addresses[:3],
|
||||||
|
[]types.Permission{&types.GodPermission{}},
|
||||||
|
sdk.MustNewDecFromStr("0.5"),
|
||||||
|
time.Hour*24*7,
|
||||||
|
types.TALLY_OPTION_FIRST_PAST_THE_POST,
|
||||||
|
)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
firstBlockTime := time.Date(1998, time.January, 1, 1, 0, 0, 0, time.UTC)
|
||||||
|
testGenesis := types.NewGenesisState(
|
||||||
|
3,
|
||||||
|
[]types.Committee{memberCommittee},
|
||||||
|
[]types.Proposal{},
|
||||||
|
[]types.Vote{},
|
||||||
|
)
|
||||||
|
suite.communityPoolAmt = sdk.NewCoins(sdk.NewCoin("neuron", sdkmath.NewInt(1000000000000000)))
|
||||||
|
suite.app.InitializeFromGenesisStates(
|
||||||
|
app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(testGenesis)},
|
||||||
|
// TODO: not used?
|
||||||
|
// NewDistributionGenesisWithPool(suite.communityPoolAmt),
|
||||||
|
)
|
||||||
|
suite.ctx = suite.app.NewContext(true, tmproto.Header{Height: 1, Time: firstBlockTime})
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (suite *MsgServerTestSuite) TestSubmitProposalMsg_Valid() {
|
||||||
|
// msg, err := types.NewMsgSubmitProposal(
|
||||||
|
// proposal.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description of this proposal.",
|
||||||
|
// []proposal.ParamChange{{
|
||||||
|
// Subspace: swaptypes.ModuleName,
|
||||||
|
// Key: string(swaptypes.KeySwapFee),
|
||||||
|
// Value: "\"0.001500000000000000\"",
|
||||||
|
// }},
|
||||||
|
// ),
|
||||||
|
// suite.addresses[0],
|
||||||
|
// 1,
|
||||||
|
// )
|
||||||
|
// suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// res, err := suite.msgServer.SubmitProposal(sdk.WrapSDKContext(suite.ctx), msg)
|
||||||
|
|
||||||
|
// suite.NoError(err)
|
||||||
|
// _, found := suite.keeper.GetProposal(suite.ctx, res.ProposalID)
|
||||||
|
// suite.True(found)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (suite *MsgServerTestSuite) TestSubmitProposalMsg_Invalid() {
|
||||||
|
// var committeeID uint64 = 1
|
||||||
|
// msg, err := types.NewMsgSubmitProposal(
|
||||||
|
// proposal.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description of this proposal.",
|
||||||
|
// []proposal.ParamChange{{
|
||||||
|
// Subspace: swaptypes.ModuleName,
|
||||||
|
// Key: "nonsense-key",
|
||||||
|
// Value: "nonsense-value",
|
||||||
|
// }},
|
||||||
|
// ),
|
||||||
|
// suite.addresses[0],
|
||||||
|
// committeeID,
|
||||||
|
// )
|
||||||
|
// suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// _, err = suite.msgServer.SubmitProposal(sdk.WrapSDKContext(suite.ctx), msg)
|
||||||
|
|
||||||
|
// suite.Error(err)
|
||||||
|
// suite.Empty(
|
||||||
|
// suite.keeper.GetProposalsByCommittee(suite.ctx, committeeID),
|
||||||
|
// "proposal found when none should exist",
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (suite *MsgServerTestSuite) TestSubmitProposalMsg_ValidUpgrade() {
|
||||||
|
msg, err := types.NewMsgSubmitProposal(
|
||||||
|
upgradetypes.NewSoftwareUpgradeProposal(
|
||||||
|
"A Title",
|
||||||
|
"A description of this proposal.",
|
||||||
|
upgradetypes.Plan{
|
||||||
|
Name: "emergency-shutdown-1", // identifier for the upgrade
|
||||||
|
Height: 100000,
|
||||||
|
Info: "Some information about the shutdown.",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
suite.addresses[0],
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
res, err := suite.msgServer.SubmitProposal(sdk.WrapSDKContext(suite.ctx), msg)
|
||||||
|
|
||||||
|
suite.NoError(err)
|
||||||
|
_, found := suite.keeper.GetProposal(suite.ctx, res.ProposalID)
|
||||||
|
suite.True(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create a unregisted proto for tests?
|
||||||
|
func (suite *MsgServerTestSuite) TestSubmitProposalMsg_Unregistered() {
|
||||||
|
var committeeID uint64 = 1
|
||||||
|
msg, err := types.NewMsgSubmitProposal(
|
||||||
|
&UnregisteredPubProposal{},
|
||||||
|
suite.addresses[0],
|
||||||
|
committeeID,
|
||||||
|
)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
_, err = suite.msgServer.SubmitProposal(sdk.WrapSDKContext(suite.ctx), msg)
|
||||||
|
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Empty(
|
||||||
|
suite.keeper.GetProposalsByCommittee(suite.ctx, committeeID),
|
||||||
|
"proposal found when none should exist",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (suite *MsgServerTestSuite) TestSubmitProposalMsgAndVote() {
|
||||||
|
// msg, err := types.NewMsgSubmitProposal(
|
||||||
|
// proposal.NewParameterChangeProposal(
|
||||||
|
// "A Title",
|
||||||
|
// "A description of this proposal.",
|
||||||
|
// []proposal.ParamChange{{
|
||||||
|
// Subspace: swaptypes.ModuleName,
|
||||||
|
// Key: string(swaptypes.KeySwapFee),
|
||||||
|
// Value: "\"0.001500000000000000\"",
|
||||||
|
// }},
|
||||||
|
// ),
|
||||||
|
// suite.addresses[0],
|
||||||
|
// 1,
|
||||||
|
// )
|
||||||
|
// suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// res, err := suite.msgServer.SubmitProposal(sdk.WrapSDKContext(suite.ctx), msg)
|
||||||
|
// suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// proposal, found := suite.keeper.GetProposal(suite.ctx, res.ProposalID)
|
||||||
|
// suite.Require().True(found)
|
||||||
|
|
||||||
|
// msgVote := types.NewMsgVote(suite.addresses[0], proposal.ID, types.VOTE_TYPE_YES)
|
||||||
|
// _, err = suite.msgServer.Vote(sdk.WrapSDKContext(suite.ctx), msgVote)
|
||||||
|
// suite.Require().NoError(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestMsgServerTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MsgServerTestSuite))
|
||||||
|
}
|
308
x/committee/keeper/proposal.go
Normal file
308
x/committee/keeper/proposal.go
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubmitProposal adds a proposal to a committee so that it can be voted on.
|
||||||
|
func (k Keeper) SubmitProposal(ctx sdk.Context, proposer sdk.AccAddress, committeeID uint64, pubProposal types.PubProposal) (uint64, error) {
|
||||||
|
// Limit proposals to only be submitted by committee members
|
||||||
|
com, found := k.GetCommittee(ctx, committeeID)
|
||||||
|
if !found {
|
||||||
|
return 0, errorsmod.Wrapf(types.ErrUnknownCommittee, "%d", committeeID)
|
||||||
|
}
|
||||||
|
if !com.HasMember(proposer) {
|
||||||
|
return 0, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "proposer not member of committee")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check committee has permissions to enact proposal.
|
||||||
|
if !com.HasPermissionsFor(ctx, k.cdc, k.paramKeeper, pubProposal) {
|
||||||
|
return 0, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "committee does not have permissions to enact proposal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check proposal is valid
|
||||||
|
if err := k.ValidatePubProposal(ctx, pubProposal); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a new ID and store the proposal
|
||||||
|
deadline := ctx.BlockTime().Add(com.GetProposalDuration())
|
||||||
|
proposalID, err := k.StoreNewProposal(ctx, pubProposal, committeeID, deadline)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeProposalSubmit,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyCommitteeID, fmt.Sprintf("%d", com.GetID())),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposalID)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyDeadline, deadline.String()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return proposalID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddVote submits a vote on a proposal.
|
||||||
|
func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress, voteType types.VoteType) error {
|
||||||
|
// Validate
|
||||||
|
pr, found := k.GetProposal(ctx, proposalID)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrapf(types.ErrUnknownProposal, "%d", proposalID)
|
||||||
|
}
|
||||||
|
if pr.HasExpiredBy(ctx.BlockTime()) {
|
||||||
|
return errorsmod.Wrapf(types.ErrProposalExpired, "%s ≥ %s", ctx.BlockTime(), pr.Deadline)
|
||||||
|
}
|
||||||
|
com, found := k.GetCommittee(ctx, pr.CommitteeID)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrapf(types.ErrUnknownCommittee, "%d", pr.CommitteeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := com.(*types.MemberCommittee); ok {
|
||||||
|
if !com.HasMember(voter) {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "voter must be a member of committee")
|
||||||
|
}
|
||||||
|
if voteType != types.VOTE_TYPE_YES {
|
||||||
|
return errorsmod.Wrap(types.ErrInvalidVoteType, "member committees only accept yes votes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store vote, overwriting any prior vote
|
||||||
|
k.SetVote(ctx, types.NewVote(proposalID, voter, voteType))
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeProposalVote,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyCommitteeID, fmt.Sprintf("%d", com.GetID())),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", pr.ID)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyVoter, voter.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyVote, fmt.Sprintf("%d", voteType)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatePubProposal checks if a pubproposal is valid.
|
||||||
|
func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubProposal) (returnErr error) {
|
||||||
|
if pubProposal == nil {
|
||||||
|
return errorsmod.Wrap(types.ErrInvalidPubProposal, "pub proposal cannot be nil")
|
||||||
|
}
|
||||||
|
if err := pubProposal.ValidateBasic(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !k.router.HasRoute(pubProposal.ProposalRoute()) {
|
||||||
|
return errorsmod.Wrapf(types.ErrNoProposalHandlerExists, "%T", pubProposal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the proposal's changes through the associated handler using a cached version of state to ensure changes are not permanent.
|
||||||
|
cacheCtx, _ := ctx.CacheContext()
|
||||||
|
handler := k.router.GetRoute(pubProposal.ProposalRoute())
|
||||||
|
|
||||||
|
// Handle an edge case where a param change proposal causes the proposal handler to panic.
|
||||||
|
// A param change proposal with a registered subspace value but unregistered key value will cause a panic in the param change proposal handler.
|
||||||
|
// This defer will catch panics and return a normal error: `recover()` gets the panic value, then the enclosing function's return value is swapped for an error.
|
||||||
|
// reference: https://stackoverflow.com/questions/33167282/how-to-return-a-value-in-a-go-function-that-panics?noredirect=1&lq=1
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
returnErr = errorsmod.Wrapf(types.ErrInvalidPubProposal, "proposal handler panicked: %s", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := handler(cacheCtx, pubProposal); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Keeper) ProcessProposals(ctx sdk.Context) {
|
||||||
|
k.IterateProposals(ctx, func(proposal types.Proposal) bool {
|
||||||
|
committee, found := k.GetCommittee(ctx, proposal.CommitteeID)
|
||||||
|
if !found {
|
||||||
|
k.CloseProposal(ctx, proposal, types.Failed)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !proposal.HasExpiredBy(ctx.BlockTime()) {
|
||||||
|
if committee.GetTallyOption() == types.TALLY_OPTION_FIRST_PAST_THE_POST {
|
||||||
|
passed := k.GetProposalResult(ctx, proposal.ID, committee)
|
||||||
|
if passed {
|
||||||
|
outcome := k.attemptEnactProposal(ctx, proposal)
|
||||||
|
k.CloseProposal(ctx, proposal, outcome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
passed := k.GetProposalResult(ctx, proposal.ID, committee)
|
||||||
|
outcome := types.Failed
|
||||||
|
if passed {
|
||||||
|
outcome = k.attemptEnactProposal(ctx, proposal)
|
||||||
|
}
|
||||||
|
k.CloseProposal(ctx, proposal, outcome)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Keeper) GetProposalResult(ctx sdk.Context, proposalID uint64, committee types.Committee) bool {
|
||||||
|
switch com := committee.(type) {
|
||||||
|
case *types.MemberCommittee:
|
||||||
|
return k.GetMemberCommitteeProposalResult(ctx, proposalID, com)
|
||||||
|
case *types.TokenCommittee:
|
||||||
|
return k.GetTokenCommitteeProposalResult(ctx, proposalID, com)
|
||||||
|
default: // Should never hit default case
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemberCommitteeProposalResult gets the result of a member committee proposal
|
||||||
|
func (k Keeper) GetMemberCommitteeProposalResult(ctx sdk.Context, proposalID uint64, committee types.Committee) bool {
|
||||||
|
currVotes := k.TallyMemberCommitteeVotes(ctx, proposalID)
|
||||||
|
possibleVotes := sdk.NewDec(int64(len(committee.GetMembers())))
|
||||||
|
return currVotes.GTE(committee.GetVoteThreshold().Mul(possibleVotes)) // vote threshold requirements
|
||||||
|
}
|
||||||
|
|
||||||
|
// TallyMemberCommitteeVotes returns the polling status of a member committee vote
|
||||||
|
func (k Keeper) TallyMemberCommitteeVotes(ctx sdk.Context, proposalID uint64) (totalVotes sdk.Dec) {
|
||||||
|
votes := k.GetVotesByProposal(ctx, proposalID)
|
||||||
|
return sdk.NewDec(int64(len(votes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTokenCommitteeProposalResult gets the result of a token committee proposal
|
||||||
|
func (k Keeper) GetTokenCommitteeProposalResult(ctx sdk.Context, proposalID uint64, committee *types.TokenCommittee) bool {
|
||||||
|
yesVotes, noVotes, totalVotes, possibleVotes := k.TallyTokenCommitteeVotes(ctx, proposalID, committee.TallyDenom)
|
||||||
|
if totalVotes.GTE(committee.Quorum.Mul(possibleVotes)) { // quorum requirement
|
||||||
|
nonAbstainVotes := yesVotes.Add(noVotes)
|
||||||
|
if yesVotes.GTE(nonAbstainVotes.Mul(committee.VoteThreshold)) { // vote threshold requirements
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TallyMemberCommitteeVotes returns the polling status of a token committee vote. Returns yes votes,
|
||||||
|
// total current votes, total possible votes (equal to token supply), vote threshold (yes vote ratio
|
||||||
|
// required for proposal to pass), and quorum (votes tallied at this percentage).
|
||||||
|
func (k Keeper) TallyTokenCommitteeVotes(ctx sdk.Context, proposalID uint64,
|
||||||
|
tallyDenom string,
|
||||||
|
) (yesVotes, noVotes, totalVotes, possibleVotes sdk.Dec) {
|
||||||
|
votes := k.GetVotesByProposal(ctx, proposalID)
|
||||||
|
|
||||||
|
yesVotes = sdk.ZeroDec()
|
||||||
|
noVotes = sdk.ZeroDec()
|
||||||
|
totalVotes = sdk.ZeroDec()
|
||||||
|
for _, vote := range votes {
|
||||||
|
// 1 token = 1 vote
|
||||||
|
acc := k.accountKeeper.GetAccount(ctx, vote.Voter)
|
||||||
|
accNumCoins := k.bankKeeper.GetBalance(ctx, acc.GetAddress(), tallyDenom).Amount
|
||||||
|
|
||||||
|
// Add votes to counters
|
||||||
|
totalVotes = totalVotes.Add(sdk.NewDecFromInt(accNumCoins))
|
||||||
|
if vote.VoteType == types.VOTE_TYPE_YES {
|
||||||
|
yesVotes = yesVotes.Add(sdk.NewDecFromInt(accNumCoins))
|
||||||
|
} else if vote.VoteType == types.VOTE_TYPE_NO {
|
||||||
|
noVotes = noVotes.Add(sdk.NewDecFromInt(accNumCoins))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
possibleVotesInt := k.bankKeeper.GetSupply(ctx, tallyDenom).Amount
|
||||||
|
return yesVotes, noVotes, totalVotes, sdk.NewDecFromInt(possibleVotesInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Keeper) attemptEnactProposal(ctx sdk.Context, proposal types.Proposal) types.ProposalOutcome {
|
||||||
|
err := k.enactProposal(ctx, proposal)
|
||||||
|
if err != nil {
|
||||||
|
return types.Invalid
|
||||||
|
}
|
||||||
|
return types.Passed
|
||||||
|
}
|
||||||
|
|
||||||
|
// enactProposal makes the changes proposed in a proposal.
|
||||||
|
func (k Keeper) enactProposal(ctx sdk.Context, proposal types.Proposal) error {
|
||||||
|
// Check committee still has permissions for the proposal
|
||||||
|
// Since the proposal was submitted params could have changed, invalidating the permission of the committee.
|
||||||
|
com, found := k.GetCommittee(ctx, proposal.CommitteeID)
|
||||||
|
if !found {
|
||||||
|
return errorsmod.Wrapf(types.ErrUnknownCommittee, "%d", proposal.CommitteeID)
|
||||||
|
}
|
||||||
|
if !com.HasPermissionsFor(ctx, k.cdc, k.paramKeeper, proposal.GetContent()) {
|
||||||
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "committee does not have permissions to enact proposal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := k.ValidatePubProposal(ctx, proposal.GetContent()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// enact the proposal
|
||||||
|
handler := k.router.GetRoute(proposal.GetContent().ProposalRoute())
|
||||||
|
if err := handler(ctx, proposal.GetContent()); err != nil {
|
||||||
|
// the handler should not error as it was checked in ValidatePubProposal
|
||||||
|
panic(fmt.Sprintf("unexpected handler error: %s", err))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProposalTallyResponse returns the tally results of a proposal.
|
||||||
|
func (k Keeper) GetProposalTallyResponse(ctx sdk.Context, proposalID uint64) (*types.QueryTallyResponse, bool) {
|
||||||
|
proposal, found := k.GetProposal(ctx, proposalID)
|
||||||
|
if !found {
|
||||||
|
return nil, found
|
||||||
|
}
|
||||||
|
committee, found := k.GetCommittee(ctx, proposal.CommitteeID)
|
||||||
|
if !found {
|
||||||
|
return nil, found
|
||||||
|
}
|
||||||
|
var proposalTally types.QueryTallyResponse
|
||||||
|
switch com := committee.(type) {
|
||||||
|
case *types.MemberCommittee:
|
||||||
|
currVotes := k.TallyMemberCommitteeVotes(ctx, proposal.ID)
|
||||||
|
possibleVotes := sdk.NewDec(int64(len(com.Members)))
|
||||||
|
proposalTally = types.QueryTallyResponse{
|
||||||
|
ProposalID: proposal.ID,
|
||||||
|
YesVotes: currVotes,
|
||||||
|
NoVotes: sdk.ZeroDec(),
|
||||||
|
CurrentVotes: currVotes,
|
||||||
|
PossibleVotes: possibleVotes,
|
||||||
|
VoteThreshold: com.VoteThreshold,
|
||||||
|
Quorum: sdk.ZeroDec(),
|
||||||
|
}
|
||||||
|
case *types.TokenCommittee:
|
||||||
|
yesVotes, noVotes, currVotes, possibleVotes := k.TallyTokenCommitteeVotes(ctx, proposal.ID, com.TallyDenom)
|
||||||
|
proposalTally = types.QueryTallyResponse{
|
||||||
|
ProposalID: proposal.ID,
|
||||||
|
YesVotes: yesVotes,
|
||||||
|
NoVotes: noVotes,
|
||||||
|
CurrentVotes: currVotes,
|
||||||
|
PossibleVotes: possibleVotes,
|
||||||
|
VoteThreshold: com.VoteThreshold,
|
||||||
|
Quorum: com.Quorum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &proposalTally, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseProposal deletes proposals and their votes, emitting an event denoting the final status of the proposal
|
||||||
|
func (k Keeper) CloseProposal(ctx sdk.Context, proposal types.Proposal, outcome types.ProposalOutcome) {
|
||||||
|
tally, _ := k.GetProposalTallyResponse(ctx, proposal.ID)
|
||||||
|
k.DeleteProposalAndVotes(ctx, proposal.ID)
|
||||||
|
|
||||||
|
bz, err := k.cdc.MarshalJSON(tally)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error marshaling proposal tally to bytes:", tally.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeProposalClose,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyCommitteeID, fmt.Sprintf("%d", proposal.CommitteeID)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposal.ID)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyProposalTally, string(bz)),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyProposalOutcome, outcome.String()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
1263
x/committee/keeper/proposal_test.go
Normal file
1263
x/committee/keeper/proposal_test.go
Normal file
File diff suppressed because it is too large
Load Diff
146
x/committee/module.go
Normal file
146
x/committee/module.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package committee
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
abci "github.com/cometbft/cometbft/abci/types"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/client/cli"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConsensusVersion defines the current module consensus version.
|
||||||
|
const ConsensusVersion = 1
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ module.AppModule = AppModule{}
|
||||||
|
_ module.AppModuleBasic = AppModuleBasic{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// AppModuleBasic
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// AppModuleBasic app module basics object
|
||||||
|
type AppModuleBasic struct{}
|
||||||
|
|
||||||
|
// Name get module name
|
||||||
|
func (AppModuleBasic) Name() string {
|
||||||
|
return types.ModuleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registers legacy amino codec
|
||||||
|
func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
||||||
|
types.RegisterLegacyAminoCodec(cdc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInterfaces registers the module's interface types
|
||||||
|
func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) {
|
||||||
|
types.RegisterInterfaces(reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultGenesis default genesis state
|
||||||
|
func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
|
||||||
|
return cdc.MustMarshalJSON(types.DefaultGenesisState())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateGenesis module validate genesis
|
||||||
|
func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error {
|
||||||
|
var genState types.GenesisState
|
||||||
|
if err := cdc.UnmarshalJSON(bz, &genState); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
|
||||||
|
}
|
||||||
|
return genState.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for committee module.
|
||||||
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxCmd returns committee module's root tx command.
|
||||||
|
func (a AppModuleBasic) GetTxCmd() *cobra.Command {
|
||||||
|
return cli.GetTxCmd()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryCmd returns committee module's root query command.
|
||||||
|
func (AppModuleBasic) GetQueryCmd() *cobra.Command {
|
||||||
|
return cli.GetQueryCmd()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// AppModule
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// AppModule implements the AppModule interface for committee module.
|
||||||
|
type AppModule struct {
|
||||||
|
AppModuleBasic
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
accountKeeper types.AccountKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppModule creates a new AppModule object
|
||||||
|
func NewAppModule(keeper keeper.Keeper, accountKeeper types.AccountKeeper) AppModule {
|
||||||
|
return AppModule{
|
||||||
|
AppModuleBasic: AppModuleBasic{},
|
||||||
|
keeper: keeper,
|
||||||
|
accountKeeper: accountKeeper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns committee module's name.
|
||||||
|
func (am AppModule) Name() string {
|
||||||
|
return am.AppModuleBasic.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterServices registers a GRPC query service to respond to the
|
||||||
|
// module-specific GRPC queries.
|
||||||
|
func (am AppModule) RegisterServices(cfg module.Configurator) {
|
||||||
|
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
|
||||||
|
types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInvariants registers committee module's invariants.
|
||||||
|
func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
|
||||||
|
|
||||||
|
// InitGenesis performs committee module's genesis initialization It returns
|
||||||
|
// no validator updates.
|
||||||
|
func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate {
|
||||||
|
var genState types.GenesisState
|
||||||
|
cdc.MustUnmarshalJSON(gs, &genState)
|
||||||
|
InitGenesis(ctx, am.keeper, &genState)
|
||||||
|
return []abci.ValidatorUpdate{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportGenesis returns committee module's exported genesis state as raw JSON bytes.
|
||||||
|
func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
|
||||||
|
genState := ExportGenesis(ctx, am.keeper)
|
||||||
|
return cdc.MustMarshalJSON(genState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsensusVersion implements ConsensusVersion.
|
||||||
|
func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion }
|
||||||
|
|
||||||
|
// BeginBlock executes all ABCI BeginBlock logic respective to committee module.
|
||||||
|
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
|
||||||
|
BeginBlocker(ctx, req, am.keeper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndBlock executes all ABCI EndBlock logic respective to committee module. It
|
||||||
|
// returns no validator updates.
|
||||||
|
func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
|
||||||
|
return []abci.ValidatorUpdate{}
|
||||||
|
}
|
55
x/committee/proposal_handler.go
Normal file
55
x/committee/proposal_handler.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package committee
|
||||||
|
|
||||||
|
import (
|
||||||
|
errorsmod "cosmossdk.io/errors"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/keeper"
|
||||||
|
"github.com/0glabs/0g-chain/x/committee/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewProposalHandler(k keeper.Keeper) govv1beta1.Handler {
|
||||||
|
return func(ctx sdk.Context, content govv1beta1.Content) error {
|
||||||
|
switch c := content.(type) {
|
||||||
|
case *types.CommitteeChangeProposal:
|
||||||
|
return handleCommitteeChangeProposal(ctx, k, c)
|
||||||
|
case *types.CommitteeDeleteProposal:
|
||||||
|
return handleCommitteeDeleteProposal(ctx, k, c)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s proposal content type: %T", types.ModuleName, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCommitteeChangeProposal(ctx sdk.Context, k keeper.Keeper, committeeProposal *types.CommitteeChangeProposal) error {
|
||||||
|
if err := committeeProposal.ValidateBasic(); err != nil {
|
||||||
|
return errorsmod.Wrap(types.ErrInvalidPubProposal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all committee's ongoing proposals
|
||||||
|
proposals := k.GetProposalsByCommittee(ctx, committeeProposal.GetNewCommittee().GetID())
|
||||||
|
for _, p := range proposals {
|
||||||
|
k.CloseProposal(ctx, p, types.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update/create the committee
|
||||||
|
k.SetCommittee(ctx, committeeProposal.GetNewCommittee())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCommitteeDeleteProposal(ctx sdk.Context, k keeper.Keeper, committeeProposal *types.CommitteeDeleteProposal) error {
|
||||||
|
if err := committeeProposal.ValidateBasic(); err != nil {
|
||||||
|
return errorsmod.Wrap(types.ErrInvalidPubProposal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all committee's ongoing proposals
|
||||||
|
proposals := k.GetProposalsByCommittee(ctx, committeeProposal.CommitteeID)
|
||||||
|
for _, p := range proposals {
|
||||||
|
k.CloseProposal(ctx, p, types.Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
k.DeleteCommittee(ctx, committeeProposal.CommitteeID)
|
||||||
|
return nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user