Implements WASM smart contract compilation, optimization, and ABI generation across JavaScript/TypeScript, Python, Go, Rust, Flutter/Dart, Java, Kotlin, Swift, C, C++, C#/.NET, and Ruby. Features: - Optimization levels: None, Basic, Size, Aggressive - WASM section stripping with customizable options - Contract validation against VM requirements - ABI extraction and generation - Contract metadata handling - Gas estimation for deployment - Security analysis and recommendations - Size breakdown analysis Sub-clients: Contracts, ABI, Analysis, Validation
483 lines
14 KiB
Go
483 lines
14 KiB
Go
package compiler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// SynorCompiler is the main compiler client.
|
|
type SynorCompiler struct {
|
|
config CompilerConfig
|
|
httpClient *http.Client
|
|
closed atomic.Bool
|
|
|
|
// Sub-clients
|
|
Contracts *ContractsClient
|
|
Abi *AbiClient
|
|
Analysis *AnalysisClient
|
|
Validation *ValidationClient
|
|
}
|
|
|
|
// NewSynorCompiler creates a new compiler client.
|
|
func NewSynorCompiler(config CompilerConfig) *SynorCompiler {
|
|
if config.Endpoint == "" {
|
|
config.Endpoint = "https://compiler.synor.io/v1"
|
|
}
|
|
if config.Timeout == 0 {
|
|
config.Timeout = 60000
|
|
}
|
|
if config.OptimizationLevel == "" {
|
|
config.OptimizationLevel = OptimizationSize
|
|
}
|
|
|
|
c := &SynorCompiler{
|
|
config: config,
|
|
httpClient: &http.Client{
|
|
Timeout: time.Duration(config.Timeout) * time.Millisecond,
|
|
},
|
|
}
|
|
|
|
c.Contracts = &ContractsClient{compiler: c}
|
|
c.Abi = &AbiClient{compiler: c}
|
|
c.Analysis = &AnalysisClient{compiler: c}
|
|
c.Validation = &ValidationClient{compiler: c}
|
|
|
|
return c
|
|
}
|
|
|
|
// DefaultOptimizationLevel returns the default optimization level.
|
|
func (c *SynorCompiler) DefaultOptimizationLevel() OptimizationLevel {
|
|
return c.config.OptimizationLevel
|
|
}
|
|
|
|
// HealthCheck checks service health.
|
|
func (c *SynorCompiler) HealthCheck(ctx context.Context) (bool, error) {
|
|
var result map[string]interface{}
|
|
if err := c.get(ctx, "/health", &result); err != nil {
|
|
return false, nil
|
|
}
|
|
return result["status"] == "healthy", nil
|
|
}
|
|
|
|
// GetInfo returns service info.
|
|
func (c *SynorCompiler) GetInfo(ctx context.Context) (map[string]interface{}, error) {
|
|
var result map[string]interface{}
|
|
if err := c.get(ctx, "/info", &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Close closes the client.
|
|
func (c *SynorCompiler) Close() {
|
|
c.closed.Store(true)
|
|
}
|
|
|
|
// IsClosed returns whether the client is closed.
|
|
func (c *SynorCompiler) IsClosed() bool {
|
|
return c.closed.Load()
|
|
}
|
|
|
|
// Compile compiles WASM bytecode.
|
|
func (c *SynorCompiler) Compile(ctx context.Context, wasm []byte, opts *CompilationRequest) (*CompilationResult, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
|
|
body := map[string]interface{}{
|
|
"wasm": wasmBase64,
|
|
}
|
|
|
|
if opts != nil {
|
|
if opts.OptimizationLevel != nil {
|
|
body["optimization_level"] = *opts.OptimizationLevel
|
|
} else {
|
|
body["optimization_level"] = c.config.OptimizationLevel
|
|
}
|
|
if opts.StripOptions != nil {
|
|
body["strip_options"] = opts.StripOptions
|
|
}
|
|
if opts.UseWasmOpt != nil {
|
|
body["use_wasm_opt"] = *opts.UseWasmOpt
|
|
} else {
|
|
body["use_wasm_opt"] = c.config.UseWasmOpt
|
|
}
|
|
if opts.Validate != nil {
|
|
body["validate"] = *opts.Validate
|
|
} else {
|
|
body["validate"] = c.config.Validate
|
|
}
|
|
if opts.ExtractMetadata != nil {
|
|
body["extract_metadata"] = *opts.ExtractMetadata
|
|
} else {
|
|
body["extract_metadata"] = c.config.ExtractMetadata
|
|
}
|
|
if opts.GenerateAbi != nil {
|
|
body["generate_abi"] = *opts.GenerateAbi
|
|
} else {
|
|
body["generate_abi"] = c.config.GenerateAbi
|
|
}
|
|
} else {
|
|
body["optimization_level"] = c.config.OptimizationLevel
|
|
body["use_wasm_opt"] = c.config.UseWasmOpt
|
|
body["validate"] = c.config.Validate
|
|
body["extract_metadata"] = c.config.ExtractMetadata
|
|
body["generate_abi"] = c.config.GenerateAbi
|
|
}
|
|
|
|
var result CompilationResult
|
|
if err := c.post(ctx, "/compile", body, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *SynorCompiler) get(ctx context.Context, path string, result interface{}) error {
|
|
if c.closed.Load() {
|
|
return &CompilerError{Message: "Client has been closed", Code: "CLIENT_CLOSED"}
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", c.config.Endpoint+path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.setHeaders(req)
|
|
return c.doRequest(req, result)
|
|
}
|
|
|
|
func (c *SynorCompiler) post(ctx context.Context, path string, body interface{}, result interface{}) error {
|
|
if c.closed.Load() {
|
|
return &CompilerError{Message: "Client has been closed", Code: "CLIENT_CLOSED"}
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", c.config.Endpoint+path, bytes.NewReader(jsonBody))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.setHeaders(req)
|
|
return c.doRequest(req, result)
|
|
}
|
|
|
|
func (c *SynorCompiler) setHeaders(req *http.Request) {
|
|
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-SDK-Version", "go/0.1.0")
|
|
}
|
|
|
|
func (c *SynorCompiler) doRequest(req *http.Request, result interface{}) error {
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode >= 400 {
|
|
var errResp map[string]interface{}
|
|
if json.Unmarshal(respBody, &errResp) == nil {
|
|
msg, _ := errResp["message"].(string)
|
|
code, _ := errResp["code"].(string)
|
|
if msg == "" {
|
|
msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
|
|
}
|
|
return &CompilerError{Message: msg, Code: code, HTTPStatus: resp.StatusCode}
|
|
}
|
|
return &CompilerError{
|
|
Message: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(respBody)),
|
|
HTTPStatus: resp.StatusCode,
|
|
}
|
|
}
|
|
|
|
if result != nil && len(respBody) > 0 {
|
|
return json.Unmarshal(respBody, result)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ContractsClient is the contracts sub-client.
|
|
type ContractsClient struct {
|
|
compiler *SynorCompiler
|
|
}
|
|
|
|
// Compile compiles WASM bytecode with default settings.
|
|
func (c *ContractsClient) Compile(ctx context.Context, wasm []byte, opts *CompilationRequest) (*CompilationResult, error) {
|
|
return c.compiler.Compile(ctx, wasm, opts)
|
|
}
|
|
|
|
// CompileDev compiles with development settings.
|
|
func (c *ContractsClient) CompileDev(ctx context.Context, wasm []byte) (*CompilationResult, error) {
|
|
level := OptimizationNone
|
|
useWasmOpt := false
|
|
validate := true
|
|
return c.compiler.Compile(ctx, wasm, &CompilationRequest{
|
|
OptimizationLevel: &level,
|
|
UseWasmOpt: &useWasmOpt,
|
|
Validate: &validate,
|
|
})
|
|
}
|
|
|
|
// CompileProduction compiles with production settings.
|
|
func (c *ContractsClient) CompileProduction(ctx context.Context, wasm []byte) (*CompilationResult, error) {
|
|
level := OptimizationAggressive
|
|
useWasmOpt := true
|
|
validate := true
|
|
extractMetadata := true
|
|
generateAbi := true
|
|
return c.compiler.Compile(ctx, wasm, &CompilationRequest{
|
|
OptimizationLevel: &level,
|
|
UseWasmOpt: &useWasmOpt,
|
|
Validate: &validate,
|
|
ExtractMetadata: &extractMetadata,
|
|
GenerateAbi: &generateAbi,
|
|
})
|
|
}
|
|
|
|
// Get gets a compiled contract by ID.
|
|
func (c *ContractsClient) Get(ctx context.Context, contractID string) (*CompilationResult, error) {
|
|
var result CompilationResult
|
|
if err := c.compiler.get(ctx, "/contracts/"+contractID, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// List lists compiled contracts.
|
|
func (c *ContractsClient) List(ctx context.Context, limit, offset *int) ([]CompilationResult, error) {
|
|
path := "/contracts"
|
|
if limit != nil || offset != nil {
|
|
path += "?"
|
|
if limit != nil {
|
|
path += fmt.Sprintf("limit=%d", *limit)
|
|
if offset != nil {
|
|
path += "&"
|
|
}
|
|
}
|
|
if offset != nil {
|
|
path += fmt.Sprintf("offset=%d", *offset)
|
|
}
|
|
}
|
|
|
|
var result struct {
|
|
Contracts []CompilationResult `json:"contracts"`
|
|
}
|
|
if err := c.compiler.get(ctx, path, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result.Contracts, nil
|
|
}
|
|
|
|
// GetCode gets optimized bytecode for a contract.
|
|
func (c *ContractsClient) GetCode(ctx context.Context, contractID string) ([]byte, error) {
|
|
var result struct {
|
|
Code string `json:"code"`
|
|
}
|
|
if err := c.compiler.get(ctx, "/contracts/"+contractID+"/code", &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return base64.StdEncoding.DecodeString(result.Code)
|
|
}
|
|
|
|
// AbiClient is the ABI sub-client.
|
|
type AbiClient struct {
|
|
compiler *SynorCompiler
|
|
}
|
|
|
|
// Extract extracts ABI from WASM bytecode.
|
|
func (c *AbiClient) Extract(ctx context.Context, wasm []byte) (*ContractAbi, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result ContractAbi
|
|
if err := c.compiler.post(ctx, "/abi/extract", map[string]string{"wasm": wasmBase64}, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// Get gets ABI for a compiled contract.
|
|
func (c *AbiClient) Get(ctx context.Context, contractID string) (*ContractAbi, error) {
|
|
var result ContractAbi
|
|
if err := c.compiler.get(ctx, "/contracts/"+contractID+"/abi", &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// EncodeCall encodes a function call.
|
|
func (c *AbiClient) EncodeCall(ctx context.Context, functionAbi *FunctionAbi, args []interface{}) (string, error) {
|
|
var result struct {
|
|
Data string `json:"data"`
|
|
}
|
|
if err := c.compiler.post(ctx, "/abi/encode", map[string]interface{}{
|
|
"function": functionAbi,
|
|
"args": args,
|
|
}, &result); err != nil {
|
|
return "", err
|
|
}
|
|
return result.Data, nil
|
|
}
|
|
|
|
// DecodeResult decodes a function result.
|
|
func (c *AbiClient) DecodeResult(ctx context.Context, functionAbi *FunctionAbi, data string) ([]interface{}, error) {
|
|
var result struct {
|
|
Values []interface{} `json:"values"`
|
|
}
|
|
if err := c.compiler.post(ctx, "/abi/decode", map[string]interface{}{
|
|
"function": functionAbi,
|
|
"data": data,
|
|
}, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result.Values, nil
|
|
}
|
|
|
|
// AnalysisClient is the analysis sub-client.
|
|
type AnalysisClient struct {
|
|
compiler *SynorCompiler
|
|
}
|
|
|
|
// Analyze analyzes WASM bytecode.
|
|
func (c *AnalysisClient) Analyze(ctx context.Context, wasm []byte) (*ContractAnalysis, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result ContractAnalysis
|
|
if err := c.compiler.post(ctx, "/analysis", map[string]string{"wasm": wasmBase64}, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// Get gets analysis for a compiled contract.
|
|
func (c *AnalysisClient) Get(ctx context.Context, contractID string) (*ContractAnalysis, error) {
|
|
var result ContractAnalysis
|
|
if err := c.compiler.get(ctx, "/contracts/"+contractID+"/analysis", &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// ExtractMetadata extracts metadata from WASM bytecode.
|
|
func (c *AnalysisClient) ExtractMetadata(ctx context.Context, wasm []byte) (*ContractMetadata, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result ContractMetadata
|
|
if err := c.compiler.post(ctx, "/analysis/metadata", map[string]string{"wasm": wasmBase64}, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// ExtractSourceMap extracts source map from WASM bytecode.
|
|
func (c *AnalysisClient) ExtractSourceMap(ctx context.Context, wasm []byte) (*SourceMap, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result struct {
|
|
SourceMap *SourceMap `json:"source_map"`
|
|
}
|
|
if err := c.compiler.post(ctx, "/analysis/source-map", map[string]string{"wasm": wasmBase64}, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result.SourceMap, nil
|
|
}
|
|
|
|
// EstimateDeployGas estimates gas for contract deployment.
|
|
func (c *AnalysisClient) EstimateDeployGas(ctx context.Context, wasm []byte) (int64, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result struct {
|
|
Gas int64 `json:"gas"`
|
|
}
|
|
if err := c.compiler.post(ctx, "/analysis/estimate-gas", map[string]string{"wasm": wasmBase64}, &result); err != nil {
|
|
return 0, err
|
|
}
|
|
return result.Gas, nil
|
|
}
|
|
|
|
// SecurityScan performs security analysis.
|
|
func (c *AnalysisClient) SecurityScan(ctx context.Context, wasm []byte) (*SecurityAnalysis, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result struct {
|
|
Security SecurityAnalysis `json:"security"`
|
|
}
|
|
if err := c.compiler.post(ctx, "/analysis/security", map[string]string{"wasm": wasmBase64}, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result.Security, nil
|
|
}
|
|
|
|
// ValidationClient is the validation sub-client.
|
|
type ValidationClient struct {
|
|
compiler *SynorCompiler
|
|
}
|
|
|
|
// Validate validates WASM bytecode against VM requirements.
|
|
func (c *ValidationClient) Validate(ctx context.Context, wasm []byte) (*ValidationResult, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result ValidationResult
|
|
if err := c.compiler.post(ctx, "/validate", map[string]string{"wasm": wasmBase64}, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// IsValid checks if WASM is valid.
|
|
func (c *ValidationClient) IsValid(ctx context.Context, wasm []byte) (bool, error) {
|
|
result, err := c.Validate(ctx, wasm)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return result.Valid, nil
|
|
}
|
|
|
|
// GetErrors gets validation errors.
|
|
func (c *ValidationClient) GetErrors(ctx context.Context, wasm []byte) ([]string, error) {
|
|
result, err := c.Validate(ctx, wasm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
errors := make([]string, len(result.Errors))
|
|
for i, e := range result.Errors {
|
|
errors[i] = e.Message
|
|
}
|
|
return errors, nil
|
|
}
|
|
|
|
// ValidateExports validates contract exports.
|
|
func (c *ValidationClient) ValidateExports(ctx context.Context, wasm []byte, requiredExports []string) (bool, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result struct {
|
|
Valid bool `json:"valid"`
|
|
}
|
|
if err := c.compiler.post(ctx, "/validate/exports", map[string]interface{}{
|
|
"wasm": wasmBase64,
|
|
"required_exports": requiredExports,
|
|
}, &result); err != nil {
|
|
return false, err
|
|
}
|
|
return result.Valid, nil
|
|
}
|
|
|
|
// ValidateMemory validates memory limits.
|
|
func (c *ValidationClient) ValidateMemory(ctx context.Context, wasm []byte, maxPages int) (bool, error) {
|
|
wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
|
|
var result struct {
|
|
Valid bool `json:"valid"`
|
|
}
|
|
if err := c.compiler.post(ctx, "/validate/memory", map[string]interface{}{
|
|
"wasm": wasmBase64,
|
|
"max_pages": maxPages,
|
|
}, &result); err != nil {
|
|
return false, err
|
|
}
|
|
return result.Valid, nil
|
|
}
|