Skip to content

Commit

Permalink
Allow providing mintable native token owner account (#1603)
Browse files Browse the repository at this point in the history
* Allow providing mintable native token owner account

* Fix test

* Fix

* Add extractNativeTokenMetadata unit test
  • Loading branch information
Stefan-Ethernal committed Jun 12, 2023
1 parent 827b08b commit c5ebc40
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 35 deletions.
3 changes: 2 additions & 1 deletion command/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ func setFlags(cmd *cobra.Command) {
&params.nativeTokenConfigRaw,
nativeTokenConfigFlag,
"",
"configuration of native token in format <name:symbol:decimals count:mintable flag>",
"native token configuration, provided in the following format: "+
"<name:symbol:decimals count:mintable flag:[mintable token owner address]>",
)

cmd.Flags().StringVar(
Expand Down
23 changes: 20 additions & 3 deletions command/genesis/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const (
defaultNativeTokenName = "Polygon"
defaultNativeTokenSymbol = "MATIC"
defaultNativeTokenDecimals = uint8(18)
nativeTokenParamsNumber = 4
minNativeTokenParamsNumber = 4
)

// Legacy flags that need to be preserved for running clients
Expand All @@ -60,7 +60,7 @@ var (
errUnsupportedConsensus = errors.New("specified consensusRaw not supported")
errInvalidEpochSize = errors.New("epoch size must be greater than 1")
errInvalidTokenParams = errors.New("native token params were not submitted in proper format " +
"(<name:symbol:decimals count:mintable flag>)")
"(<name:symbol:decimals count:mintable flag:[mintable token owner address]>)")
errRewardWalletAmountZero = errors.New("reward wallet amount can not be zero or negative")
)

Expand Down Expand Up @@ -481,41 +481,58 @@ func (p *genesisParams) extractNativeTokenMetadata() error {
Symbol: defaultNativeTokenSymbol,
Decimals: defaultNativeTokenDecimals,
IsMintable: false,
Owner: types.ZeroAddress,
}

return nil
}

params := strings.Split(p.nativeTokenConfigRaw, ":")
if len(params) != nativeTokenParamsNumber {
if len(params) < minNativeTokenParamsNumber {
return errInvalidTokenParams
}

// name
name := strings.TrimSpace(params[0])
if name == "" {
return errInvalidTokenParams
}

// symbol
symbol := strings.TrimSpace(params[1])
if symbol == "" {
return errInvalidTokenParams
}

// decimals
decimals, err := strconv.ParseUint(strings.TrimSpace(params[2]), 10, 8)
if err != nil || decimals > math.MaxUint8 {
return errInvalidTokenParams
}

// is mintable native token used
isMintable, err := strconv.ParseBool(strings.TrimSpace(params[3]))
if err != nil {
return errInvalidTokenParams
}

// in case it is mintable native token, it is expected to have 5 parameters provided
if isMintable && len(params) != minNativeTokenParamsNumber+1 {
return errInvalidTokenParams
}

// owner address
owner := types.ZeroAddress
if isMintable {
owner = types.StringToAddress(strings.TrimSpace(params[4]))
}

p.nativeTokenConfig = &polybft.TokenConfig{
Name: name,
Symbol: symbol,
Decimals: uint8(decimals),
IsMintable: isMintable,
Owner: owner,
}

return nil
Expand Down
118 changes: 118 additions & 0 deletions command/genesis/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package genesis

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/0xPolygon/polygon-edge/consensus/polybft"
"github.com/0xPolygon/polygon-edge/types"
)

func Test_extractNativeTokenMetadata(t *testing.T) {
t.Parallel()

cases := []struct {
name string
rawConfig string
expectedCfg *polybft.TokenConfig
expectErr bool
}{
{
name: "default token config",
rawConfig: "",
expectedCfg: &polybft.TokenConfig{
Name: defaultNativeTokenName,
Symbol: defaultNativeTokenSymbol,
Decimals: defaultNativeTokenDecimals,
IsMintable: false,
Owner: types.ZeroAddress,
},
expectErr: false,
},
{
name: "not enough params provided",
rawConfig: "Test:TST:18",
expectErr: true,
},
{
name: "empty name provided",
rawConfig: ":TST:18:false",
expectErr: true,
},
{
name: "empty symbol provided",
rawConfig: "Test::18:false",
expectErr: true,
},
{
name: "invalid decimals number provided",
rawConfig: "Test:TST:9999999999999999999999999999999999999999999999999999999999:false",
expectErr: true,
},
{
name: "invalid mintable flag provided",
rawConfig: "Test:TST:18:bar",
expectErr: true,
},
{
name: "mintable token not enough params provided",
rawConfig: "Test:TST:18:true",
expectErr: true,
},
{
name: "non-mintable valid config",
rawConfig: "MyToken:MTK:9:false",
expectedCfg: &polybft.TokenConfig{
Name: "MyToken",
Symbol: "MTK",
Decimals: 9,
IsMintable: false,
Owner: types.ZeroAddress,
},
expectErr: false,
},
{
name: "non-mintable token config, owner provided but ignored",
rawConfig: "MyToken:MTK:9:false:0x123456789",
expectedCfg: &polybft.TokenConfig{
Name: "MyToken",
Symbol: "MTK",
Decimals: 9,
IsMintable: false,
Owner: types.ZeroAddress,
},
expectErr: false,
},
{
name: "mintable token valid config",
rawConfig: "MyMintToken:MMTK:9:true:0x123456789",
expectedCfg: &polybft.TokenConfig{
Name: "MyMintToken",
Symbol: "MMTK",
Decimals: 9,
IsMintable: true,
Owner: types.StringToAddress("0x123456789"),
},
expectErr: false,
},
}

for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()

p := &genesisParams{nativeTokenConfigRaw: c.rawConfig}
err := p.extractNativeTokenMetadata()

if c.expectErr {
require.Error(t, err)
require.Nil(t, p.nativeTokenConfig)
} else {
require.NoError(t, err)
require.Equal(t, c.expectedCfg, p.nativeTokenConfig)
}
})
}
}
2 changes: 1 addition & 1 deletion command/genesis/polybft_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (p *genesisParams) generatePolyBftChainConfig(o command.OutputFormatter) er
SprintSize: p.sprintSize,
EpochReward: p.epochReward,
// use 1st account as governance address
Governance: initialValidators[0].Address,
Governance: types.ZeroAddress,
InitialTrieRoot: types.StringToHash(p.initialStateRoot),
NativeTokenConfig: p.nativeTokenConfig,
MinValidatorSetSize: p.minNumValidators,
Expand Down
2 changes: 1 addition & 1 deletion consensus/polybft/polybft.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ func GenesisPostHookFactory(config *chain.Chain, engineName string) func(txn *st
// initialize NativeERC20Mintable SC
params := &contractsapi.InitializeNativeERC20MintableFn{
Predicate_: contracts.ChildERC20PredicateContract,
Owner_: polyBFTConfig.Governance,
Owner_: polyBFTConfig.NativeTokenConfig.Owner,
RootToken_: polyBFTConfig.Bridge.RootNativeERC20Addr,
Name_: polyBFTConfig.NativeTokenConfig.Name,
Symbol_: polyBFTConfig.NativeTokenConfig.Symbol,
Expand Down
9 changes: 5 additions & 4 deletions consensus/polybft/polybft_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,11 @@ func (r *RootchainConfig) ToBridgeConfig() *BridgeConfig {

// TokenConfig is the configuration of native token used by edge network
type TokenConfig struct {
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals uint8 `json:"decimals"`
IsMintable bool `json:"isMintable"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals uint8 `json:"decimals"`
IsMintable bool `json:"isMintable"`
Owner types.Address `json:"owner"`
}

type RewardsConfig struct {
Expand Down
2 changes: 1 addition & 1 deletion e2e-polybft/e2e/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ func TestE2E_Bridge_ChildChainMintableTokensTransfer(t *testing.T) {
cluster := framework.NewTestCluster(t, 5,
framework.WithNumBlockConfirmations(0),
framework.WithEpochSize(epochSize),
framework.WithNativeTokenConfig("Mintable Edge Coin:MEC:18:true"),
framework.WithNativeTokenConfig(fmt.Sprintf("Mintable Edge Coin:MEC:18:true:%s", adminAddr)),
framework.WithBridgeAllowListAdmin(adminAddr),
framework.WithBridgeBlockListAdmin(adminAddr),
framework.WithPremine(append(depositors, adminAddr)...)) //nolint:makezero
Expand Down
43 changes: 19 additions & 24 deletions e2e-polybft/e2e/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo"
"github.com/umbracle/ethgo/abi"
"github.com/umbracle/ethgo/wallet"

"github.com/0xPolygon/polygon-edge/command/genesis"
"github.com/0xPolygon/polygon-edge/command/sidechain"
Expand Down Expand Up @@ -428,7 +429,6 @@ func TestE2E_Consensus_MintableERC20NativeToken(t *testing.T) {
const (
validatorCount = 5
epochSize = 5
minterPath = "test-chain-1"

tokenName = "Edge Coin"
tokenSymbol = "EDGE"
Expand All @@ -453,33 +453,28 @@ func TestE2E_Consensus_MintableERC20NativeToken(t *testing.T) {
}

validatorsAddrs := make([]types.Address, validatorCount)
initialStake := ethgo.Gwei(1)
initialBalance := ethgo.Ether(100000)
initValidatorsBalance := ethgo.Ether(1)
initMinterBalance := ethgo.Ether(100000)

minter, err := wallet.GenerateKey()
require.NoError(t, err)

cluster := framework.NewTestCluster(t,
validatorCount,
framework.WithNativeTokenConfig(fmt.Sprintf("%s:%s:%d:true", tokenName, tokenSymbol, decimals)),
framework.WithNativeTokenConfig(
fmt.Sprintf("%s:%s:%d:true:%s", tokenName, tokenSymbol, decimals, minter.Address())),
framework.WithEpochSize(epochSize),
framework.WithSecretsCallback(func(addrs []types.Address, config *framework.TestClusterConfig) {
config.Premine = append(config.Premine, fmt.Sprintf("%s:%d", minter.Address(), initMinterBalance))
for i, addr := range addrs {
// first one is the owner of the NativeERC20Mintable SC
// and it should have premine set to default 1M tokens
// (it is going to send mint transactions to all the other validators)
if i > 0 {
// premine other validators with as minimum stake as possible just to ensure liveness of consensus protocol
config.Premine = append(config.Premine, fmt.Sprintf("%s:%d", addr, initialBalance))
config.StakeAmounts = append(config.StakeAmounts, fmt.Sprintf("%s:%d", addr, initialStake))
}
config.Premine = append(config.Premine, fmt.Sprintf("%s:%d", addr, initValidatorsBalance))
config.StakeAmounts = append(config.StakeAmounts, fmt.Sprintf("%s:%d", addr, initValidatorsBalance))
validatorsAddrs[i] = addr
}
}))
defer cluster.Stop()

minterSrv := cluster.Servers[0]
targetJSONRPC := minterSrv.JSONRPC()

minterAcc, err := sidechain.GetAccountFromDir(minterSrv.DataDir())
require.NoError(t, err)
targetJSONRPC := cluster.Servers[0].JSONRPC()

cluster.WaitForReady(t)

Expand Down Expand Up @@ -507,8 +502,7 @@ func TestE2E_Consensus_MintableERC20NativeToken(t *testing.T) {
mintAmount := ethgo.Ether(10)

// make sure minter account can mint tokens
// (first account, which is minter sends mint transactions to all the other validators)
for _, addr := range validatorsAddrs[1:] {
for _, addr := range validatorsAddrs {
balance, err := targetJSONRPC.Eth().GetBalance(ethgo.Address(addr), ethgo.Latest)
require.NoError(t, err)
t.Logf("Pre-mint balance: %v=%d\n", addr, balance)
Expand All @@ -520,26 +514,27 @@ func TestE2E_Consensus_MintableERC20NativeToken(t *testing.T) {
&ethgo.Transaction{
To: &nativeTokenAddr,
Input: mintInput,
}, minterAcc.Ecdsa)
}, minter)
require.NoError(t, err)
require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status)

balance, err = targetJSONRPC.Eth().GetBalance(ethgo.Address(addr), ethgo.Latest)
require.NoError(t, err)

t.Logf("Post-mint balance: %v=%d\n", addr, balance)
require.Equal(t, new(big.Int).Add(mintAmount, initialBalance), balance)
require.Equal(t, new(big.Int).Add(initValidatorsBalance, mintAmount), balance)
}

minterBalance, err := targetJSONRPC.Eth().GetBalance(minterAcc.Ecdsa.Address(), ethgo.Latest)
// assert that minter balance remained the same
minterBalance, err := targetJSONRPC.Eth().GetBalance(minter.Address(), ethgo.Latest)
require.NoError(t, err)
require.Equal(t, ethgo.Ether(1e6), minterBalance)
require.Equal(t, initMinterBalance, minterBalance)

// try sending mint transaction from non minter account and make sure it would fail
nonMinterAcc, err := sidechain.GetAccountFromDir(cluster.Servers[1].DataDir())
require.NoError(t, err)

mintInput, err := mintFn.Encode([]interface{}{validatorsAddrs[2], ethgo.Ether(1)})
mintInput, err := mintFn.Encode([]interface{}{validatorsAddrs[0], ethgo.Ether(1)})
require.NoError(t, err)

receipt, err := relayer.SendTransaction(
Expand Down

0 comments on commit c5ebc40

Please sign in to comment.