github.com/mavryk-network/mvgo@v1.19.9/internal/compose/alpha/parse.go (about) 1 // Copyright (c) 2023 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc, abdul@blockwatch.cc 3 4 package alpha 5 6 import ( 7 "encoding" 8 "encoding/hex" 9 "encoding/json" 10 "fmt" 11 "path/filepath" 12 "strconv" 13 "strings" 14 15 "github.com/mavryk-network/mvgo/contract/bind" 16 "github.com/mavryk-network/mvgo/internal/compose" 17 "github.com/mavryk-network/mvgo/mavryk" 18 "github.com/mavryk-network/mvgo/micheline" 19 20 "github.com/pkg/errors" 21 ) 22 23 func ParseScript(ctx compose.Context, task Task) (*micheline.Script, error) { 24 var ( 25 script *micheline.Script 26 err error 27 ) 28 if task.Script.ValueSource.IsUsed() { 29 script, err = loadSource[micheline.Script](ctx, task.Script.ValueSource) 30 if err != nil { 31 return nil, errors.Wrap(err, "loading script") 32 } 33 } else { 34 var ( 35 code *micheline.Code 36 store *micheline.Prim 37 ) 38 if task.Script.Code == nil || !task.Script.Code.ValueSource.IsUsed() { 39 return nil, fmt.Errorf("missing script code source") 40 } 41 code, err = loadSource[micheline.Code](ctx, task.Script.Code.ValueSource) 42 if err != nil { 43 return nil, errors.Wrap(err, "loading script code") 44 } 45 if task.Script.Storage == nil || (!task.Script.Storage.ValueSource.IsUsed() && task.Script.Storage.Args == nil) { 46 return nil, fmt.Errorf("missing initial storage source") 47 } 48 if task.Script.Storage.Args != nil { 49 // processed below 50 store = &micheline.Prim{} 51 } else { 52 store, err = loadSource[micheline.Prim](ctx, task.Script.Storage.ValueSource) 53 if err != nil { 54 return nil, errors.Wrap(err, "loading initial storage") 55 } 56 } 57 script = &micheline.Script{ 58 Code: *code, 59 Storage: *store, 60 } 61 } 62 if !script.IsValid() { 63 return nil, fmt.Errorf("invalid script") 64 } 65 66 // Note: code patching is not supported 67 68 // patch storage 69 if task.Script.Storage != nil { 70 // ctx.Log.Infof("Storage Type: %s", script.Code.Storage.Dump()) 71 // ctx.Log.Infof("Original Storage: %s", script.Storage.Dump()) 72 for _, p := range task.Script.Storage.Patch { 73 if p.Path == nil { 74 ctx.Log.Debugf("Resolving storage path for %q...", *p.Key) 75 idx, ok := script.StorageType().LabelIndex(*p.Key) 76 if !ok { 77 return nil, fmt.Errorf("storage key %q not found", *p.Key) 78 } 79 s := idxToPath(idx) 80 p.Path = &s 81 } 82 ctx.Log.Debugf("Patching script storage...") 83 err = patch(ctx, p, func(path string, oc micheline.OpCode, prim micheline.Prim) error { 84 ctx.Log.Debugf("> path=%s type=%s val=%s", path, oc, prim.Dump()) 85 return script.Storage.SetPath(path, prim) 86 }) 87 if err != nil { 88 return nil, errors.Wrap(err, "patching storage") 89 } 90 } 91 if task.Script.Storage.Args != nil { 92 // replace variables in args 93 if _, _, err := resolveArgs(ctx, task.Script.Storage.Args); err != nil { 94 return nil, err 95 } 96 // map args to storage spec 97 // ctx.Log.Infof("Args: %#v", task.Script.Storage.Args) 98 // ctx.Log.Infof("Typedef: %s", script.StorageType().Typedef("")) 99 script.Storage, err = script.StorageType().Typedef("").Marshal(task.Script.Storage.Args, false) 100 if err != nil { 101 return nil, err 102 } 103 // ctx.Log.Infof("Gen: %s", script.Storage.Dump()) 104 // ctx.Log.Infof("Gen: %#v", script.Storage) 105 // buf, err := script.Storage.MarshalBinary() 106 // if err != nil { 107 // return nil, err 108 // } 109 // ctx.Log.Infof("Gen: %s", mavryk.HexBytes(buf)) 110 } 111 } 112 if !script.Storage.IsValid() { 113 return nil, fmt.Errorf("invalid storage %s", script.Storage.Dump()) 114 } 115 116 return script, nil 117 } 118 119 func ParseParams(ctx compose.Context, task Task) (*micheline.Prim, error) { 120 var ( 121 params *micheline.Prim 122 err error 123 ) 124 if task.Params.ValueSource.IsUsed() { 125 params, err = loadSource[micheline.Prim](ctx, task.Params.ValueSource) 126 if err != nil { 127 return nil, err 128 } 129 } 130 for _, p := range task.Params.Patch { 131 if p.Path == nil { 132 ctx.Log.Debugf("Resolving destination script for %s...", task.Destination) 133 // load script 134 addr, err := mavryk.ParseAddress(task.Destination) 135 if err != nil { 136 return nil, err 137 } 138 script, err := ctx.ResolveScript(addr) 139 if err != nil { 140 return nil, err 141 } 142 ctx.Log.Debugf("Resolving storage path for %q...", *p.Key) 143 idx, ok := script.StorageType().LabelIndex(*p.Key) 144 if !ok { 145 return nil, fmt.Errorf("storage key %q not found", *p.Key) 146 } 147 s := idxToPath(idx) 148 p.Path = &s 149 } 150 151 ctx.Log.Debugf("Patching params...") 152 err = patch(ctx, p, func(path string, oc micheline.OpCode, prim micheline.Prim) error { 153 ctx.Log.Debugf("> path=%s type=%s val=%s", path, oc, prim.Dump()) 154 return params.SetPath(path, prim) 155 }) 156 if err != nil { 157 return nil, errors.Wrap(err, "patching params") 158 } 159 } 160 if task.Params.Args != nil { 161 // replace variables in args 162 if _, _, err := resolveArgs(ctx, task.Params.Args); err != nil { 163 return nil, err 164 } 165 addr, err := ctx.ResolveAddress(task.Destination) 166 if err != nil { 167 return nil, err 168 } 169 // load script 170 ctx.Log.Debugf("Resolving destination script for %s...", addr) 171 script, err := ctx.ResolveScript(addr) 172 if err != nil { 173 return nil, err 174 } 175 // map args to entrypoint spec 176 eps, err := script.Entrypoints(true) 177 if err != nil { 178 return nil, err 179 } 180 ep, ok := eps[task.Params.Entrypoint] 181 if !ok { 182 return nil, fmt.Errorf("entrypoint %q not found, have %#v", task.Params.Entrypoint, eps) 183 } 184 // marshal to prim tree 185 // Note: be mindful of the way entrypoint typedefs are structured: 186 // - 1 arg: use scalar value in ep.Typedef[0] 187 // - >1 arg: use entire list in ep.Typedef but wrap into struct 188 ctx.Log.Debugf("Marshaling params...") 189 // ctx.Log.Debugf("EP prim=%s", ep.Prim.Dump()) 190 // ctx.Log.Debugf("EP type=%s", ep.Typedef) 191 typ := ep.Typedef[0] 192 if len(ep.Typedef) > 1 { 193 typ = micheline.Typedef{ 194 Name: micheline.CONST_ENTRYPOINT, 195 Type: micheline.TypeStruct, 196 Args: ep.Typedef, 197 } 198 } 199 // ctx.Log.Debugf("USE type=%s", typ) 200 prim, err := typ.Marshal(task.Params.Args, true) 201 if err != nil { 202 return nil, errors.Wrap(err, "marshal params") 203 } 204 ctx.Log.Debugf("Result %s", prim.Dump()) 205 params = &prim 206 } 207 return params, nil 208 } 209 210 func loadSource[T any](ctx compose.Context, src ValueSource) (val *T, err error) { 211 switch { 212 case src.Value != "": 213 val = new(T) 214 if !isHex(src.Value) { 215 err = json.Unmarshal([]byte(src.Value), val) 216 } else { 217 var buf []byte 218 buf, err = hex.DecodeString(src.Value) 219 if err == nil { 220 x := any(val) 221 if u, ok := x.(encoding.BinaryUnmarshaler); ok { 222 err = u.UnmarshalBinary(buf) 223 } else { 224 err = fmt.Errorf("type %T does not implement encoding.BinaryUnmarshaler", val) 225 } 226 } 227 } 228 case src.File != "": 229 fname := filepath.Join(ctx.Filepath(), src.File) 230 val, err = compose.ReadJsonFile[T](fname) 231 case src.Url != "": 232 val, err = compose.Fetch[T](ctx, src.Url) 233 default: 234 err = fmt.Errorf("invalid source") 235 } 236 return 237 } 238 239 func patch(ctx compose.Context, p Patch, updater func(string, micheline.OpCode, micheline.Prim) error) error { 240 opCode, err := micheline.ParseOpCode(p.Type) 241 if err != nil { 242 return err 243 } 244 if !opCode.IsTypeCode() { 245 return fmt.Errorf("%s is not a valid type", p.Type) 246 } 247 str, err := ctx.ResolveString(*p.Value) 248 if err != nil { 249 return err 250 } 251 val, err := compose.ParseValue(opCode, str) 252 if err != nil { 253 return err 254 } 255 prim, err := bind.MarshalPrim(val, p.Optimized) 256 if err != nil { 257 return err 258 } 259 return updater(*p.Path, opCode, prim) 260 } 261 262 func idxToPath(idx []int) string { 263 if len(idx) == 0 { 264 return "" 265 } 266 var b strings.Builder 267 b.WriteString(strconv.Itoa(idx[0])) 268 for _, v := range idx[1:] { 269 b.WriteByte('/') 270 b.WriteString(strconv.Itoa(v)) 271 } 272 return b.String() 273 } 274 275 func resolveArgs(ctx compose.Context, args any) (string, bool, error) { 276 if args == nil { 277 return "", false, nil 278 } 279 switch val := args.(type) { 280 case map[string]any: 281 for n, v := range val { 282 s, ok, err := resolveArgs(ctx, v) 283 if err != nil { 284 return "", false, fmt.Errorf("arg %s: %v", n, err) 285 } 286 // try convert key (may be composite key) 287 if newKey, ok := ctx.ResolveNestedVars(n); ok { 288 delete(val, n) 289 n = newKey 290 val[n] = v 291 } 292 if ok { 293 val[n] = s 294 } 295 } 296 case []any: 297 for i, v := range val { 298 s, ok, err := resolveArgs(ctx, v) 299 if err != nil { 300 return "", false, fmt.Errorf("arg %d: %v", i, err) 301 } 302 if ok { 303 val[i] = s 304 } 305 } 306 case string: 307 if s, err := ctx.ResolveString(val); err != nil { 308 return "", false, err 309 } else { 310 return s, true, nil 311 } 312 case int: 313 // skip 314 case bool: 315 // skip 316 default: 317 return "", false, fmt.Errorf("unsupported arg type %T", args) 318 } 319 return "", false, nil 320 } 321 322 func isHex(s string) bool { 323 _, err := hex.DecodeString(s) 324 return err == nil 325 } 326 327 // func isJson(s string) bool { 328 // if len(s) == 0 { 329 // return true 330 // } 331 // switch s[0] { 332 // case '{', '[', '"', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': 333 // return true 334 // default: 335 // return false 336 // } 337 // }