From bdc18d95063a82207094ab71067f984656ed9082 Mon Sep 17 00:00:00 2001
From: Xavier Romero <xavier@polygon.technology>
Date: Tue, 10 Dec 2024 20:07:34 +0100
Subject: [PATCH] Add storage initialization + read bytecode from file to
 wrap-contract

---
 cmd/retest/retest.go             | 25 ++--------
 cmd/wrapcontract/usage.md        | 17 +++++++
 cmd/wrapcontract/wrapcontract.go | 83 ++++++++++++++++++++++++++++----
 doc/polycli_wrap-contract.md     | 22 ++++++++-
 util/util.go                     | 20 ++++++++
 5 files changed, 133 insertions(+), 34 deletions(-)

diff --git a/cmd/retest/retest.go b/cmd/retest/retest.go
index 73b1a94f..5827a2a1 100644
--- a/cmd/retest/retest.go
+++ b/cmd/retest/retest.go
@@ -330,7 +330,7 @@ func EthTestDataToString(data EthTestData) string {
 	case reflect.Float64:
 		// We have few tests with numeric code, ex:
 		// "code": 16449,
-		return processStorageData(data.(float64))
+		return util.GetHexString(data.(float64))
 	
 	default:
 		log.Fatal().Any("input", data).Str("kind", v.Kind().String()).Msg("Attempted to convert unknown type to raw data")
@@ -378,25 +378,6 @@ func processTestDataString(data string) string {
 	return ""
 }
 
-func processStorageData(data any) string {
-	var result string
-	if reflect.TypeOf(data).Kind() == reflect.Float64 {
-		result = fmt.Sprintf("%x", int64(data.(float64)))
-	} else if reflect.TypeOf(data).Kind() == reflect.String {
-		if strings.HasPrefix(data.(string), "0x") {
-			result = strings.TrimPrefix(data.(string), "0x")
-		} else {
-			result = data.(string)
-		}
-	} else {
-		log.Fatal().Any("data", data).Msg("unknown storage data type")
-	}
-	if len(result) % 2 != 0 {
-		result = "0" + result
-	}
-	return result
-}
-
 // isStandardSolidityString will do a rough check to see if the string looks like a typical solidity file rather than
 // the contracts that are usually in the retest code base
 func isStandardSolidityString(contract string) bool {
@@ -676,8 +657,8 @@ func storageToByteCode(storage map[string]EthTestNumeric) string {
 			log.Warn().Str("slot", slot).Msg("found a storage entry for invalid slot")
 		}
 
-		s := processStorageData(slot)
-		v := processStorageData(value)
+		s := util.GetHexString(slot)
+		v := util.GetHexString(value)
 		sLen := len(s) / 2
 		vLen := len(v) / 2
 		sPushCode := 0x5F + sLen
diff --git a/cmd/wrapcontract/usage.md b/cmd/wrapcontract/usage.md
index 789dd74c..8cf281e6 100644
--- a/cmd/wrapcontract/usage.md
+++ b/cmd/wrapcontract/usage.md
@@ -2,9 +2,26 @@ This command takes the runtime bytecode, the bytecode deployed on-chain, as inpu
 
 ```bash
 $ polycli wrap-contract 69602a60005260206000f3600052600a6016f3
+$ echo 69602a60005260206000f3600052600a6016f3 | polycli wrap-contract 
 
 ```
 
+You can also provide a path to a file, and the bytecode while be read from there.
+
+```bash
+$ polycli wrap-contract bytecode.txt
+$ polycli wrap-contract ../bytecode.txt
+$ polycli wrap-contract /tmp/bytecode.txt
+$ echo /tmp/bytecode.txt | polycli wrap-contract
+```
+
+Additionally, you can provide storage for the contract in JSON
+```bash
+$ polycli wrap-contract 0x4455 --storage '{"0x01":"0x0034"}'
+$ polycli wrap-contract 0x4455 --storage '{"0x01":"0x0034", "0x02": "0xFF"}'
+$ echo 69602a60005260206000f3600052600a6016f3 | polycli wrap-contract --storage '{"0x01":"0x0034", "0x02": "0xFF"}'
+```
+
 The resulting bytecode will be formatted this way:
 
 		0x??   // storage initialization code if any
diff --git a/cmd/wrapcontract/wrapcontract.go b/cmd/wrapcontract/wrapcontract.go
index 101ea25b..23cce01f 100644
--- a/cmd/wrapcontract/wrapcontract.go
+++ b/cmd/wrapcontract/wrapcontract.go
@@ -1,33 +1,96 @@
 package wrapcontract
 
 import (
-	"fmt"
 	_ "embed"
-	"github.com/spf13/cobra"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+	"encoding/json"
+
 	"github.com/0xPolygon/polygon-cli/util"
+	"github.com/spf13/cobra"
 )
 
 var (
 	//go:embed usage.md
-	usage                  string
+	usage string
+	jsonStorage *string
 )
 
 var WrapContractCmd = &cobra.Command{
-	Use:   "wrap-contract bytecode",
+	Use:   "wrap-contract bytecode|file",
 	Aliases: []string{"wrapcontract", "wrapContract"},
 	Short: "Wrap deployed bytecode into create bytecode.",
 	Long:  usage,
 	RunE: func(cmd *cobra.Command, args []string) error {
-		deployed_bytecode := args[0]
-		storage_bytecode := ""
-		create_bytecode := util.WrapDeployedCode(deployed_bytecode, storage_bytecode)
-		fmt.Println(create_bytecode)
+		deployedBytecode, err := getInputData(args)
+		if err != nil {
+			cmd.PrintErrf("There was an error reading input for wrapping contract: %s", err.Error())
+			return err
+		}
+		storageBytecode, err := getStorageBytecode()
+		if err != nil {
+			cmd.PrintErrf("There was an error reading storage map: %s", err.Error())
+		}
+		createBytecode := util.WrapDeployedCode(deployedBytecode, storageBytecode)
+		fmt.Println(createBytecode)
 		return nil
 	},
 	Args: func(cmd *cobra.Command, args []string) error {
-		if len(args) != 1 {
-			return fmt.Errorf("expected exactly one argument: bytecode")
+		if len(args) > 1 {
+			return fmt.Errorf("expected at most one argument: bytecode")
 		}
 		return nil
 	},
 }
+
+func init() {
+	flagSet := WrapContractCmd.PersistentFlags()
+	jsonStorage = flagSet.String("storage", "", "Provide storage slots in json format k:v")
+}
+
+func getInputData(args []string) (string, error) {
+	var deployedBytecode string
+	if len(args) == 0 {
+		deployedBytecodeBytes, err := io.ReadAll(os.Stdin)
+		if err != nil {
+			return "", err
+		}
+		deployedBytecode = string(deployedBytecodeBytes)
+	} else {
+		deployedBytecodeOrFile := args[0]
+		// Try to open the param as a file, otherwise treat it as bytecode
+		deployedBytecodeBytes, err := os.ReadFile(deployedBytecodeOrFile)
+		if err != nil {
+			deployedBytecode = deployedBytecodeOrFile
+		} else {
+			deployedBytecode = string(deployedBytecodeBytes)
+		}
+	}
+
+	return strings.TrimSpace(deployedBytecode), nil
+}
+
+func getStorageBytecode() (string, error) {
+	var storageBytecode string = ""
+	
+	if jsonStorage != nil && *jsonStorage != "" {
+		var storage map[string]string
+		err := json.Unmarshal([]byte(*jsonStorage), &storage)
+		if err != nil {
+			return storageBytecode, err
+		}
+		for k, v := range storage {
+			slot := util.GetHexString(k)
+			value := util.GetHexString(v)
+			sLen := len(slot) / 2
+			vLen := len(value) / 2
+			sPushCode := 0x5f + sLen
+			vPushCode := 0x5f + vLen
+			storageBytecode += fmt.Sprintf("%02x%s%02x%s55", vPushCode, value, sPushCode, slot)
+		}
+	}
+
+	return storageBytecode, nil
+}
diff --git a/doc/polycli_wrap-contract.md b/doc/polycli_wrap-contract.md
index e67bae1e..1c9b1074 100644
--- a/doc/polycli_wrap-contract.md
+++ b/doc/polycli_wrap-contract.md
@@ -14,7 +14,7 @@
 Wrap deployed bytecode into create bytecode.
 
 ```bash
-polycli wrap-contract bytecode [flags]
+polycli wrap-contract bytecode|file [flags]
 ```
 
 ## Usage
@@ -23,9 +23,26 @@ This command takes the runtime bytecode, the bytecode deployed on-chain, as inpu
 
 ```bash
 $ polycli wrap-contract 69602a60005260206000f3600052600a6016f3
+$ echo 69602a60005260206000f3600052600a6016f3 | polycli wrap-contract 
 
 ```
 
+You can also provide a path to a file, and the bytecode while be read from there.
+
+```bash
+$ polycli wrap-contract bytecode.txt
+$ polycli wrap-contract ../bytecode.txt
+$ polycli wrap-contract /tmp/bytecode.txt
+$ echo /tmp/bytecode.txt | polycli wrap-contract
+```
+
+Additionally, you can provide storage for the contract in JSON
+```bash
+$ polycli wrap-contract 0x4455 --storage '{"0x01":"0x0034"}'
+$ polycli wrap-contract 0x4455 --storage '{"0x01":"0x0034", "0x02": "0xFF"}'
+$ echo 69602a60005260206000f3600052600a6016f3 | polycli wrap-contract --storage '{"0x01":"0x0034", "0x02": "0xFF"}'
+```
+
 The resulting bytecode will be formatted this way:
 
 		0x??   // storage initialization code if any
@@ -41,7 +58,8 @@ The resulting bytecode will be formatted this way:
 ## Flags
 
 ```bash
-  -h, --help   help for wrap-contract
+  -h, --help             help for wrap-contract
+      --storage string   Provide storage slots in json format k:v
 ```
 
 The command also inherits flags from parent commands.
diff --git a/util/util.go b/util/util.go
index 0c0fa159..50f9781d 100644
--- a/util/util.go
+++ b/util/util.go
@@ -7,6 +7,7 @@ import (
 	"strconv"
 	"strings"
 	"time"
+	"reflect"
 
 	"github.com/cenkalti/backoff"
 	"github.com/ethereum/go-ethereum/common/hexutil"
@@ -337,3 +338,22 @@ func WrapDeployedCode(deployedBytecode string, storageBytecode string) string {
 		"%s",			// CODE starts here.
 		storageBytecode, codeCopySize, codeCopyOffset, codeCopySize, deployedBytecode)
 }
+
+func GetHexString(data any) string {
+	var result string
+	if reflect.TypeOf(data).Kind() == reflect.Float64 {
+		result = fmt.Sprintf("%x", int64(data.(float64)))
+	} else if reflect.TypeOf(data).Kind() == reflect.String {
+		if strings.HasPrefix(data.(string), "0x") {
+			result = strings.TrimPrefix(data.(string), "0x")
+		} else {
+			result = data.(string)
+		}
+	} else {
+		log.Fatal().Any("data", data).Msg("unknown storage data type")
+	}
+	if len(result) % 2 != 0 {
+		result = "0" + result
+	}
+	return strings.ToLower(result)
+}