diff --git a/command/polybft/polybft_command.go b/command/polybft/polybft_command.go index 079080eb4b..af5bed0800 100644 --- a/command/polybft/polybft_command.go +++ b/command/polybft/polybft_command.go @@ -4,6 +4,7 @@ import ( "github.com/0xPolygon/polygon-edge/command/rootchain/registration" "github.com/0xPolygon/polygon-edge/command/rootchain/staking" "github.com/0xPolygon/polygon-edge/command/rootchain/supernet" + "github.com/0xPolygon/polygon-edge/command/rootchain/supernet/stakemanager" "github.com/0xPolygon/polygon-edge/command/rootchain/validators" "github.com/0xPolygon/polygon-edge/command/rootchain/whitelist" "github.com/0xPolygon/polygon-edge/command/rootchain/withdraw" @@ -39,6 +40,8 @@ func GetCommand() *cobra.Command { // rootchain (supernet manager) command for finalizing genesis // validator set and enabling staking supernet.GetCommand(), + // rootchain command for deploying stake manager + stakemanager.GetCommand(), ) return polybftCmd diff --git a/command/rootchain/deploy/deploy.go b/command/rootchain/deploy/deploy.go index 00e474da43..43f8e30d3d 100644 --- a/command/rootchain/deploy/deploy.go +++ b/command/rootchain/deploy/deploy.go @@ -43,7 +43,6 @@ const ( rootERC1155Name = "RootERC1155" erc1155TemplateName = "ERC1155Template" customSupernetManagerName = "CustomSupernetManager" - stakeManagerName = "StakeManager" ) var ( @@ -97,21 +96,11 @@ var ( customSupernetManagerName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { rootchainConfig.CustomSupernetManagerAddress = addr }, - stakeManagerName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.StakeManagerAddress = addr - }, } // initializersMap maps rootchain contract names to initializer function callbacks initializersMap = map[string]func(command.OutputFormatter, txrelayer.TxRelayer, *polybft.RootchainConfig, ethgo.Key) error{ - stakeManagerName: func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - - return initializeStakeManager(fmt, relayer, config, key) - }, customSupernetManagerName: func(fmt command.OutputFormatter, relayer txrelayer.TxRelayer, config *polybft.RootchainConfig, @@ -209,7 +198,15 @@ func GetCommand() *cobra.Command { " (otherwise provided secrets are used to resolve deployer account)", ) + cmd.Flags().StringVar( + ¶ms.stakeManagerAddr, + helper.StakeManagerFlag, + "", + helper.StakeManagerFlagDesc, + ) + cmd.MarkFlagsMutuallyExclusive(helper.TestModeFlag, deployerKeyFlag) + _ = cmd.MarkFlagRequired(helper.StakeManagerFlag) return cmd } @@ -222,7 +219,7 @@ func runCommand(cmd *cobra.Command, _ []string) { outputter := command.InitializeOutputter(cmd) defer outputter.WriteOutput() - outputter.WriteCommandResult(&messageResult{ + outputter.WriteCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s started... Rootchain JSON RPC address %s.", contractsDeploymentTitle, params.jsonRPCAddress), }) @@ -255,7 +252,7 @@ func runCommand(cmd *cobra.Command, _ []string) { return } else if code != "0x" { - outputter.SetCommandResult(&messageResult{ + outputter.SetCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s contracts are already deployed. Aborting.", contractsDeploymentTitle), }) @@ -272,7 +269,15 @@ func runCommand(cmd *cobra.Command, _ []string) { } // populate bridge configuration - consensusConfig.Bridge = rootchainCfg.ToBridgeConfig() + bridgeConfig := rootchainCfg.ToBridgeConfig() + if consensusConfig.Bridge != nil { + // only true if stake-manager-deploy command was executed + // users can still deploy stake manager manually + // only used for e2e tests + bridgeConfig.StakeTokenAddr = consensusConfig.Bridge.StakeTokenAddr + } + + consensusConfig.Bridge = bridgeConfig // set event tracker start blocks for rootchain contract(s) of interest blockNum, err := client.Eth().BlockNumber() @@ -296,7 +301,7 @@ func runCommand(cmd *cobra.Command, _ []string) { return } - outputter.SetCommandResult(&messageResult{ + outputter.SetCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s finished. All contracts are successfully deployed and initialized.", contractsDeploymentTitle), }) @@ -331,6 +336,8 @@ func deployContracts(outputter command.OutputFormatter, client *jsonrpc.Client, rootchainConfig := &polybft.RootchainConfig{ JSONRPCAddr: params.jsonRPCAddress, + // update stake manager address in genesis in case if stake manager was deployed manually + StakeManagerAddress: types.StringToAddress(params.stakeManagerAddr), } tokenContracts := []*contractInfo{} @@ -416,10 +423,6 @@ func deployContracts(outputter command.OutputFormatter, client *jsonrpc.Client, name: erc1155TemplateName, artifact: contractsapi.ChildERC1155, }, - { - name: stakeManagerName, - artifact: contractsapi.StakeManager, - }, { name: customSupernetManagerName, artifact: contractsapi.CustomSupernetManager, @@ -562,7 +565,7 @@ func registerChainOnStakeManager(txRelayer txrelayer.TxRelayer, return 0, fmt.Errorf("failed to encode parameters for registering child chain on supernets. error: %w", err) } - receipt, err := sendTransaction(txRelayer, ethgo.Address(rootchainCfg.StakeManagerAddress), + receipt, err := helper.SendTransaction(txRelayer, ethgo.Address(rootchainCfg.StakeManagerAddress), encoded, checkpointManagerName, deployerKey) if err != nil { return 0, err @@ -624,11 +627,11 @@ func initializeCheckpointManager( addr := ethgo.Address(rootchainCfg.CheckpointManagerAddress) - if _, err = sendTransaction(txRelayer, addr, input, checkpointManagerName, deployerKey); err != nil { + if _, err = helper.SendTransaction(txRelayer, addr, input, checkpointManagerName, deployerKey); err != nil { return err } - cmdOutput.WriteCommandResult(&messageResult{ + cmdOutput.WriteCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, checkpointManagerName), }) @@ -646,12 +649,12 @@ func initializeExitHelper(cmdOutput command.OutputFormatter, return fmt.Errorf("failed to encode parameters for ExitHelper.initialize. error: %w", err) } - if _, err = sendTransaction(txRelayer, ethgo.Address(rootchainConfig.ExitHelperAddress), + if _, err = helper.SendTransaction(txRelayer, ethgo.Address(rootchainConfig.ExitHelperAddress), input, exitHelperName, deployerKey); err != nil { return err } - cmdOutput.WriteCommandResult(&messageResult{ + cmdOutput.WriteCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, exitHelperName), }) @@ -674,13 +677,13 @@ func initializeRootERC20Predicate(cmdOutput command.OutputFormatter, txRelayer t return fmt.Errorf("failed to encode parameters for RootERC20Predicate.initialize. error: %w", err) } - if _, err := sendTransaction(txRelayer, ethgo.Address(rootchainConfig.RootERC20PredicateAddress), + if _, err := helper.SendTransaction(txRelayer, ethgo.Address(rootchainConfig.RootERC20PredicateAddress), input, rootERC20PredicateName, deployerKey); err != nil { return err } cmdOutput.WriteCommandResult( - &messageResult{ + &helper.MessageResult{ Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, rootERC20PredicateName), }) @@ -701,12 +704,12 @@ func initializeRootERC721Predicate(cmdOutput command.OutputFormatter, txRelayer return fmt.Errorf("failed to encode parameters for RootERC721Predicate.initialize. error: %w", err) } - if _, err := sendTransaction(txRelayer, ethgo.Address(rootchainConfig.RootERC721PredicateAddress), + if _, err := helper.SendTransaction(txRelayer, ethgo.Address(rootchainConfig.RootERC721PredicateAddress), input, rootERC721PredicateName, deployerKey); err != nil { return err } - cmdOutput.WriteCommandResult(&messageResult{ + cmdOutput.WriteCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, rootERC721PredicateName), }) @@ -728,42 +731,18 @@ func initializeRootERC1155Predicate(cmdOutput command.OutputFormatter, txRelayer return fmt.Errorf("failed to encode parameters for RootERC1155Predicate.initialize. error: %w", err) } - if _, err := sendTransaction(txRelayer, ethgo.Address(rootchainConfig.RootERC1155PredicateAddress), + if _, err := helper.SendTransaction(txRelayer, ethgo.Address(rootchainConfig.RootERC1155PredicateAddress), input, rootERC1155PredicateName, deployerKey); err != nil { return err } - cmdOutput.WriteCommandResult(&messageResult{ + cmdOutput.WriteCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, rootERC1155PredicateName), }) return nil } -// initializeStakeManager invokes initialize function on StakeManager contract -func initializeStakeManager(cmdOutput command.OutputFormatter, - txRelayer txrelayer.TxRelayer, - rootchainConfig *polybft.RootchainConfig, - deployerKey ethgo.Key) error { - initFn := &contractsapi.InitializeStakeManagerFn{MATIC_: rootchainConfig.RootNativeERC20Address} - - input, err := initFn.EncodeAbi() - if err != nil { - return err - } - - if _, err := sendTransaction(txRelayer, ethgo.Address(rootchainConfig.StakeManagerAddress), - input, stakeManagerName, deployerKey); err != nil { - return err - } - - cmdOutput.WriteCommandResult(&messageResult{ - Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, stakeManagerName), - }) - - return nil -} - // initializeSupernetManager invokes initialize function on CustomSupernetManager contract func initializeSupernetManager(cmdOutput command.OutputFormatter, txRelayer txrelayer.TxRelayer, rootchainConfig *polybft.RootchainConfig, @@ -783,39 +762,18 @@ func initializeSupernetManager(cmdOutput command.OutputFormatter, return err } - if _, err := sendTransaction(txRelayer, ethgo.Address(rootchainConfig.CustomSupernetManagerAddress), + if _, err := helper.SendTransaction(txRelayer, ethgo.Address(rootchainConfig.CustomSupernetManagerAddress), input, customSupernetManagerName, deployerKey); err != nil { return err } - cmdOutput.WriteCommandResult(&messageResult{ + cmdOutput.WriteCommandResult(&helper.MessageResult{ Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, customSupernetManagerName), }) return nil } -// sendTransaction sends provided transaction -func sendTransaction(txRelayer txrelayer.TxRelayer, addr ethgo.Address, input []byte, contractName string, - deployerKey ethgo.Key) (*ethgo.Receipt, error) { - txn := ðgo.Transaction{ - To: &addr, - Input: input, - } - - receipt, err := txRelayer.SendTransaction(txn, deployerKey) - if err != nil { - return nil, fmt.Errorf("failed to send transaction to %s contract (%s). error: %w", - contractName, txn.To.Address(), err) - } - - if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { - return nil, fmt.Errorf("transaction execution failed on %s contract", contractName) - } - - return receipt, nil -} - // validatorSetToABISlice converts given validators to generic map // which is used for ABI encoding validator set being sent to the rootchain contract func validatorSetToABISlice(o command.OutputFormatter, diff --git a/command/rootchain/deploy/deploy_test.go b/command/rootchain/deploy/deploy_test.go index a8486c42c0..ebb83dcd1d 100644 --- a/command/rootchain/deploy/deploy_test.go +++ b/command/rootchain/deploy/deploy_test.go @@ -6,11 +6,13 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" "github.com/umbracle/ethgo/jsonrpc" "github.com/umbracle/ethgo/testutil" "github.com/0xPolygon/polygon-edge/command" "github.com/0xPolygon/polygon-edge/command/rootchain/helper" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" "github.com/0xPolygon/polygon-edge/consensus/polybft/validator" "github.com/0xPolygon/polygon-edge/types" ) @@ -35,7 +37,17 @@ func TestDeployContracts_NoPanics(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + txn := ðgo.Transaction{ + To: nil, // contract deployment + Input: contractsapi.StakeManager.Bytecode, + } + + receipt, err = server.SendTxn(txn) + require.NoError(t, err) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + outputter := command.InitializeOutputter(GetCommand()) + params.stakeManagerAddr = receipt.ContractAddress.String() require.NotPanics(t, func() { _, _, err = deployContracts(outputter, client, 1, []*validator.GenesisValidator{}, context.Background()) diff --git a/command/rootchain/deploy/params.go b/command/rootchain/deploy/params.go index 7ea0405e57..7a1c55cdc0 100644 --- a/command/rootchain/deploy/params.go +++ b/command/rootchain/deploy/params.go @@ -20,6 +20,7 @@ type deployParams struct { rootERC20TokenAddr string rootERC721TokenAddr string rootERC1155TokenAddr string + stakeManagerAddr string isTestMode bool } diff --git a/command/rootchain/deploy/result.go b/command/rootchain/deploy/result.go index da9cd577b8..86ca20b8c9 100644 --- a/command/rootchain/deploy/result.go +++ b/command/rootchain/deploy/result.go @@ -38,16 +38,3 @@ func (r deployContractResult) GetOutput() string { return buffer.String() } - -type messageResult struct { - Message string `json:"message"` -} - -func (r messageResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString(r.Message) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/fund/fund.go b/command/rootchain/fund/fund.go index 47a496bbfa..10d6310680 100644 --- a/command/rootchain/fund/fund.go +++ b/command/rootchain/fund/fund.go @@ -55,17 +55,17 @@ func setFlags(cmd *cobra.Command) { ) cmd.Flags().StringVar( - ¶ms.nativeRootTokenAddr, - helper.NativeRootTokenFlag, + ¶ms.stakeTokenAddr, + helper.StakeTokenFlag, "", - helper.NativeRootTokenFlagDesc, + helper.StakeTokenFlag, ) cmd.Flags().BoolVar( - ¶ms.mintRootToken, - mintRootTokenFlag, + ¶ms.mintStakeToken, + mintStakeTokenFlag, false, - "indicates if root token deployer should mint root tokens to given validators", + "indicates if stake token deployer should mint root tokens to given validators", ) cmd.Flags().StringVar( @@ -92,11 +92,11 @@ func runCommand(cmd *cobra.Command, _ []string) { } var ( - deployerKey ethgo.Key - rootTokenAddr types.Address + deployerKey ethgo.Key + stakeTokenAddr types.Address ) - if params.mintRootToken { + if params.mintStakeToken { deployerKey, err = helper.GetRootchainPrivateKey(params.deployerPrivateKey) if err != nil { outputter.SetError(fmt.Errorf("failed to initialize deployer private key: %w", err)) @@ -104,7 +104,7 @@ func runCommand(cmd *cobra.Command, _ []string) { return } - rootTokenAddr = types.StringToAddress(params.nativeRootTokenAddr) + stakeTokenAddr = types.StringToAddress(params.stakeTokenAddr) } results := make([]command.CommandResult, len(params.addresses)) @@ -135,9 +135,9 @@ func runCommand(cmd *cobra.Command, _ []string) { return fmt.Errorf("failed to fund validator '%s'", validatorAddr) } - if params.mintRootToken { + if params.mintStakeToken { // mint tokens to validator, so he is able to send them - mintTxn, err := helper.CreateMintTxn(validatorAddr, rootTokenAddr, params.amountValues[i]) + mintTxn, err := helper.CreateMintTxn(validatorAddr, stakeTokenAddr, params.amountValues[i]) if err != nil { return fmt.Errorf("failed to create mint native tokens transaction for validator '%s'. err: %w", validatorAddr, err) @@ -156,7 +156,7 @@ func runCommand(cmd *cobra.Command, _ []string) { results[i] = &result{ ValidatorAddr: validatorAddr, TxHash: types.Hash(receipt.TransactionHash), - IsMinted: params.mintRootToken, + IsMinted: params.mintStakeToken, } } diff --git a/command/rootchain/fund/params.go b/command/rootchain/fund/params.go index 749c35132f..4dd10f2edd 100644 --- a/command/rootchain/fund/params.go +++ b/command/rootchain/fund/params.go @@ -9,10 +9,10 @@ import ( ) const ( - addressesFlag = "addresses" - amountsFlag = "amounts" - jsonRPCFlag = "json-rpc" - mintRootTokenFlag = "mint" + addressesFlag = "addresses" + amountsFlag = "amounts" + jsonRPCFlag = "json-rpc" + mintStakeTokenFlag = "mint" ) var ( @@ -21,12 +21,12 @@ var ( ) type fundParams struct { - addresses []string - amounts []string - nativeRootTokenAddr string - deployerPrivateKey string - mintRootToken bool - jsonRPCAddress string + addresses []string + amounts []string + stakeTokenAddr string + deployerPrivateKey string + mintStakeToken bool + jsonRPCAddress string amountValues []*big.Int } diff --git a/command/rootchain/helper/metadata.go b/command/rootchain/helper/metadata.go index 1e37e5b6e3..c5ceeb00e2 100644 --- a/command/rootchain/helper/metadata.go +++ b/command/rootchain/helper/metadata.go @@ -1,6 +1,7 @@ package helper import ( + "bytes" "context" "errors" "fmt" @@ -33,6 +34,8 @@ const ( GenesisPathFlag = "genesis" GenesisPathFlagDesc = "genesis file path, which contains chain configuration" DefaultGenesisPath = "./genesis.json" + StakeTokenFlag = "stake-token" + StakeTokenFlagDesc = "address of ERC20 token used for staking on rootchain" ) var ( @@ -43,6 +46,19 @@ var ( rootchainAccountKey *wallet.Key ) +type MessageResult struct { + Message string `json:"message"` +} + +func (r MessageResult) GetOutput() string { + var buffer bytes.Buffer + + buffer.WriteString(r.Message) + buffer.WriteString("\n") + + return buffer.String() +} + // GetRootchainPrivateKey initializes a private key from provided raw private key func GetRootchainPrivateKey(rawKey string) (ethgo.Key, error) { privateKeyRaw := TestAccountPrivKey @@ -198,7 +214,7 @@ func GetValidatorInfo(validatorAddr ethgo.Address, supernetManagerAddr, stakeMan } // CreateMintTxn encodes parameters for mint function on rootchain token contract -func CreateMintTxn(receiver, rootTokenAddr types.Address, amount *big.Int) (*ethgo.Transaction, error) { +func CreateMintTxn(receiver, erc20TokenAddr types.Address, amount *big.Int) (*ethgo.Transaction, error) { mintFn := &contractsapi.MintRootERC20Fn{ To: receiver, Amount: amount, @@ -209,7 +225,7 @@ func CreateMintTxn(receiver, rootTokenAddr types.Address, amount *big.Int) (*eth return nil, fmt.Errorf("failed to encode provided parameters: %w", err) } - addr := ethgo.Address(rootTokenAddr) + addr := ethgo.Address(erc20TokenAddr) return ðgo.Transaction{ To: &addr, @@ -220,7 +236,7 @@ func CreateMintTxn(receiver, rootTokenAddr types.Address, amount *big.Int) (*eth // CreateApproveERC20Txn sends approve transaction // to ERC20 token for spender so that it is able to spend given tokens func CreateApproveERC20Txn(amount *big.Int, - spender, rootERC20Token types.Address) (*ethgo.Transaction, error) { + spender, erc20TokenAddr types.Address) (*ethgo.Transaction, error) { approveFnParams := &contractsapi.ApproveRootERC20Fn{ Spender: spender, Amount: amount, @@ -231,10 +247,31 @@ func CreateApproveERC20Txn(amount *big.Int, return nil, fmt.Errorf("failed to encode parameters for RootERC20.approve. error: %w", err) } - addr := ethgo.Address(rootERC20Token) + addr := ethgo.Address(erc20TokenAddr) return ðgo.Transaction{ To: &addr, Input: input, }, nil } + +// SendTransaction sends provided transaction +func SendTransaction(txRelayer txrelayer.TxRelayer, addr ethgo.Address, input []byte, contractName string, + deployerKey ethgo.Key) (*ethgo.Receipt, error) { + txn := ðgo.Transaction{ + To: &addr, + Input: input, + } + + receipt, err := txRelayer.SendTransaction(txn, deployerKey) + if err != nil { + return nil, fmt.Errorf("failed to send transaction to %s contract (%s). error: %w", + contractName, txn.To.Address(), err) + } + + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return nil, fmt.Errorf("transaction execution failed on %s contract", contractName) + } + + return receipt, nil +} diff --git a/command/rootchain/staking/params.go b/command/rootchain/staking/params.go index 7913068a7c..673fa01a3b 100644 --- a/command/rootchain/staking/params.go +++ b/command/rootchain/staking/params.go @@ -12,13 +12,13 @@ import ( var supernetIDFlag = "supernet-id" type stakeParams struct { - accountDir string - accountConfig string - stakeManagerAddr string - nativeRootTokenAddr string - jsonRPC string - supernetID int64 - amount string + accountDir string + accountConfig string + stakeManagerAddr string + stakeTokenAddr string + jsonRPC string + supernetID int64 + amount string amountValue *big.Int } diff --git a/command/rootchain/staking/stake.go b/command/rootchain/staking/stake.go index 9dd2a3ba92..c5f6b35197 100644 --- a/command/rootchain/staking/stake.go +++ b/command/rootchain/staking/stake.go @@ -72,10 +72,10 @@ func setFlags(cmd *cobra.Command) { ) cmd.Flags().StringVar( - ¶ms.nativeRootTokenAddr, - rootHelper.NativeRootTokenFlag, + ¶ms.stakeTokenAddr, + rootHelper.StakeTokenFlag, "", - rootHelper.NativeRootTokenFlagDesc, + rootHelper.StakeTokenFlagDesc, ) cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) @@ -108,7 +108,7 @@ func runCommand(cmd *cobra.Command, _ []string) error { } approveTxn, err := rootHelper.CreateApproveERC20Txn(params.amountValue, - types.StringToAddress(params.stakeManagerAddr), types.StringToAddress(params.nativeRootTokenAddr)) + types.StringToAddress(params.stakeManagerAddr), types.StringToAddress(params.stakeTokenAddr)) if err != nil { return err } diff --git a/command/rootchain/supernet/stakemanager/params.go b/command/rootchain/supernet/stakemanager/params.go new file mode 100644 index 0000000000..c62aa0d9c0 --- /dev/null +++ b/command/rootchain/supernet/stakemanager/params.go @@ -0,0 +1,34 @@ +package stakemanager + +import ( + "fmt" + "os" + + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" +) + +type stakeManagerDeployParams struct { + accountDir string + accountConfig string + privateKey string + jsonRPC string + genesisPath string + stakeTokenAddress string + isTestMode bool +} + +func (s *stakeManagerDeployParams) validateFlags() error { + if !s.isTestMode && s.privateKey == "" { + return sidechainHelper.ValidateSecretFlags(s.accountDir, s.accountConfig) + } + + if _, err := os.Stat(s.genesisPath); err != nil { + return fmt.Errorf("provided genesis path '%s' is invalid. Error: %w ", s.genesisPath, err) + } + + // validate jsonrpc address + _, err := helper.ParseJSONRPCAddress(s.jsonRPC) + + return err +} diff --git a/command/rootchain/supernet/stakemanager/stake_manager_deploy.go b/command/rootchain/supernet/stakemanager/stake_manager_deploy.go new file mode 100644 index 0000000000..55bdeaf742 --- /dev/null +++ b/command/rootchain/supernet/stakemanager/stake_manager_deploy.go @@ -0,0 +1,266 @@ +package stakemanager + +import ( + "fmt" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/command/polybftsecrets" + rootHelper "github.com/0xPolygon/polygon-edge/command/rootchain/helper" + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + "golang.org/x/sync/errgroup" +) + +var params stakeManagerDeployParams + +func GetCommand() *cobra.Command { + registerCmd := &cobra.Command{ + Use: "stake-manager-deploy", + Short: "Command for deploying stake manager contract on rootchain", + PreRunE: runPreRun, + RunE: runCommand, + } + + setFlags(registerCmd) + + return registerCmd +} + +func runPreRun(cmd *cobra.Command, _ []string) error { + params.jsonRPC = helper.GetJSONRPCAddress(cmd) + + return params.validateFlags() +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.accountDir, + polybftsecrets.AccountDirFlag, + "", + polybftsecrets.AccountDirFlagDesc, + ) + + cmd.Flags().StringVar( + ¶ms.accountConfig, + polybftsecrets.AccountConfigFlag, + "", + polybftsecrets.AccountConfigFlagDesc, + ) + + cmd.Flags().StringVar( + ¶ms.privateKey, + polybftsecrets.PrivateKeyFlag, + "", + polybftsecrets.PrivateKeyFlagDesc, + ) + + cmd.Flags().StringVar( + ¶ms.genesisPath, + rootHelper.GenesisPathFlag, + rootHelper.DefaultGenesisPath, + rootHelper.GenesisPathFlagDesc, + ) + + cmd.Flags().StringVar( + ¶ms.stakeTokenAddress, + rootHelper.StakeTokenFlag, + "", + rootHelper.StakeTokenFlagDesc, + ) + + cmd.Flags().BoolVar( + ¶ms.isTestMode, + rootHelper.TestModeFlag, + false, + "indicates if command is run in test mode. If test mode is used contract will be deployed using test account "+ + "and a test stake ERC20 token will be deployed to be used for staking", + ) + + cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) + cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountConfigFlag) + cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountDirFlag) + cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, polybftsecrets.PrivateKeyFlag) + cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, polybftsecrets.AccountConfigFlag) + cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, polybftsecrets.AccountDirFlag) + cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, rootHelper.StakeTokenFlag) + + helper.RegisterJSONRPCFlag(cmd) +} + +func runCommand(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + var ( + deployerKey ethgo.Key + err error + ) + + if params.isTestMode { + deployerKey, err = rootHelper.GetRootchainPrivateKey("") + } else { + deployerKey, err = rootHelper.GetECDSAKey(params.privateKey, params.accountDir, params.accountConfig) + } + + if err != nil { + return err + } + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC)) + if err != nil { + return fmt.Errorf("deploying stake manager failed: %w", err) + } + + if params.isTestMode { + // fund deployer so that he can deploy contracts + deployerAddr := deployerKey.Address() + txn := ðgo.Transaction{To: &deployerAddr, Value: ethgo.Ether(1)} + + if _, err = txRelayer.SendTransactionLocal(txn); err != nil { + return err + } + } + + var ( + stakeManagerAddress types.Address + stakeTokenAddress = types.StringToAddress(params.stakeTokenAddress) + g, ctx = errgroup.WithContext(cmd.Context()) + ) + + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + // deploy stake manager + contractAddress, err := deployContract(txRelayer, deployerKey, + contractsapi.StakeManager, "StakeManager") + if err != nil { + return err + } + + outputter.WriteCommandResult(&rootHelper.MessageResult{ + Message: "[STAKEMANAGER - DEPLOY] Successfully deployed StakeManager contract", + }) + + stakeManagerAddress = contractAddress + + return nil + } + }) + + if params.isTestMode { + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + // this is only used for testing, we are deploying a test ERC20 token to use for staking + // this should not be used in production + // deploy stake manager + contractAddress, err := deployContract(txRelayer, deployerKey, + contractsapi.RootERC20, "MockERC20StakeToken") + if err != nil { + return err + } + + outputter.WriteCommandResult(&rootHelper.MessageResult{ + Message: "[STAKEMANAGER - DEPLOY] Successfully deployed MockERC20StakeToken contract", + }) + + stakeTokenAddress = contractAddress + + return nil + } + }) + } + + if err := g.Wait(); err != nil { + return err + } + + // initialize stake manager + if err := initializeStakeManager(outputter, txRelayer, + stakeManagerAddress, stakeTokenAddress, deployerKey); err != nil { + return fmt.Errorf("could not initialize stake manager contract. Error: %w", err) + } + + // update stake manager address in genesis + chainConfig, err := chain.ImportFromFile(params.genesisPath) + if err != nil { + return fmt.Errorf("failed to read chain configuration: %w", err) + } + + consensusConfig, err := polybft.GetPolyBFTConfig(chainConfig) + if err != nil { + return fmt.Errorf("failed to retrieve consensus configuration: %w", err) + } + + if consensusConfig.Bridge == nil { + consensusConfig.Bridge = &polybft.BridgeConfig{} + } + + consensusConfig.Bridge.StakeManagerAddr = stakeManagerAddress + consensusConfig.Bridge.StakeTokenAddr = stakeTokenAddress + + // write updated chain configuration + chainConfig.Params.Engine[polybft.ConsensusName] = consensusConfig + + if err := helper.WriteGenesisConfigToDisk(chainConfig, params.genesisPath); err != nil { + return fmt.Errorf("failed to save chain configuration bridge data: %w", err) + } + + return nil +} + +// initializeStakeManager invokes initialize function on StakeManager contract +func initializeStakeManager(cmdOutput command.OutputFormatter, + txRelayer txrelayer.TxRelayer, + stakeManagerAddress types.Address, + stakeTokenAddress types.Address, + deployerKey ethgo.Key) error { + initFn := &contractsapi.InitializeStakeManagerFn{MATIC_: stakeTokenAddress} + + input, err := initFn.EncodeAbi() + if err != nil { + return err + } + + if _, err := rootHelper.SendTransaction(txRelayer, ethgo.Address(stakeManagerAddress), + input, "StakeManager", deployerKey); err != nil { + return err + } + + cmdOutput.WriteCommandResult(&rootHelper.MessageResult{ + Message: "[STAKEMANAGER - DEPLOY] StakeManager contract is initialized", + }) + + return nil +} + +func deployContract(txRelayer txrelayer.TxRelayer, deployerKey ethgo.Key, + artifact *artifact.Artifact, contractName string) (types.Address, error) { + txn := ðgo.Transaction{ + To: nil, // contract deployment + Input: artifact.Bytecode, + } + + receipt, err := txRelayer.SendTransaction(txn, deployerKey) + if err != nil { + return types.ZeroAddress, + fmt.Errorf("failed sending %s contract deploy transaction: %w", contractName, err) + } + + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return types.ZeroAddress, fmt.Errorf("deployment of %s contract failed", contractName) + } + + return types.Address(receipt.ContractAddress), nil +} diff --git a/consensus/polybft/README.md b/consensus/polybft/README.md index b48ab7bb9f..0fae04b420 100644 --- a/consensus/polybft/README.md +++ b/consensus/polybft/README.md @@ -48,17 +48,28 @@ It has a native support for running bridge, which enables running cross-chain tr $ polygon-edge rootchain server ``` -5. Deploy and initialize rootchain contracts - this command deploys rootchain smart contracts and initializes them. It also updates genesis configuration with rootchain contract addresses and rootchain default sender address. +5. Deploy StakeManager - if not already deployed to rootchain. Command has a test flag used only in testing purposes which would deploy a mock ERC20 token which would be used for staking. If not used for testing, stake-token flag should be provided: + ``bash + $ polygon-edge polybft stake-manager-deploy \ + --deployer-key \ + [--genesis ./genesis.json] \ + [--json-rpc http://127.0.0.1:8545] \ + [--stake-token 0xaddressOfStakeToken] \ + [--test] + ``` + +6. Deploy and initialize rootchain contracts - this command deploys rootchain smart contracts and initializes them. It also updates genesis configuration with rootchain contract addresses and rootchain default sender address. ```bash $ polygon-edge rootchain deploy \ --deployer-key \ + --stake-manager \ [--genesis ./genesis.json] \ [--json-rpc http://127.0.0.1:8545] \ [--test] ``` -6. Fund validators on rootchain - in order for validators to be able to send transactions to Ethereum, they need to be funded in order to be able to cover gas cost. **This command is for testing purposes only.** +7. Fund validators on rootchain - in order for validators to be able to send transactions to Ethereum, they need to be funded in order to be able to cover gas cost. **This command is for testing purposes only.** ```bash $ polygon-edge rootchain fund \ @@ -66,29 +77,29 @@ It has a native support for running bridge, which enables running cross-chain tr --amounts 200000000000000000000 ``` -7. Whitelist validators on rootchain - in order for validators to be able to be registered on the SupernetManager contract on rootchain. Note that only deployer of SupernetManager contract (the one who run the deploy command) can whitelist validators on rootchain. He can use either its hex encoded private key, or data-dir flag if he has secerets initialized: +8. Whitelist validators on rootchain - in order for validators to be able to be registered on the SupernetManager contract on rootchain. Note that only deployer of SupernetManager contract (the one who run the deploy command) can whitelist validators on rootchain. He can use either its hex encoded private key, or data-dir flag if he has secerets initialized: ```bash $ polygon-edge polybft whitelist-validators --private-key \ --addresses --supernet-manager ``` -8. Register validators on rootchain - each validator registers itself on SupernetManager. **This command is for testing purposes only.** +9. Register validators on rootchain - each validator registers itself on SupernetManager. **This command is for testing purposes only.** ```bash $ polygon-edge polybft register-validator --data-dir ./test-chain-1 \ --supernet-manager ``` -9. Initial staking on rootchain - each validator needs to do initial staking on rootchain (StakeManager) contract. **This command is for testing purposes only.** +10. Initial staking on rootchain - each validator needs to do initial staking on rootchain (StakeManager) contract. **This command is for testing purposes only.** ```bash $ polygon-edge polybft stake --data-dir ./test-chain-1 --supernet-id \ --amount \ - --stake-manager --native-root-token + --stake-manager --stake-token ``` -10. Finalize genesis validator set on rootchain (SupernetManager) contract. This is done after all validators from genesis do initial staking on rootchain, and it's a final step that is required before starting the child chain. This needs to be done by the deployer of SupernetManager contract (the user that run the deploy command). He can use either its hex encoded private key, or data-dir flag if he has secerets initialized. If enable-staking flag is provided, validators will be able to continue staking on rootchain. If not, genesis validators will not be able update its stake or unstake, nor will newly registered validators after genesis will be able to stake tokens on the rootchain. Enabling of staking can be done through this command, or later after the child chain starts. +11. Finalize genesis validator set on rootchain (SupernetManager) contract. This is done after all validators from genesis do initial staking on rootchain, and it's a final step that is required before starting the child chain. This needs to be done by the deployer of SupernetManager contract (the user that run the deploy command). He can use either its hex encoded private key, or data-dir flag if he has secerets initialized. If enable-staking flag is provided, validators will be able to continue staking on rootchain. If not, genesis validators will not be able update its stake or unstake, nor will newly registered validators after genesis will be able to stake tokens on the rootchain. Enabling of staking can be done through this command, or later after the child chain starts. ```bash $ polygon-edge polybft supernet --private-key \ @@ -98,7 +109,7 @@ It has a native support for running bridge, which enables running cross-chain tr --finalize-genesis --enable-staking ``` -11. Run (child chain) cluster, consisting of 4 Edge clients in this particular example +12. Run (child chain) cluster, consisting of 4 Edge clients in this particular example ```bash $ polygon-edge server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :5001 --libp2p :30301 --jsonrpc :9545 \ diff --git a/consensus/polybft/polybft_config.go b/consensus/polybft/polybft_config.go index 84f7629aaa..23de6f001a 100644 --- a/consensus/polybft/polybft_config.go +++ b/consensus/polybft/polybft_config.go @@ -93,6 +93,8 @@ type BridgeConfig struct { RootERC1155PredicateAddr types.Address `json:"erc1155PredicateAddress"` CustomSupernetManagerAddr types.Address `json:"customSupernetManagerAddr"` StakeManagerAddr types.Address `json:"stakeManagerAddr"` + // only populated if stake-manager-deploy command is executed, and used for e2e tests + StakeTokenAddr types.Address `json:"stakeTokenAddr,omitempty"` JSONRPCEndpoint string `json:"jsonRPCEndpoint"` EventTrackerStartBlocks map[types.Address]uint64 `json:"eventTrackerStartBlocks"` @@ -122,6 +124,7 @@ type RootchainConfig struct { ERC1155TemplateAddress types.Address CustomSupernetManagerAddress types.Address StakeManagerAddress types.Address + StakeTokenAddress types.Address } // ToBridgeConfig creates BridgeConfig instance diff --git a/e2e-polybft/e2e/bridge_test.go b/e2e-polybft/e2e/bridge_test.go index 661fb97c25..f9c03b9039 100644 --- a/e2e-polybft/e2e/bridge_test.go +++ b/e2e-polybft/e2e/bridge_test.go @@ -748,7 +748,7 @@ func TestE2E_Bridge_ChangeVotingPower(t *testing.T) { validatorSrv := cluster.Servers[idx] // fund validators (send accumulated rewards amount) - require.NoError(t, validatorSrv.RootchainFund(polybftCfg.Bridge.RootNativeERC20Addr, validator.WithdrawableRewards)) + require.NoError(t, validatorSrv.RootchainFund(polybftCfg.Bridge.StakeTokenAddr, validator.WithdrawableRewards)) // stake previously funded amount require.NoError(t, validatorSrv.Stake(polybftCfg, validator.WithdrawableRewards)) @@ -780,7 +780,7 @@ func TestE2E_Bridge_ChangeVotingPower(t *testing.T) { currentExtra, err := polybft.GetIbftExtra(currentBlock.ExtraData) require.NoError(t, err) - targetEpoch := currentExtra.Checkpoint.EpochNumber + 1 + targetEpoch := currentExtra.Checkpoint.EpochNumber + 2 require.NoError(t, waitForRootchainEpoch(targetEpoch, 2*time.Minute, rootRelayer, polybftCfg.Bridge.CheckpointManagerAddr)) diff --git a/e2e-polybft/e2e/consensus_test.go b/e2e-polybft/e2e/consensus_test.go index 5e4480aba4..85a6ef0533 100644 --- a/e2e-polybft/e2e/consensus_test.go +++ b/e2e-polybft/e2e/consensus_test.go @@ -178,12 +178,12 @@ func TestE2E_Consensus_RegisterValidator(t *testing.T) { initialBalance := ethgo.Ether(500) // fund first new validator - err = cluster.Bridge.FundValidators(polybftConfig.Bridge.RootNativeERC20Addr, + err = cluster.Bridge.FundValidators(polybftConfig.Bridge.StakeTokenAddr, []string{path.Join(cluster.Config.TmpDir, firstValidatorSecrets)}, []*big.Int{initialBalance}) require.NoError(t, err) // fund second new validator - err = cluster.Bridge.FundValidators(polybftConfig.Bridge.RootNativeERC20Addr, + err = cluster.Bridge.FundValidators(polybftConfig.Bridge.StakeTokenAddr, []string{path.Join(cluster.Config.TmpDir, secondValidatorSecrets)}, []*big.Int{initialBalance}) require.NoError(t, err) diff --git a/e2e-polybft/framework/test-bridge.go b/e2e-polybft/framework/test-bridge.go index 51d4db030b..119bb6a98c 100644 --- a/e2e-polybft/framework/test-bridge.go +++ b/e2e-polybft/framework/test-bridge.go @@ -271,9 +271,15 @@ func (t *TestBridge) cmdRun(args ...string) error { // deployRootchainContracts deploys and initializes rootchain contracts func (t *TestBridge) deployRootchainContracts(genesisPath string) error { + polybftConfig, err := polybft.LoadPolyBFTConfig(genesisPath) + if err != nil { + return err + } + args := []string{ "rootchain", "deploy", + "--stake-manager", polybftConfig.Bridge.StakeManagerAddr.String(), "--genesis", genesisPath, "--test", } @@ -300,7 +306,7 @@ func (t *TestBridge) fundRootchainValidators(polybftConfig polybft.PolyBFTConfig balances[i] = polybftConfig.InitialValidatorSet[i].Balance } - if err := t.FundValidators(polybftConfig.Bridge.RootNativeERC20Addr, + if err := t.FundValidators(polybftConfig.Bridge.StakeTokenAddr, secrets, balances); err != nil { return fmt.Errorf("failed to fund validators on the rootchain: %w", err) } @@ -392,7 +398,7 @@ func (t *TestBridge) initialStakingOfGenesisValidators(polybftConfig polybft.Pol "--" + polybftsecrets.AccountDirFlag, path.Join(t.clusterConfig.TmpDir, secret), "--amount", polybftConfig.InitialValidatorSet[i].Stake.String(), "--supernet-id", strconv.FormatInt(polybftConfig.SupernetID, 10), - "--native-root-token", polybftConfig.Bridge.RootNativeERC20Addr.String(), + "--stake-token", polybftConfig.Bridge.StakeTokenAddr.String(), } if err := t.cmdRun(args...); err != nil { @@ -428,7 +434,7 @@ func (t *TestBridge) finalizeGenesis(genesisPath string, polybftConfig polybft.P } // FundValidators sends tokens to a rootchain validators -func (t *TestBridge) FundValidators(tookenAddress types.Address, secretsPaths []string, amounts []*big.Int) error { +func (t *TestBridge) FundValidators(tokenAddress types.Address, secretsPaths []string, amounts []*big.Int) error { if len(secretsPaths) != len(amounts) { return errors.New("expected the same length of secrets paths and amounts") } @@ -436,7 +442,7 @@ func (t *TestBridge) FundValidators(tookenAddress types.Address, secretsPaths [] args := []string{ "rootchain", "fund", - "--native-root-token", tookenAddress.String(), + "--stake-token", tokenAddress.String(), "--mint", } @@ -461,3 +467,19 @@ func (t *TestBridge) FundValidators(tookenAddress types.Address, secretsPaths [] return nil } + +func (t *TestBridge) deployStakeManager(genesisPath string) error { + args := []string{ + "polybft", + "stake-manager-deploy", + "--jsonrpc", t.JSONRPCAddr(), + "--genesis", genesisPath, + "--test", + } + + if err := t.cmdRun(args...); err != nil { + return fmt.Errorf("failed to deploy stake manager contract: %w", err) + } + + return nil +} diff --git a/e2e-polybft/framework/test-cluster.go b/e2e-polybft/framework/test-cluster.go index fd50ff9a5e..47526249e1 100644 --- a/e2e-polybft/framework/test-cluster.go +++ b/e2e-polybft/framework/test-cluster.go @@ -570,8 +570,12 @@ func NewTestCluster(t *testing.T, validatorsCount int, opts ...ClusterOption) *T cluster.Bridge, err = NewTestBridge(t, cluster.Config) require.NoError(t, err) + // deploy stake manager contract + err := cluster.Bridge.deployStakeManager(genesisPath) + require.NoError(t, err) + // deploy rootchain contracts - err := cluster.Bridge.deployRootchainContracts(genesisPath) + err = cluster.Bridge.deployRootchainContracts(genesisPath) require.NoError(t, err) polybftConfig, err := polybft.LoadPolyBFTConfig(genesisPath) diff --git a/e2e-polybft/framework/test-server.go b/e2e-polybft/framework/test-server.go index ab5668e946..bb655c2156 100644 --- a/e2e-polybft/framework/test-server.go +++ b/e2e-polybft/framework/test-server.go @@ -198,14 +198,14 @@ func (t *TestServer) Stop() { } // RootchainFund funds given validator account on the rootchain -func (t *TestServer) RootchainFund(rootNativeERC20Addr types.Address, amount *big.Int) error { +func (t *TestServer) RootchainFund(stakeToken types.Address, amount *big.Int) error { args := []string{ "rootchain", "fund", "--addresses", t.address.String(), "--amounts", amount.String(), "--json-rpc", t.BridgeJSONRPCAddr(), - "--native-root-token", rootNativeERC20Addr.String(), + "--stake-token", stakeToken.String(), "--mint", } @@ -226,7 +226,7 @@ func (t *TestServer) Stake(polybftConfig polybft.PolyBFTConfig, amount *big.Int) "--" + polybftsecrets.AccountDirFlag, t.config.DataDir, "--amount", amount.String(), "--supernet-id", strconv.FormatInt(polybftConfig.SupernetID, 10), - "--native-root-token", polybftConfig.Bridge.RootNativeERC20Addr.String(), + "--stake-token", polybftConfig.Bridge.StakeTokenAddr.String(), } return runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("stake"))