Skip to content

Commit

Permalink
Integrate NativeERC20Mintable token contract (#1316)
Browse files Browse the repository at this point in the history
* Deploy NativeERC20Mintable SC

* Update core-contracts commit

* Linters fix

* Implement e2e test

* Revert SC spec

* Update TestE2E_Consensus_MintableERC20NativeToken test
  • Loading branch information
Stefan-Ethernal committed Mar 23, 2023
1 parent 16822c1 commit 90d8020
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 55 deletions.
7 changes: 7 additions & 0 deletions command/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ func setFlags(cmd *cobra.Command) {
"",
"trie root from the corresponding triedb",
)

cmd.Flags().BoolVar(
&params.mintableNativeToken,
mintableTokenFlag,
false,
"flag indicate whether mintable or non-mintable native ERC20 token is deployed",
)
}

// Allow list
Expand Down
2 changes: 2 additions & 0 deletions command/genesis/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
posFlag = "pos"
minValidatorCount = "min-validator-count"
maxValidatorCount = "max-validator-count"
mintableTokenFlag = "mintable-native-token"
)

// Legacy flags that need to be preserved for running clients
Expand Down Expand Up @@ -90,6 +91,7 @@ type genesisParams struct {
// allowlist
contractDeployerAllowListAdmin []string
contractDeployerAllowListEnabled []string
mintableNativeToken bool
}

func (p *genesisParams) validateFlags() error {
Expand Down
30 changes: 18 additions & 12 deletions command/genesis/polybft_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,12 @@ func (p *genesisParams) generatePolyBftChainConfig(o command.OutputFormatter) er
SprintSize: p.sprintSize,
EpochReward: p.epochReward,
// use 1st account as governance address
Governance: manifest.GenesisValidators[0].Address,
Bridge: bridge,
ValidatorSetAddr: contracts.ValidatorSetContract,
StateReceiverAddr: contracts.StateReceiverContract,
InitialTrieRoot: types.StringToHash(p.initialStateRoot),
Governance: manifest.GenesisValidators[0].Address,
Bridge: bridge,
ValidatorSetAddr: contracts.ValidatorSetContract,
StateReceiverAddr: contracts.StateReceiverContract,
InitialTrieRoot: types.StringToHash(p.initialStateRoot),
MintableERC20Token: p.mintableNativeToken,
}

chainConfig := &chain.Chain{
Expand Down Expand Up @@ -218,10 +219,12 @@ func (p *genesisParams) generatePolyBftChainConfig(o command.OutputFormatter) er
}

func (p *genesisParams) deployContracts(totalStake *big.Int) (map[types.Address]*chain.GenesisAccount, error) {
genesisContracts := []struct {
type contractInfo struct {
artifact *artifact.Artifact
address types.Address
}{
}

genesisContracts := []*contractInfo{
{
// ChildValidatorSet contract
artifact: contractsapi.ChildValidatorSet,
Expand All @@ -232,11 +235,6 @@ func (p *genesisParams) deployContracts(totalStake *big.Int) (map[types.Address]
artifact: contractsapi.StateReceiver,
address: contracts.StateReceiverContract,
},
{
// NativeERC20 Token contract
artifact: contractsapi.NativeERC20,
address: contracts.NativeERC20TokenContract,
},
{
// ChildERC20 token contract
artifact: contractsapi.ChildERC20,
Expand Down Expand Up @@ -264,6 +262,14 @@ func (p *genesisParams) deployContracts(totalStake *big.Int) (map[types.Address]
},
}

if !params.mintableNativeToken {
genesisContracts = append(genesisContracts,
&contractInfo{artifact: contractsapi.NativeERC20, address: contracts.NativeERC20TokenContract})
} else {
genesisContracts = append(genesisContracts,
&contractInfo{artifact: contractsapi.NativeERC20Mintable, address: contracts.NativeERC20TokenContract})
}

allocations := make(map[types.Address]*chain.GenesisAccount, len(genesisContracts))

for _, contract := range genesisContracts {
Expand Down
14 changes: 0 additions & 14 deletions consensus/polybft/contracts_initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,6 @@ func getInitChildERC20PredicateInput(config *BridgeConfig) ([]byte, error) {
return params.EncodeAbi()
}

// getInitNativeERC20Input builds input parameters for NativeERC20 SC initialization
func getInitNativeERC20Input(nativeTokenName, nativeTokenSymbol string, nativeTokenDecimals uint8,
rootTokenAddr, childPredicateAddr types.Address) ([]byte, error) {
params := &contractsapi.InitializeNativeERC20Function{
Name_: nativeTokenName,
Symbol_: nativeTokenSymbol,
Decimals_: nativeTokenDecimals,
RootToken_: rootTokenAddr,
Predicate_: childPredicateAddr,
}

return params.EncodeAbi()
}

func initContract(to types.Address, input []byte, contractName string, transition *state.Transition) error {
result := transition.Call2(contracts.SystemCaller, to, input,
big.NewInt(0), 100_000_000)
Expand Down
4 changes: 4 additions & 0 deletions consensus/polybft/contractsapi/artifacts-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ func main() {
"child/NativeERC20.sol",
"NativeERC20",
},
{
"child/NativeERC20Mintable.sol",
"NativeERC20Mintable",
},
{
"child/ChildERC20.sol",
"ChildERC20",
Expand Down
8 changes: 8 additions & 0 deletions consensus/polybft/contractsapi/bindings-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ func main() {
},
[]string{},
},
{
"NativeERC20Mintable",
gensc.NativeERC20Mintable,
[]string{
"initialize",
},
[]string{},
},
{
"RootERC20Predicate",
gensc.RootERC20Predicate,
Expand Down
17 changes: 17 additions & 0 deletions consensus/polybft/contractsapi/contractsapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion consensus/polybft/contractsapi/gen_sc_data.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions consensus/polybft/contractsapi/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var (
Merkle *artifact.Artifact
ChildValidatorSet *artifact.Artifact
NativeERC20 *artifact.Artifact
NativeERC20Mintable *artifact.Artifact
StateReceiver *artifact.Artifact
ChildERC20 *artifact.Artifact
ChildERC20Predicate *artifact.Artifact
Expand Down Expand Up @@ -110,6 +111,11 @@ func init() {
log.Fatal(err)
}

NativeERC20Mintable, err = artifact.DecodeArtifact([]byte(NativeERC20MintableArtifact))
if err != nil {
log.Fatal(err)
}

RootERC20, err = artifact.DecodeArtifact([]byte(MockERC20Artifact))
if err != nil {
log.Fatal(err)
Expand Down
49 changes: 39 additions & 10 deletions consensus/polybft/polybft.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/0xPolygon/polygon-edge/chain"
"github.com/0xPolygon/polygon-edge/consensus"
"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer"
"github.com/0xPolygon/polygon-edge/consensus/polybft/wallet"
"github.com/0xPolygon/polygon-edge/contracts"
Expand Down Expand Up @@ -142,18 +143,46 @@ func GenesisPostHookFactory(config *chain.Chain, engineName string) func(txn *st
rootNativeERC20Token = polyBFTConfig.Bridge.RootNativeERC20Addr
}

// initialize NativeERC20 SC
input, err = getInitNativeERC20Input(
nativeTokenName,
nativeTokenSymbol,
nativeTokenDecimals,
rootNativeERC20Token,
contracts.ChildERC20PredicateContract)
if err != nil {
return err
if polyBFTConfig.MintableERC20Token {
// initialize NativeERC20Mintable SC
params := &contractsapi.InitializeNativeERC20MintableFunction{
Predicate_: contracts.ChildERC20PredicateContract,
Owner_: polyBFTConfig.Governance,
RootToken_: rootNativeERC20Token,
Name_: nativeTokenName,
Symbol_: nativeTokenSymbol,
Decimals_: nativeTokenDecimals,
}

input, err := params.EncodeAbi()
if err != nil {
return err
}

if err = initContract(contracts.NativeERC20TokenContract, input, "NativeERC20Mintable", transition); err != nil {
return err
}
} else {
// initialize NativeERC20 SC
params := &contractsapi.InitializeNativeERC20Function{
Name_: nativeTokenName,
Symbol_: nativeTokenSymbol,
Decimals_: nativeTokenDecimals,
RootToken_: rootNativeERC20Token,
Predicate_: contracts.ChildERC20PredicateContract,
}

input, err := params.EncodeAbi()
if err != nil {
return err
}

if err = initContract(contracts.NativeERC20TokenContract, input, "NativeERC20", transition); err != nil {
return err
}
}

return initContract(contracts.NativeERC20TokenContract, input, "NativeERC20", transition)
return nil
}
}

Expand Down
3 changes: 3 additions & 0 deletions consensus/polybft/polybft_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type PolyBFTConfig struct {
// Governance is the initial governance address
Governance types.Address `json:"governance"`

// MintableERC20Token denotes whether mintable ERC20 token is used
MintableERC20Token bool `json:"mintableERC20"`

// TODO: Remove these two addresses as they are hardcoded and known in advance
// Address of the system contracts, as of now (testing) this is populated automatically during genesis
ValidatorSetAddr types.Address `json:"validatorSetAddr"`
Expand Down
94 changes: 94 additions & 0 deletions e2e-polybft/e2e/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/0xPolygon/polygon-edge/command/genesis"
"github.com/0xPolygon/polygon-edge/command/sidechain"
"github.com/0xPolygon/polygon-edge/consensus/polybft"
"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
"github.com/0xPolygon/polygon-edge/contracts"
"github.com/0xPolygon/polygon-edge/e2e-polybft/framework"
"github.com/0xPolygon/polygon-edge/txrelayer"
Expand Down Expand Up @@ -609,3 +610,96 @@ func TestE2E_Consensus_CorrectnessOfExtraValidatorsShouldNotDependOnDelegate(t *
close(endCh)
<-waitCh
}

func TestE2E_Consensus_MintableERC20NativeToken(t *testing.T) {
const (
validatorCount = 5
epochSize = 5
minterPath = "test-chain-1"
)

validatorsAddrs := make([]types.Address, validatorCount)
initialStake := ethgo.Gwei(1)
initialBalance := int64(0)

cluster := framework.NewTestCluster(t,
validatorCount,
framework.WithMintableNativeToken(true),
framework.WithEpochSize(epochSize),
framework.WithSecretsCallback(func(addrs []types.Address, config *framework.TestClusterConfig) {
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.StakeAmounts = append(config.StakeAmounts, fmt.Sprintf("%s:%d", addr, initialStake))
config.PremineValidators = append(config.PremineValidators, fmt.Sprintf("%s:%d", addr, initialBalance))
}
validatorsAddrs[i] = addr
}
}))
defer cluster.Stop()

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

minterAcc, err := sidechain.GetAccountFromDir(minterSrv.DataDir())
require.NoError(t, err)

cluster.WaitForReady(t)

// send mint transactions
relayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(targetJSONRPC))
require.NoError(t, err)

mintFn, exists := contractsapi.NativeERC20Mintable.Abi.Methods["mint"]
require.True(t, exists)

nativeTokenAddr := ethgo.Address(contracts.NativeERC20TokenContract)
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:] {
balance, err := targetJSONRPC.Eth().GetBalance(ethgo.Address(addr), ethgo.Latest)
require.NoError(t, err)
t.Logf("Pre-mint balance: %v=%d\n", addr, balance)

mintInput, err := mintFn.Encode([]interface{}{addr, mintAmount})
require.NoError(t, err)

receipt, err := relayer.SendTransaction(
&ethgo.Transaction{
To: &nativeTokenAddr,
Input: mintInput,
}, minterAcc.Ecdsa)
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, big.NewInt(initialBalance)), balance)
}

minterBalance, err := targetJSONRPC.Eth().GetBalance(minterAcc.Ecdsa.Address(), ethgo.Latest)
require.NoError(t, err)
require.Equal(t, ethgo.Ether(1e6), 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)})
require.NoError(t, err)

receipt, err := relayer.SendTransaction(
&ethgo.Transaction{
To: &nativeTokenAddr,
Input: mintInput,
}, nonMinterAcc.Ecdsa)
require.NoError(t, err)
require.Equal(t, uint64(types.ReceiptFailed), receipt.Status)
}
Loading

0 comments on commit 90d8020

Please sign in to comment.