Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ wallets.toml

# Go workspace file
go.work
docker/gethData

# Node
node_modules
1 change: 1 addition & 0 deletions 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266.keystore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"address":"f39fd6e51aad88f6f4ce6ab8827279cfffb92266","crypto":{"cipher":"aes-128-ctr","ciphertext":"d005030a7684f3adad2447cbb27f63039eec2224c451eaa445de0d90502b9f3d","cipherparams":{"iv":"dc07a54bc7e388efa89c34d42f2ebdb4"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"cf2ec55ecae11171de575112cfb16963570533a9c46fb774473ceb11519eb24a"},"mac":"3eb180d405a5da6e462b2adc00091c14856c91d574bf27348714506357d6e177"},"id":"035454db-6b6d-477f-8a79-ce24c10b185f","version":3}
79 changes: 79 additions & 0 deletions cmd/common.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
package main

import (
"bufio"
"context"
"errors"
"fmt"
"os"
"path"
"time"

"github.com/0xPolygon/cdk-contracts-tooling/config"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli/v2"
)

const (
Expand All @@ -21,3 +32,71 @@ func checkWorkingDir() (string, error) {
}
return baseDir, nil
}

func askForConfirmation(cliCtx *cli.Context, msg string) error {
skip := cliCtx.Bool(skipConfirmationFlagName)
if skip {
return nil
}
reader := bufio.NewReader(os.Stdin)
fmt.Println(msg, " (yes / no): ")
answer, err := reader.ReadString('\n')
if err != nil {
return err
}
if answer != "yes" {
return errors.New("confirmation rejected")
}
return nil
}

func loadAuthAndClient(cliCtx *cli.Context) (common.Address, *bind.TransactOpts, *ethclient.Client, error) {
fmt.Println("loading RPC")
rpcs, err := config.LoadRPCs()
if err != nil {
return common.Address{}, nil, nil, err
}
l1Network := cliCtx.String(l1FlagName)
client, err := rpcs.GetClient(l1Network)
if err != nil {
return common.Address{}, nil, nil, err
}
chainID, err := rpcs.GetChainID(l1Network)
if err != nil {
return common.Address{}, nil, nil, err
}

fmt.Println("loading wallet")
wallets, err := config.LoadWallets()
if err != nil {
return common.Address{}, nil, nil, err
}
walletAddr := cliCtx.String(walletFlagName)
walletPass := cliCtx.String(walletPasswordFlagName)
auth, err := wallets.GetAuth(walletAddr, walletPass, chainID)
if err != nil {
return common.Address{}, nil, nil, err
}
return common.HexToAddress(walletAddr), auth, client, nil
}

// waitTxToBeMined boolean indicates if the tx was successful
func waitTxToBeMined(parentCtx context.Context, client *ethclient.Client, tx *types.Transaction, timeout time.Duration) (bool, error) {
ctx, cancel := context.WithTimeout(parentCtx, timeout)
defer cancel()
receipt, err := bind.WaitMined(ctx, client, tx)
if errors.Is(err, context.DeadlineExceeded) {
return false, err
} else if err != nil {
return false, err
}
return receipt.Status == types.ReceiptStatusSuccessful, nil
}

func getTimeout(cliCtx *cli.Context) time.Duration {
timeout := cliCtx.Duration(timeoutFlagName)
if timeout == 0 {
return defaultTimeout
}
return timeout
}
45 changes: 45 additions & 0 deletions cmd/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"context"
"os"
"path"
"testing"

"github.com/0xPolygon/cdk-contracts-tooling/docker"
"github.com/ethereum/go-ethereum/ethclient"
)

const (
thisDir = "cmd"
walletAddr = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
walletPass = "testonly"
)

func TestMain(t *testing.M) {
baseDir, err := os.Getwd()
if err != nil {
panic(err)
}
_, f := path.Split(baseDir)
if f == thisDir {
err = os.Chdir("..")
if err != nil {
panic(err)
}
}
ctx := context.Background()
if err := docker.StartMockL1Docker(ctx); err != nil {
panic(err)
}
defer func() {
if err := docker.StopMockL1Docker(); err != nil {
panic(err)
}
}()
t.Run()
}

func getClient() (*ethclient.Client, error) {
return ethclient.Dial("http://localhost:8545")
}
Comment on lines +43 to +45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably doesn't deserve a separate function for this. WDYT?

Also as a side note, would it be an overkill if we were starting up the mock l1 network in case it is not running already?

214 changes: 214 additions & 0 deletions cmd/dac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"sort"
"strings"

"github.com/0xPolygon/cdk-contracts-tooling/contracts/common/erc1967proxy"
"github.com/0xPolygon/cdk-contracts-tooling/contracts/elderberry/polygondatacommittee"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/urfave/cli/v2"
)

const (
setupDACFilePathFlagName = "setup-file"
implementationAddressFlagName = "implementation-address"
)

var (
deployDACCommand = &cli.Command{
Name: "deploy-dac",
Aliases: []string{},
Usage: "Deploy the Data Availability smart contract",
Action: deployDAC,
Flags: []cli.Flag{
l1Flag,
walletFlag,
walletPasswordFlag,
skipConfirmationFlag,
timeoutFlag,
&cli.StringFlag{
Name: implementationAddressFlagName,
Aliases: []string{"implementation", "impl"},
Usage: `Smart contract address of a DAC implementation. If provided, it will be used for the proxy implementation instead of deploying a new one`,
Required: false,
},
},
}
setupDACCommand = &cli.Command{
Name: "setup-dac",
Aliases: []string{},
Usage: "Setup a Data Availability smart contract",
Action: setupDAC,
Flags: []cli.Flag{
l1Flag,
addressFlag,
walletFlag,
walletPasswordFlag,
skipConfirmationFlag,
timeoutFlag,
&cli.PathFlag{
Name: setupDACFilePathFlagName,
Aliases: []string{"f"},
Usage: `File path of a JSON that looks like {"requiredSingatures": X, "members": [{"address": "0x...", "url": "http://..."}]}`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Usage: `File path of a JSON that looks like {"requiredSingatures": X, "members": [{"address": "0x...", "url": "http://..."}]}`,
Usage: `File path of a JSON that looks like {"requiredSignatures": X, "members": [{"address": "0x...", "url": "http://..."}]}`,

Probably it would be a good idea providing a JSON example as it is done for the wallets and rpcs toml files?

Required: true,
},
},
}
)

func deployDAC(cliCtx *cli.Context) error {
_, err := checkWorkingDir()
if err != nil {
return err
}
walletAddr, auth, client, err := loadAuthAndClient(cliCtx)
if err != nil {
return err
}

timeout := getTimeout(cliCtx)
dacAddrStr := cliCtx.String(implementationAddressFlagName)
var dacAddr common.Address
if dacAddrStr != "" {
dacAddr = common.HexToAddress(dacAddrStr)
fmt.Printf("Using %s as DAC implementation (previously deployed)\n", dacAddr)
} else {
err = askForConfirmation(cliCtx, fmt.Sprintf(
"Do you want to send the tx that will deploy the DAC from the address %s?",
walletAddr,
))
if err != nil {
return err
}
fmt.Println("deploying DAC implementaiton")
var tx *types.Transaction
dacAddr, tx, _, err = polygondatacommittee.DeployPolygondatacommittee(auth, client)
if err != nil {
return err
}
fmt.Printf("DAC implementation will be deployed at %s with the tx %s\n", dacAddr.Hex(), tx.Hash())
fmt.Printf("Waiting for the tx to be mined, this will timeout after %s\n", timeout)
ok, err := waitTxToBeMined(cliCtx.Context, client, tx, timeout)
if err != nil {
return err
}
if !ok {
return errors.New("the transaction was mined, but it was not executed successfuly")
}
}

fmt.Println("deploying proxy")
proxyAddr, tx, _, err := erc1967proxy.DeployErc1967proxy(
auth,
client,
dacAddr,
common.Hex2Bytes("8129fc1c00000000000000000000000000000000000000000000000000000000"), // initialize() signature
)
if err != nil {
return err
}
fmt.Printf("DAC proxy will be deployed at %s with the tx %s\n", proxyAddr.Hex(), tx.Hash())
fmt.Printf("Waiting for the tx to be mined, this will timeout after %s\n", timeout)
ok, err := waitTxToBeMined(cliCtx.Context, client, tx, timeout)
if err != nil {
return err
}
if !ok {
return errors.New("the transaction was mined, but it was not executed successfuly")
}

return nil
}

func setupDAC(cliCtx *cli.Context) error {
_, err := checkWorkingDir()
if err != nil {
return err
}
walletAddr, auth, client, err := loadAuthAndClient(cliCtx)
if err != nil {
return err
}

fmt.Println("loading setup file")
setupDACFilePath := cliCtx.String(setupDACFilePathFlagName)
data, err := os.ReadFile(setupDACFilePath)
if err != nil {
return err
}
var setup DACSetup
err = json.Unmarshal(data, &setup)
if err != nil {
return err
}
fmt.Printf(`
DAC Configuration:
required signatures %d / %d.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
required signatures %d / %d.
Required signatures %d / %d.

Members: %+v

`, setup.RequiredSingatures, len(setup.Members), setup.Members,
)
dacAddr := cliCtx.String(addressFlagName)
err = askForConfirmation(cliCtx, fmt.Sprintf(
"Do you want to send the tx that will setup the DAC deployed at %s from the address %s with the configuration shown above?",
dacAddr, walletAddr,
))
if err != nil {
return err
}

fmt.Println("sending tx")
dac, err := polygondatacommittee.NewPolygondatacommittee(common.HexToAddress(dacAddr), client)
if err != nil {
return err
}
addrs, urls := setup.MembersAndAddrsForSetup()
tx, err := dac.SetupCommittee(auth, big.NewInt(setup.RequiredSingatures), urls, addrs)
if err != nil {
return err
}
fmt.Printf("DAC will be setup with the tx %s\n", tx.Hash())
timeout := getTimeout(cliCtx)
fmt.Printf("Waiting for the tx to be mined, this will timeout after %s\n", timeout)
ok, err := waitTxToBeMined(cliCtx.Context, client, tx, timeout)
if err != nil {
return err
}
if !ok {
return errors.New("the transaction was mined, but it was not executed successfuly")
}

return nil
}

type DACMember struct {
Address common.Address `json:"address"`
URL string `json:"url"`
}

type DACSetup struct {
RequiredSingatures int64 `json:"requiredSingatures"`
Members []DACMember `json:"members"`
}

func (s DACSetup) Len() int { return len(s.Members) }
func (s DACSetup) Less(i, j int) bool {
return strings.ToUpper(s.Members[i].Address.Hex()) < strings.ToUpper(s.Members[j].Address.Hex())
}
func (s DACSetup) Swap(i, j int) { s.Members[i], s.Members[j] = s.Members[j], s.Members[i] }

func (d *DACSetup) MembersAndAddrsForSetup() (addrsBytes []byte, urls []string) {
sort.Sort(d)
for _, m := range d.Members {
addrsBytes = append(addrsBytes, m.Address.Bytes()...)
urls = append(urls, m.URL)
}
return
}
Loading