Skip to content

Commit

Permalink
ERC 721 tokens bridge integration (#1348)
Browse files Browse the repository at this point in the history
* Integrate ERC 721

* Simplify ERC721 parameters

* Fix initialization of ChildERC721Predicate contract

* Minor fixes

* More fixes...

* Pass e2e test

* Sanitize smart contracts from debug logs

* Add owner of assertions for rootchain ERC 721

* Use constants for default JSON RPC addresses

* Rebase fix
  • Loading branch information
Stefan-Ethernal committed Apr 23, 2023
1 parent de84d4f commit 989978d
Show file tree
Hide file tree
Showing 19 changed files with 1,001 additions and 13 deletions.
6 changes: 6 additions & 0 deletions command/bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (

depositERC1155 "github.com/0xPolygon/polygon-edge/command/bridge/deposit/erc1155"
depositERC20 "github.com/0xPolygon/polygon-edge/command/bridge/deposit/erc20"
depositERC721 "github.com/0xPolygon/polygon-edge/command/bridge/deposit/erc721"
"github.com/0xPolygon/polygon-edge/command/bridge/exit"
withdrawERC1155 "github.com/0xPolygon/polygon-edge/command/bridge/withdraw/erc1155"
withdrawERC20 "github.com/0xPolygon/polygon-edge/command/bridge/withdraw/erc20"
withdrawERC721 "github.com/0xPolygon/polygon-edge/command/bridge/withdraw/erc721"
)

// GetCommand creates "bridge" helper command
Expand All @@ -26,10 +28,14 @@ func registerSubcommands(baseCmd *cobra.Command) {
baseCmd.AddCommand(
// bridge deposit-erc20
depositERC20.GetCommand(),
// bridge deposit-erc721
depositERC721.GetCommand(),
// bridge deposit-erc1155
depositERC1155.GetCommand(),
// bridge withdraw-erc20
withdrawERC20.GetCommand(),
// bridge withdraw-erc721
withdrawERC721.GetCommand(),
// bridge withdraw-erc1155
withdrawERC1155.GetCommand(),
// bridge exit
Expand Down
17 changes: 17 additions & 0 deletions command/bridge/common/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ func (bp *ERC20BridgeParams) Validate() error {
return nil
}

type ERC721BridgeParams struct {
*BridgeParams
TokenIDs []string
}

func NewERC721BridgeParams() *ERC721BridgeParams {
return &ERC721BridgeParams{BridgeParams: &BridgeParams{}}
}

func (bp *ERC721BridgeParams) Validate() error {
if len(bp.Receivers) != len(bp.TokenIDs) {
return errInconsistentTokenIds
}

return nil
}

type ERC1155BridgeParams struct {
*BridgeParams
Amounts []string
Expand Down
2 changes: 1 addition & 1 deletion command/bridge/deposit/erc1155/deposit_erc1155.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func GetCommand() *cobra.Command {
depositCmd.Flags().StringVar(
&dp.JSONRPCAddr,
common.JSONRPCFlag,
"http://127.0.0.1:8545",
txrelayer.DefaultRPCAddress,
"the JSON RPC root chain endpoint",
)

Expand Down
2 changes: 1 addition & 1 deletion command/bridge/deposit/erc20/deposit_erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func GetCommand() *cobra.Command {
depositCmd.Flags().StringVar(
&dp.JSONRPCAddr,
common.JSONRPCFlag,
"http://127.0.0.1:8545",
txrelayer.DefaultRPCAddress,
"the JSON RPC root chain endpoint",
)

Expand Down
294 changes: 294 additions & 0 deletions command/bridge/deposit/erc721/deposit_erc721.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
package deposit

import (
"bytes"
"fmt"
"math/big"
"strings"

"github.com/0xPolygon/polygon-edge/command"
"github.com/0xPolygon/polygon-edge/command/bridge/common"
cmdHelper "github.com/0xPolygon/polygon-edge/command/helper"
"github.com/0xPolygon/polygon-edge/command/rootchain/helper"
"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
"github.com/0xPolygon/polygon-edge/txrelayer"
"github.com/0xPolygon/polygon-edge/types"
"github.com/spf13/cobra"
"github.com/umbracle/ethgo"
)

type depositERC721Params struct {
*common.ERC721BridgeParams
testMode bool
}

var (
dp *depositERC721Params = &depositERC721Params{ERC721BridgeParams: common.NewERC721BridgeParams()}
)

func GetCommand() *cobra.Command {
depositCmd := &cobra.Command{
Use: "deposit-erc721",
Short: "Deposits ERC721 tokens from the root chain to the child chain",
PreRunE: preRunCommand,
Run: runCommand,
}

depositCmd.Flags().StringVar(
&dp.SenderKey,
common.SenderKeyFlag,
"",
"hex encoded private key of the account which sends rootchain deposit transactions",
)

depositCmd.Flags().StringSliceVar(
&dp.Receivers,
common.ReceiversFlag,
nil,
"receiving accounts addresses on child chain",
)

depositCmd.Flags().StringSliceVar(
&dp.TokenIDs,
common.TokenIDsFlag,
nil,
"token ids that are sent to the receivers accounts",
)

depositCmd.Flags().StringVar(
&dp.TokenAddr,
common.RootTokenFlag,
"",
"root ERC 721 token address",
)

depositCmd.Flags().StringVar(
&dp.PredicateAddr,
common.RootPredicateFlag,
"",
"root ERC 721 token predicate address",
)

depositCmd.Flags().StringVar(
&dp.JSONRPCAddr,
common.JSONRPCFlag,
txrelayer.DefaultRPCAddress,
"the JSON RPC root chain endpoint",
)

depositCmd.Flags().BoolVar(
&dp.testMode,
helper.TestModeFlag,
false,
"test indicates whether depositor is hardcoded test account "+
"(in that case tokens are minted to it, so it is able to make deposits)",
)

_ = depositCmd.MarkFlagRequired(common.ReceiversFlag)
_ = depositCmd.MarkFlagRequired(common.TokenIDsFlag)
_ = depositCmd.MarkFlagRequired(common.RootTokenFlag)
_ = depositCmd.MarkFlagRequired(common.RootPredicateFlag)

depositCmd.MarkFlagsMutuallyExclusive(helper.TestModeFlag, common.SenderKeyFlag)

return depositCmd
}

func preRunCommand(_ *cobra.Command, _ []string) error {
return dp.Validate()
}

func runCommand(cmd *cobra.Command, _ []string) {
outputter := command.InitializeOutputter(cmd)
defer outputter.WriteOutput()

depositorKey, err := helper.GetRootchainPrivateKey(dp.SenderKey)
if err != nil {
outputter.SetError(fmt.Errorf("failed to initialize depositor private key: %w", err))
}

depositorAddr := depositorKey.Address()

txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(dp.JSONRPCAddr))
if err != nil {
outputter.SetError(fmt.Errorf("failed to initialize rootchain tx relayer: %w", err))

return
}

receivers := make([]ethgo.Address, len(dp.Receivers))
tokenIDs := make([]*big.Int, len(dp.Receivers))

for i, tokenIDRaw := range dp.TokenIDs {
tokenIDRaw := tokenIDRaw

tokenID, err := types.ParseUint256orHex(&tokenIDRaw)
if err != nil {
outputter.SetError(fmt.Errorf("failed to decode provided token id %s: %w", tokenIDRaw, err))

return
}

receivers[i] = ethgo.Address(types.StringToAddress(dp.Receivers[i]))
tokenIDs[i] = tokenID
}

if dp.testMode {
for i := 0; i < len(tokenIDs); i++ {
mintTxn, err := createMintTxn(types.Address(depositorAddr), types.Address(depositorAddr))
if err != nil {
outputter.SetError(fmt.Errorf("mint transaction creation failed: %w", err))

return
}

receipt, err := txRelayer.SendTransaction(mintTxn, depositorKey)
if err != nil {
outputter.SetError(fmt.Errorf("failed to send mint transaction to depositor %s", depositorAddr))

return
}

if receipt.Status == uint64(types.ReceiptFailed) {
outputter.SetError(fmt.Errorf("failed to mint tokens to depositor %s", depositorAddr))

return
}
}
}

approveTxn, err := createApproveERC721PredicateTxn(types.StringToAddress(dp.PredicateAddr),
types.StringToAddress(dp.TokenAddr))
if err != nil {
outputter.SetError(fmt.Errorf("failed to approve predicate: %w", err))

return
}

receipt, err := txRelayer.SendTransaction(approveTxn, depositorKey)
if err != nil {
outputter.SetError(fmt.Errorf("failed to send root erc 721 approve transaction"))

return
}

if receipt.Status == uint64(types.ReceiptFailed) {
outputter.SetError(fmt.Errorf("failed to approve root erc 721 predicate"))

return
}

// deposit tokens
depositTxn, err := createDepositTxn(depositorAddr, receivers, tokenIDs)
if err != nil {
outputter.SetError(fmt.Errorf("failed to create tx input: %w", err))

return
}

receipt, err = txRelayer.SendTransaction(depositTxn, depositorKey)
if err != nil {
outputter.SetError(fmt.Errorf("sending deposit transactions failed (receivers: %s, tokenIDs: %s): %w",
strings.Join(dp.Receivers, ", "), strings.Join(dp.TokenIDs, ", "), err))

return
}

if receipt.Status == uint64(types.ReceiptFailed) {
outputter.SetError(fmt.Errorf("sending deposit transactions failed (receivers: %s, tokenIDs: %s)",
strings.Join(dp.Receivers, ", "), strings.Join(dp.TokenIDs, ", ")))

return
}

outputter.SetCommandResult(
&depositERC721Result{
Sender: depositorAddr.String(),
Receivers: dp.Receivers,
TokenIDs: dp.TokenIDs,
})
}

// createDepositTxn encodes parameters for deposit fnction on rootchain predicate contract
func createDepositTxn(sender ethgo.Address,
receivers []ethgo.Address, tokenIDs []*big.Int) (*ethgo.Transaction, error) {
depositToRoot := &contractsapi.DepositBatchRootERC721PredicateFn{
RootToken: types.StringToAddress(dp.TokenAddr),
Receivers: receivers,
TokenIDs: tokenIDs,
}

input, err := depositToRoot.EncodeAbi()
if err != nil {
return nil, fmt.Errorf("failed to encode provided parameters: %w", err)
}

addr := ethgo.Address(types.StringToAddress(dp.PredicateAddr))

return &ethgo.Transaction{
From: sender,
To: &addr,
Input: input,
}, nil
}

// createMintTxn encodes parameters for mint function on rootchain token contract
func createMintTxn(sender, receiver types.Address) (*ethgo.Transaction, error) {
mintFn := &contractsapi.MintRootERC721Fn{
To: receiver,
}

input, err := mintFn.EncodeAbi()
if err != nil {
return nil, fmt.Errorf("failed to encode provided parameters: %w", err)
}

addr := ethgo.Address(types.StringToAddress(dp.TokenAddr))

return &ethgo.Transaction{
From: ethgo.Address(sender),
To: &addr,
Input: input,
}, nil
}

// createApproveERC721PredicateTxn sends approve transaction
func createApproveERC721PredicateTxn(rootERC721Predicate, rootERC721Token types.Address) (*ethgo.Transaction, error) {
approveFnParams := &contractsapi.SetApprovalForAllRootERC721Fn{
Operator: rootERC721Predicate,
Approved: true,
}

input, err := approveFnParams.EncodeAbi()
if err != nil {
return nil, fmt.Errorf("failed to encode parameters for RootERC721.approve. error: %w", err)
}

addr := ethgo.Address(rootERC721Token)

return &ethgo.Transaction{
To: &addr,
Input: input,
}, nil
}

type depositERC721Result struct {
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
TokenIDs []string `json:"tokenIDs"`
}

func (r *depositERC721Result) GetOutput() string {
var buffer bytes.Buffer

vals := make([]string, 0, 3)
vals = append(vals, fmt.Sprintf("Sender|%s", r.Sender))
vals = append(vals, fmt.Sprintf("Receivers|%s", strings.Join(r.Receivers, ", ")))
vals = append(vals, fmt.Sprintf("TokenIDs|%s", strings.Join(r.TokenIDs, ", ")))

buffer.WriteString("\n[DEPOSIT ERC 721]\n")
buffer.WriteString(cmdHelper.FormatKV(vals))
buffer.WriteString("\n")

return buffer.String()
}
2 changes: 1 addition & 1 deletion command/bridge/exit/exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func GetCommand() *cobra.Command {
exitCmd.Flags().StringVar(
&ep.rootJSONRPCAddr,
rootJSONRPCFlag,
"http://127.0.0.1:8545",
txrelayer.DefaultRPCAddress,
"the JSON RPC root chain endpoint",
)

Expand Down
Loading

0 comments on commit 989978d

Please sign in to comment.