diff --git a/contracts/neutron_chain_manager.wasm b/contracts/neutron_chain_manager.wasm new file mode 100644 index 000000000..9fe227122 Binary files /dev/null and b/contracts/neutron_chain_manager.wasm differ diff --git a/network/init-neutrond.sh b/network/init-neutrond.sh index bf2278311..76fbdb78e 100755 --- a/network/init-neutrond.sh +++ b/network/init-neutrond.sh @@ -48,6 +48,8 @@ SUBDAO_PROPOSAL_CONTRACT=$CONTRACTS_BINARIES_DIR/cwd_subdao_proposal_single.wasm CW4_VOTING_CONTRACT=$THIRD_PARTY_CONTRACTS_DIR/cw4_voting.wasm CW4_GROUP_CONTRACT=$THIRD_PARTY_CONTRACTS_DIR/cw4_group.wasm +NEUTRON_CHAIN_MANAGER_CONTRACT=$CONTRACTS_BINARIES_DIR/neutron_chain_manager.wasm + echo "Add consumer section..." $BINARY add-consumer-section --home "$CHAIN_DIR" ### PARAMETERS SECTION @@ -137,6 +139,8 @@ SECURITY_SUBDAO_PROPOSAL_LABEL="neutron.subdaos.security.proposals.single" SECURITY_SUBDAO_PRE_PROPOSE_LABEL="neutron.subdaos.security.proposals.single.pre_propose" SECURITY_SUBDAO_VOTE_LABEL="neutron.subdaos.security.voting" +NEUTRON_CHAIN_MANAGER_LABEL="neutron.chain.manager" + echo "Initializing dao contract in genesis..." function store_binary() { @@ -172,6 +176,8 @@ SUBDAO_PROPOSAL_BINARY_ID=$(store_binary "$SUBDAO_PROPOSAL_CONTRA CW4_VOTING_CONTRACT_BINARY_ID=$(store_binary "$CW4_VOTING_CONTRACT") CW4_GROUP_CONTRACT_BINARY_ID=$(store_binary "$CW4_GROUP_CONTRACT") +NEUTRON_CHAIN_MANAGER_BINARY_ID=$(store_binary "$NEUTRON_CHAIN_MANAGER_CONTRACT") + # WARNING! # The following code is needed to pre-generate the contract addresses # Those addresses depend on the ORDER OF CONTRACTS INITIALIZATION @@ -221,6 +227,8 @@ GRANTS_SUBDAO_PRE_PROPOSE_CONTRACT_ADDRESS=$(genaddr "$SUBDAO_PRE_PROPOSE_BINA GRANTS_SUBDAO_TIMELOCK_CONTRACT_ADDRESS=$(genaddr "$SUBDAO_TIMELOCK_BINARY_ID") && (( INSTANCE_ID_COUNTER++ )) GRANTS_SUBDAO_GROUP_CONTRACT_ADDRESS=$(genaddr "$CW4_GROUP_CONTRACT_BINARY_ID") && (( INSTANCE_ID_COUNTER++ )) +NEUTRON_CHAIN_MANAGER_CONTRACT_ADDRESS=$(genaddr "$NEUTRON_CHAIN_MANAGER_BINARY_ID") && (( INSTANCE_ID_COUNTER++ )) + function check_json() { MSG=$1 if ! jq -e . >/dev/null 2>&1 <<<"$MSG"; then @@ -604,6 +612,11 @@ GRANTS_SUBDAO_CORE_INIT_MSG='{ "security_dao": "'"$SECURITY_SUBDAO_CORE_CONTRACT_ADDRESS"'" }' +# CHAIN MANAGER +NEUTRON_CHAIN_MANAGER_INIT_MSG='{ + "initial_strategy_address": "'"$DAO_CONTRACT_ADDRESS"'" +}' + echo "Instantiate contracts" function init_contract() { @@ -621,12 +634,13 @@ function init_contract() { # If you're to do any changes, please do it consistently in both sections init_contract "$NEUTRON_VAULT_CONTRACT_BINARY_ID" "$NEUTRON_VAULT_INIT" "$NEUTRON_VAULT_LABEL" init_contract "$NEUTRON_INVESTORS_VAULT_CONTRACT_BINARY_ID" "$NEUTRON_INVESTORS_VAULT_INIT" "$NEUTRON_INVESTORS_VAULT_LABEL" -init_contract "$NEUTRON_VESTING_INVESTORS_BINARY_ID" "$NEUTRON_VESTING_INVESTORS_INIT" "$NEUTRON_VESTING_INVESTORS_LABEL" +init_contract "$NEUTRON_VESTING_INVESTORS_BINARY_ID" "$NEUTRON_VESTING_INVESTORS_INIT" "$NEUTRON_VESTING_INVESTORS_LABEL" init_contract "$DAO_CONTRACT_BINARY_ID" "$DAO_INIT" "$DAO_CORE_LABEL" init_contract "$RESERVE_CONTRACT_BINARY_ID" "$RESERVE_INIT" "$RESERVE_LABEL" init_contract "$DISTRIBUTION_CONTRACT_BINARY_ID" "$DISTRIBUTION_INIT" "$DISTRIBUTION_LABEL" init_contract "$SUBDAO_CORE_BINARY_ID" "$SECURITY_SUBDAO_CORE_INIT_MSG" "$SECURITY_SUBDAO_CORE_LABEL" init_contract "$SUBDAO_CORE_BINARY_ID" "$GRANTS_SUBDAO_CORE_INIT_MSG" "$GRANTS_SUBDAO_CORE_LABEL" +init_contract "$NEUTRON_CHAIN_MANAGER_BINARY_ID" "$NEUTRON_CHAIN_MANAGER_INIT_MSG" "$NEUTRON_CHAIN_MANAGER_LABEL" ADD_SUBDAOS_MSG='{ "update_sub_daos": { @@ -702,7 +716,7 @@ function convert_bech32_base64_esc() { DAO_CONTRACT_ADDRESS_B64=$(convert_bech32_base64_esc "$DAO_CONTRACT_ADDRESS") echo $DAO_CONTRACT_ADDRESS_B64 -set_genesis_param admins "[\"$DAO_CONTRACT_ADDRESS\"]" # admin module +set_genesis_param admins "[\"$NEUTRON_CHAIN_MANAGER_CONTRACT_ADDRESS\"]" # admin module set_genesis_param treasury_address "\"$DAO_CONTRACT_ADDRESS\"" # feeburner set_genesis_param fee_collector_address "\"$DAO_CONTRACT_ADDRESS\"" # tokenfactory set_genesis_param security_address "\"$SECURITY_SUBDAO_CORE_CONTRACT_ADDRESS\"," # cron diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index ec2ef1ef8..8cd04e6b5 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -682,14 +682,14 @@ func (m *CustomMessenger) setBeforeSendHook(ctx sdk.Context, contractAddr sdk.Ac } // PerformMint used with mintTokens to validate the mint message and mint through token factory. -func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindings.MintTokens) error { +func PerformMint(f *tokenfactorykeeper.Keeper, _ *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindings.MintTokens) error { rcpt, err := parseAddress(mint.MintToAddress) if err != nil { return err } coin := sdk.Coin{Denom: mint.Denom, Amount: mint.Amount} - sdkMsg := tokenfactorytypes.NewMsgMint(contractAddr.String(), coin) + sdkMsg := tokenfactorytypes.NewMsgMintTo(contractAddr.String(), coin, rcpt.String()) if err = sdkMsg.ValidateBasic(); err != nil { return err } @@ -701,11 +701,6 @@ func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk return errors.Wrap(err, "minting coins from message") } - err = b.SendCoins(ctx, contractAddr, rcpt, sdk.NewCoins(coin)) - if err != nil { - return errors.Wrap(err, "sending newly minted coins from message") - } - return nil } diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go index 30146f227..753d25a89 100644 --- a/x/tokenfactory/keeper/bankactions.go +++ b/x/tokenfactory/keeper/bankactions.go @@ -1,8 +1,6 @@ package keeper import ( - "sort" - sdk "github.com/cosmos/cosmos-sdk/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -17,12 +15,16 @@ func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { return err } - err = k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) + addr, err := sdk.AccAddressFromBech32(mintTo) if err != nil { return err } - addr, err := sdk.AccAddressFromBech32(mintTo) + if k.isModuleAccount(ctx, addr) { + return status.Errorf(codes.Internal, "minting to module accounts is forbidden") + } + + err = k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) if err != nil { return err } @@ -39,13 +41,17 @@ func (k Keeper) burnFrom(ctx sdk.Context, amount sdk.Coin, burnFrom string) erro return err } - addr, err := sdk.AccAddressFromBech32(burnFrom) + burnFromAddr, err := sdk.AccAddressFromBech32(burnFrom) if err != nil { return err } + if k.isModuleAccount(ctx, burnFromAddr) { + return status.Errorf(codes.Internal, "burning from module accounts is forbidden") + } + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, - addr, + burnFromAddr, types.ModuleName, sdk.NewCoins(amount)) if err != nil { @@ -62,37 +68,38 @@ func (k Keeper) forceTransfer(ctx sdk.Context, amount sdk.Coin, fromAddr, toAddr return err } - fromAcc, err := sdk.AccAddressFromBech32(fromAddr) + fromSdkAddr, err := sdk.AccAddressFromBech32(fromAddr) + if err != nil { + return err + } + + toSdkAddr, err := sdk.AccAddressFromBech32(toAddr) if err != nil { return err } - sortedPermAddrs := make([]string, 0, len(k.permAddrs)) - for moduleName := range k.permAddrs { - sortedPermAddrs = append(sortedPermAddrs, moduleName) + if k.isModuleAccount(ctx, fromSdkAddr) { + return status.Errorf(codes.Internal, "force transfer from module acc not available") } - sort.Strings(sortedPermAddrs) - for _, moduleName := range sortedPermAddrs { + if k.isModuleAccount(ctx, toSdkAddr) { + return status.Errorf(codes.Internal, "force transfer to module acc not available") + } + + return k.bankKeeper.SendCoins(ctx, fromSdkAddr, toSdkAddr, sdk.NewCoins(amount)) +} + +func (k Keeper) isModuleAccount(ctx sdk.Context, addr sdk.AccAddress) bool { + for _, moduleName := range k.knownModules { account := k.accountKeeper.GetModuleAccount(ctx, moduleName) if account == nil { - return status.Errorf(codes.NotFound, "account %s not found", moduleName) + continue } - if account.GetAddress().Equals(fromAcc) { - return status.Errorf(codes.Internal, "send from module acc not available") + if account.GetAddress().Equals(addr) { + return true } } - fromSdkAddr, err := sdk.AccAddressFromBech32(fromAddr) - if err != nil { - return err - } - - toSdkAddr, err := sdk.AccAddressFromBech32(toAddr) - if err != nil { - return err - } - - return k.bankKeeper.SendCoins(ctx, fromSdkAddr, toSdkAddr, sdk.NewCoins(amount)) + return false } diff --git a/x/tokenfactory/keeper/keeper.go b/x/tokenfactory/keeper/keeper.go index 56f9424d9..4f94ad823 100644 --- a/x/tokenfactory/keeper/keeper.go +++ b/x/tokenfactory/keeper/keeper.go @@ -2,11 +2,10 @@ package keeper import ( "fmt" + "sort" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/store/prefix" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,7 +16,7 @@ import ( type ( Keeper struct { storeKey storetypes.StoreKey - permAddrs map[string]authtypes.PermissionsForAddress + knownModules []string cdc codec.Codec accountKeeper types.AccountKeeper bankKeeper types.BankKeeper @@ -36,15 +35,16 @@ func NewKeeper( contractKeeper types.ContractKeeper, authority string, ) Keeper { - permAddrs := make(map[string]authtypes.PermissionsForAddress) - for name, perms := range maccPerms { - permAddrs[name] = authtypes.NewPermissionsForAddress(name, perms) + sortedKnownModules := make([]string, 0, len(maccPerms)) + for moduleName := range maccPerms { + sortedKnownModules = append(sortedKnownModules, moduleName) } + sort.Strings(sortedKnownModules) return Keeper{ cdc: cdc, storeKey: storeKey, - permAddrs: permAddrs, + knownModules: sortedKnownModules, accountKeeper: accountKeeper, bankKeeper: bankKeeper, contractKeeper: contractKeeper, diff --git a/x/tokenfactory/keeper/keeper_test.go b/x/tokenfactory/keeper/keeper_test.go index 01744e262..16fca3b4a 100644 --- a/x/tokenfactory/keeper/keeper_test.go +++ b/x/tokenfactory/keeper/keeper_test.go @@ -125,6 +125,47 @@ func (suite *KeeperTestSuite) TestForceTransferMsg() { suite.Require().NoError(err) _, err = suite.msgServer.ForceTransfer(suite.ChainA.GetContext(), types.NewMsgForceTransfer(suite.TestAccs[0].String(), mintAmt, govModAcc.GetAddress().String(), suite.TestAccs[1].String())) - suite.Require().ErrorContains(err, "send from module acc not available") + suite.Require().ErrorContains(err, "force transfer from module acc not available") + + _, err = suite.msgServer.ForceTransfer(suite.ChainA.GetContext(), types.NewMsgForceTransfer(suite.TestAccs[0].String(), mintAmt, suite.TestAccs[1].String(), govModAcc.GetAddress().String())) + suite.Require().ErrorContains(err, "force transfer to module acc not available") + }) +} + +func (suite *KeeperTestSuite) TestMintToMsg() { + suite.Setup() + + // Create a denom + suite.CreateDefaultDenom(suite.ChainA.GetContext()) + + suite.Run("test mint to", func() { + mintAmt := sdktypes.NewInt64Coin(suite.defaultDenom, 10) + + govModAcc := suite.GetNeutronZoneApp(suite.ChainA).AccountKeeper.GetModuleAccount(suite.ChainA.GetContext(), authtypes.FeeCollectorName) + + _, err := suite.msgServer.Mint(suite.ChainA.GetContext(), types.NewMsgMintTo(suite.TestAccs[0].String(), mintAmt, govModAcc.GetAddress().String())) + suite.Require().ErrorContains(err, "minting to module accounts is forbidden") + }) +} + +func (suite *KeeperTestSuite) TestBurnFromMsg() { + suite.Setup() + + // Create a denom + suite.CreateDefaultDenom(suite.ChainA.GetContext()) + + suite.Run("test burn from", func() { + mintAmt := sdktypes.NewInt64Coin(suite.defaultDenom, 10) + + _, err := suite.msgServer.Mint(sdktypes.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgMint(suite.TestAccs[0].String(), mintAmt)) + suite.Require().NoError(err) + + govModAcc := suite.GetNeutronZoneApp(suite.ChainA).AccountKeeper.GetModuleAccount(suite.ChainA.GetContext(), authtypes.FeeCollectorName) + + err = suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.SendCoins(suite.ChainA.GetContext(), suite.TestAccs[0], govModAcc.GetAddress(), sdktypes.NewCoins(mintAmt)) + suite.Require().NoError(err) + + _, err = suite.msgServer.Burn(suite.ChainA.GetContext(), types.NewMsgBurnFrom(suite.TestAccs[0].String(), mintAmt, govModAcc.GetAddress().String())) + suite.Require().ErrorContains(err, "burning from module accounts is forbidden") }) } diff --git a/x/tokenfactory/types/msgs.go b/x/tokenfactory/types/msgs.go index 77190c4ea..b3481a431 100644 --- a/x/tokenfactory/types/msgs.go +++ b/x/tokenfactory/types/msgs.go @@ -106,10 +106,11 @@ func NewMsgBurn(sender string, amount sdk.Coin) *MsgBurn { } // NewMsgBurn creates a message to burn tokens -func NewMsgBurnFrom(sender string, amount sdk.Coin, _ string) *MsgBurn { +func NewMsgBurnFrom(sender string, amount sdk.Coin, from string) *MsgBurn { return &MsgBurn{ - Sender: sender, - Amount: amount, + Sender: sender, + Amount: amount, + BurnFromAddress: from, } }