github.com/0xPolygon/supernets2-node@v0.0.0-20230711153321-2fe574524eaa/test/scripts/cmd/compilesc/manager.go (about) 1 package compilesc 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "os/exec" 8 "os/user" 9 "path" 10 "path/filepath" 11 "runtime" 12 "strings" 13 14 "github.com/0xPolygon/supernets2-node/log" 15 "golang.org/x/sync/errgroup" 16 "gopkg.in/yaml.v2" 17 ) 18 19 const ( 20 defaultAbigenImage = "ethereum/client-go:alltools-latest" 21 defaultSolcImage = "ethereum/solc" 22 23 containerBase = "/contracts" 24 ) 25 26 // CompileUnit represents a single contract to be compiled. 27 type CompileUnit struct { 28 Name string `yaml:"name"` 29 SolcVersion string `yaml:"solcVersion"` 30 InputPath string `yaml:"inputPath"` 31 OutputPath string `yaml:"outputPath"` 32 } 33 34 // CompileUnits represents data for all the contracts to be compiled. 35 type CompileUnits struct { 36 Parallel []CompileUnit `yaml:"parallel"` 37 Sequential []CompileUnit `yaml:"sequential"` 38 } 39 40 type compileIndex struct { 41 CompileUnits CompileUnits `yaml:"compileUnits"` 42 } 43 44 // Manager handles smart contract compilation. 45 type Manager struct { 46 basePath string 47 absoluteBasePath string 48 currentUser *user.User 49 } 50 51 // NewManager is the Manager constructor. 52 func NewManager(basePath string) (*Manager, error) { 53 _, filename, _, _ := runtime.Caller(0) 54 dir := path.Join(path.Dir(filename), "../../../") 55 absoluteBasePath := path.Join(dir, basePath) 56 57 currentUser, err := user.Current() 58 if err != nil { 59 return nil, err 60 } 61 62 return &Manager{ 63 basePath: basePath, 64 absoluteBasePath: absoluteBasePath, 65 currentUser: currentUser, 66 }, nil 67 } 68 69 // Run executes the compilation of smart contracts and generation of golang 70 // bindings. 71 func (cm *Manager) Run() error { 72 yamlFile, err := os.ReadFile(path.Join(cm.absoluteBasePath, "index.yaml")) 73 if err != nil { 74 return err 75 } 76 ci := &compileIndex{} 77 err = yaml.Unmarshal(yamlFile, ci) 78 if err != nil { 79 return err 80 } 81 82 g := new(errgroup.Group) 83 84 g.Go(func() error { 85 for _, item := range ci.CompileUnits.Parallel { 86 err = cm.parallelActions(item) 87 if err != nil { 88 return err 89 } 90 } 91 return nil 92 }) 93 94 g.Go(func() error { 95 for _, item := range ci.CompileUnits.Sequential { 96 err = cm.sequentialActions(item) 97 if err != nil { 98 return err 99 } 100 } 101 return nil 102 }) 103 104 return g.Wait() 105 } 106 107 // parallelActions performs compile and generate actions in parallel for a given 108 // compile unit. 109 func (cm *Manager) parallelActions(item CompileUnit) error { 110 entryPoint := path.Join(cm.basePath, item.InputPath) 111 file, err := os.Open(entryPoint) // #nosec G304 112 if err != nil { 113 return err 114 } 115 defer func() { 116 if err := file.Close(); err != nil { 117 log.Errorf("Could not close file %q, %v", file.Name(), err) 118 } 119 }() 120 121 fileInfo, err := file.Stat() 122 if err != nil { 123 return err 124 } 125 126 if !fileInfo.IsDir() { 127 return cm.fileActions(path.Base(file.Name()), item.SolcVersion, item.InputPath, item.OutputPath) 128 } 129 130 g := new(errgroup.Group) 131 132 g.Go(func() error { 133 return filepath.WalkDir(file.Name(), func(target string, info fs.DirEntry, err error) error { 134 if err != nil { 135 return err 136 } 137 if info == nil || info.IsDir() { 138 return nil 139 } 140 if filepath.Ext(target) != ".sol" { 141 return nil 142 } 143 fileName := strings.TrimSuffix(target, ".sol") 144 145 g.Go(func() error { 146 return cm.fileActions(path.Base(fileName), item.SolcVersion, item.InputPath, item.OutputPath) 147 }) 148 return nil 149 }) 150 }) 151 return g.Wait() 152 } 153 154 // sequentialActions performs compile and generate actions sequentially for a given 155 // compile unit. 156 func (cm *Manager) sequentialActions(item CompileUnit) error { 157 entryPoint := path.Join(cm.basePath, item.InputPath, fmt.Sprintf("%s.sol", item.Name)) 158 file, err := os.Open(entryPoint) // #nosec G304 159 if err != nil && err != os.ErrNotExist { 160 return err 161 } 162 defer func() { 163 if err := file.Close(); err != nil { 164 log.Errorf("Could not close file %q, %v", file.Name(), err) 165 } 166 }() 167 168 return cm.fileActions(item.Name, item.SolcVersion, item.InputPath, item.OutputPath) 169 } 170 171 func (cm *Manager) fileActions(name, solcVersion, inputPath, outputPath string) error { 172 err := cm.Compile(path.Base(name), solcVersion, inputPath, outputPath) 173 if err != nil { 174 return err 175 } 176 177 return cm.Abigen(path.Base(name), inputPath, outputPath) 178 } 179 180 // Compile invokes solc on the given sol file. 181 func (cm *Manager) Compile(name, solcVersion, inputPath, outputPath string) error { 182 log.Infof("Compiling %s.sol with version %s", path.Join(cm.basePath, inputPath, name), solcVersion) 183 184 solcImage := fmt.Sprintf("%s:%s", defaultSolcImage, solcVersion) 185 186 c := exec.Command( 187 "docker", "run", "--rm", 188 "--user", fmt.Sprintf("%s:%s", cm.currentUser.Uid, cm.currentUser.Gid), 189 "-v", fmt.Sprintf("%s:%s", cm.absoluteBasePath, containerBase), 190 solcImage, 191 "-", 192 fmt.Sprintf("%s.sol", path.Join(containerBase, inputPath, name)), 193 "-o", path.Join(containerBase, "bin", outputPath, name), 194 "--abi", "--bin", "--overwrite", "--optimize") // #nosec G204 195 196 envPath := os.Getenv("PATH") 197 c.Env = []string{fmt.Sprintf("PATH=%s", envPath)} 198 199 err := c.Run() 200 if err != nil { 201 return err 202 } 203 log.Infof("Compiler run successfully, artifacts can be found at %q", path.Join(cm.basePath, "bin", name)) 204 return nil 205 } 206 207 // Abigen generates bindings for the given file 208 func (cm *Manager) Abigen(name, inputPath, outputPath string) error { 209 log.Infof("Generating go code for %q...", name) 210 211 c := exec.Command( 212 "docker", "run", "--rm", 213 "--user", fmt.Sprintf("%s:%s", cm.currentUser.Uid, cm.currentUser.Gid), 214 "-v", fmt.Sprintf("%s:%s", cm.absoluteBasePath, containerBase), 215 defaultAbigenImage, 216 "abigen", 217 "--bin", path.Join(containerBase, "bin", outputPath, name, fmt.Sprintf("%s.bin", name)), 218 "--abi", path.Join(containerBase, "bin", outputPath, name, fmt.Sprintf("%s.abi", name)), 219 "--pkg", name, 220 "--out", path.Join(containerBase, "bin", outputPath, name, fmt.Sprintf("%s.go", name))) // #nosec G204 221 222 envPath := os.Getenv("PATH") 223 c.Env = []string{fmt.Sprintf("PATH=%s", envPath)} 224 225 err := c.Run() 226 if err != nil { 227 return err 228 } 229 log.Infof("Code generated at %q", path.Join(cm.basePath, "bin", outputPath, name, fmt.Sprintf("%s.go", name))) 230 return nil 231 }