diff --git a/sdk/c/include/synor/compiler.h b/sdk/c/include/synor/compiler.h new file mode 100644 index 0000000..e95d9fc --- /dev/null +++ b/sdk/c/include/synor/compiler.h @@ -0,0 +1,520 @@ +/** + * Synor Compiler SDK for C + * + * Smart contract compilation, optimization, and ABI generation. + */ + +#ifndef SYNOR_COMPILER_H +#define SYNOR_COMPILER_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Opaque Types + * ============================================================================ */ + +typedef struct synor_compiler_client synor_compiler_client_t; +typedef struct synor_compilation_result synor_compilation_result_t; +typedef struct synor_contract_abi synor_contract_abi_t; +typedef struct synor_contract_analysis synor_contract_analysis_t; +typedef struct synor_validation_result synor_validation_result_t; + +/* ============================================================================ + * Enumerations + * ============================================================================ */ + +/** Optimization level */ +typedef enum { + SYNOR_COMPILER_OPT_NONE = 0, /**< No optimization */ + SYNOR_COMPILER_OPT_BASIC = 1, /**< Basic optimizations (stripping only) */ + SYNOR_COMPILER_OPT_SIZE = 2, /**< Optimize for size (-Os equivalent) */ + SYNOR_COMPILER_OPT_AGGRESSIVE = 3 /**< Aggressive optimization (-O3 -Os equivalent) */ +} synor_compiler_optimization_level_t; + +/** Error codes */ +typedef enum { + SYNOR_COMPILER_OK = 0, + SYNOR_COMPILER_ERROR_INVALID_ARGUMENT = 1, + SYNOR_COMPILER_ERROR_NETWORK = 2, + SYNOR_COMPILER_ERROR_AUTH = 3, + SYNOR_COMPILER_ERROR_NOT_FOUND = 4, + SYNOR_COMPILER_ERROR_TIMEOUT = 5, + SYNOR_COMPILER_ERROR_INTERNAL = 6, + SYNOR_COMPILER_ERROR_CLIENT_CLOSED = 7, + SYNOR_COMPILER_ERROR_PARSE = 8, + SYNOR_COMPILER_ERROR_VALIDATION = 9, + SYNOR_COMPILER_ERROR_OPTIMIZATION = 10, + SYNOR_COMPILER_ERROR_CONTRACT_TOO_LARGE = 11 +} synor_compiler_error_t; + +/* ============================================================================ + * Structures + * ============================================================================ */ + +/** Strip options */ +typedef struct { + bool strip_debug; /**< Strip debug sections */ + bool strip_producers; /**< Strip producer sections */ + bool strip_names; /**< Strip name sections */ + bool strip_custom; /**< Strip custom sections */ + const char** preserve_sections; /**< Sections to preserve */ + size_t preserve_sections_count; + bool strip_unused; /**< Strip unused functions */ +} synor_compiler_strip_options_t; + +/** Compiler configuration */ +typedef struct { + const char* api_key; + const char* endpoint; /**< Default: "https://compiler.synor.io/v1" */ + uint32_t timeout_ms; /**< Default: 60000 */ + uint32_t retries; /**< Default: 3 */ + synor_compiler_optimization_level_t optimization_level; /**< Default: SIZE */ + synor_compiler_strip_options_t* strip_options; /**< Optional */ + size_t max_contract_size; /**< Default: 262144 (256 KB) */ + bool use_wasm_opt; /**< Default: true */ + bool validate; /**< Default: true */ + bool extract_metadata; /**< Default: true */ + bool generate_abi; /**< Default: true */ + bool debug; /**< Default: false */ +} synor_compiler_config_t; + +/** Compilation request */ +typedef struct { + synor_compiler_optimization_level_t* optimization_level; /**< Optional */ + synor_compiler_strip_options_t* strip_options; /**< Optional */ + bool* use_wasm_opt; /**< Optional */ + bool* validate; /**< Optional */ + bool* extract_metadata; /**< Optional */ + bool* generate_abi; /**< Optional */ +} synor_compiler_request_t; + +/** Validation error */ +typedef struct { + char code[64]; + char message[256]; + char* location; /**< Optional */ +} synor_compiler_validation_error_t; + +/** Contract metadata */ +typedef struct { + char* name; /**< Optional */ + char* version; /**< Optional */ + char** authors; + size_t authors_count; + char* description; /**< Optional */ + char* license; /**< Optional */ + char* repository; /**< Optional */ + uint64_t build_timestamp; + char* rust_version; /**< Optional */ + char* sdk_version; /**< Optional */ +} synor_compiler_metadata_t; + +/** Compilation result info */ +typedef struct { + char contract_id[64]; + char code_hash[66]; + size_t original_size; + size_t optimized_size; + double size_reduction; + uint64_t estimated_deploy_gas; + bool validation_valid; + size_t warning_count; +} synor_compiler_result_info_t; + +/** Function ABI */ +typedef struct { + char name[128]; + char selector[10]; /* 4 bytes hex + null */ + size_t input_count; + size_t output_count; + bool view; + bool payable; + char* doc; +} synor_compiler_function_abi_t; + +/** ABI info */ +typedef struct { + char name[128]; + char version[32]; + size_t function_count; + size_t event_count; + size_t error_count; +} synor_compiler_abi_info_t; + +/** Size breakdown */ +typedef struct { + size_t code; + size_t data; + size_t types; + size_t functions; + size_t memory; + size_t table; + size_t exports; + size_t imports; + size_t custom; + size_t total; +} synor_compiler_size_breakdown_t; + +/** Security issue */ +typedef struct { + char severity[16]; /* low, medium, high, critical */ + char type[64]; + char description[256]; + char* location; +} synor_compiler_security_issue_t; + +/** Security analysis */ +typedef struct { + int score; /* 0-100 */ + synor_compiler_security_issue_t* issues; + size_t issue_count; + char** recommendations; + size_t recommendation_count; +} synor_compiler_security_analysis_t; + +/* ============================================================================ + * Client Lifecycle + * ============================================================================ */ + +/** + * Create a new compiler client + * + * @param config Configuration options + * @return New client handle, or NULL on error + */ +synor_compiler_client_t* synor_compiler_client_new(const synor_compiler_config_t* config); + +/** + * Close and free a client + * + * @param client Client to close + */ +void synor_compiler_client_free(synor_compiler_client_t* client); + +/** + * Check if client is closed + * + * @param client Client handle + * @return true if closed + */ +bool synor_compiler_client_is_closed(const synor_compiler_client_t* client); + +/** + * Health check + * + * @param client Client handle + * @param healthy Output parameter for health status + * @return Error code + */ +synor_compiler_error_t synor_compiler_health_check(synor_compiler_client_t* client, bool* healthy); + +/* ============================================================================ + * Compilation Operations + * ============================================================================ */ + +/** + * Compile WASM bytecode + * + * @param client Client handle + * @param wasm WASM bytecode + * @param wasm_len Length of WASM bytecode + * @param request Optional compilation request parameters + * @param result Output parameter for compilation result + * @return Error code + */ +synor_compiler_error_t synor_compiler_compile( + synor_compiler_client_t* client, + const uint8_t* wasm, + size_t wasm_len, + const synor_compiler_request_t* request, + synor_compilation_result_t** result +); + +/** + * Get compilation result info + * + * @param result Compilation result handle + * @param info Output parameter for result info + * @return Error code + */ +synor_compiler_error_t synor_compiler_result_get_info( + const synor_compilation_result_t* result, + synor_compiler_result_info_t* info +); + +/** + * Get optimized bytecode from result + * + * @param result Compilation result handle + * @param code Output buffer for bytecode + * @param code_len Input: buffer size, Output: actual bytecode length + * @return Error code + */ +synor_compiler_error_t synor_compiler_result_get_code( + const synor_compilation_result_t* result, + uint8_t* code, + size_t* code_len +); + +/** + * Get metadata from result + * + * @param result Compilation result handle + * @param metadata Output parameter for metadata + * @return Error code + */ +synor_compiler_error_t synor_compiler_result_get_metadata( + const synor_compilation_result_t* result, + synor_compiler_metadata_t* metadata +); + +/** + * Free compilation result + * + * @param result Result to free + */ +void synor_compiler_result_free(synor_compilation_result_t* result); + +/* ============================================================================ + * ABI Operations + * ============================================================================ */ + +/** + * Extract ABI from WASM bytecode + * + * @param client Client handle + * @param wasm WASM bytecode + * @param wasm_len Length of WASM bytecode + * @param abi Output parameter for ABI + * @return Error code + */ +synor_compiler_error_t synor_compiler_abi_extract( + synor_compiler_client_t* client, + const uint8_t* wasm, + size_t wasm_len, + synor_contract_abi_t** abi +); + +/** + * Get ABI info + * + * @param abi ABI handle + * @param info Output parameter for ABI info + * @return Error code + */ +synor_compiler_error_t synor_compiler_abi_get_info( + const synor_contract_abi_t* abi, + synor_compiler_abi_info_t* info +); + +/** + * Get function from ABI by index + * + * @param abi ABI handle + * @param index Function index + * @param func Output parameter for function + * @return Error code + */ +synor_compiler_error_t synor_compiler_abi_get_function( + const synor_contract_abi_t* abi, + size_t index, + synor_compiler_function_abi_t* func +); + +/** + * Find function by name + * + * @param abi ABI handle + * @param name Function name + * @param func Output parameter for function (NULL if not found) + * @return Error code + */ +synor_compiler_error_t synor_compiler_abi_find_function( + const synor_contract_abi_t* abi, + const char* name, + synor_compiler_function_abi_t* func +); + +/** + * Free ABI + * + * @param abi ABI to free + */ +void synor_compiler_abi_free(synor_contract_abi_t* abi); + +/* ============================================================================ + * Analysis Operations + * ============================================================================ */ + +/** + * Analyze WASM bytecode + * + * @param client Client handle + * @param wasm WASM bytecode + * @param wasm_len Length of WASM bytecode + * @param analysis Output parameter for analysis + * @return Error code + */ +synor_compiler_error_t synor_compiler_analyze( + synor_compiler_client_t* client, + const uint8_t* wasm, + size_t wasm_len, + synor_contract_analysis_t** analysis +); + +/** + * Get size breakdown from analysis + * + * @param analysis Analysis handle + * @param breakdown Output parameter for size breakdown + * @return Error code + */ +synor_compiler_error_t synor_compiler_analysis_get_size_breakdown( + const synor_contract_analysis_t* analysis, + synor_compiler_size_breakdown_t* breakdown +); + +/** + * Get security analysis + * + * @param analysis Analysis handle + * @param security Output parameter for security analysis + * @return Error code + */ +synor_compiler_error_t synor_compiler_analysis_get_security( + const synor_contract_analysis_t* analysis, + synor_compiler_security_analysis_t* security +); + +/** + * Estimate deployment gas + * + * @param client Client handle + * @param wasm WASM bytecode + * @param wasm_len Length of WASM bytecode + * @param gas Output parameter for gas estimate + * @return Error code + */ +synor_compiler_error_t synor_compiler_estimate_deploy_gas( + synor_compiler_client_t* client, + const uint8_t* wasm, + size_t wasm_len, + uint64_t* gas +); + +/** + * Free analysis + * + * @param analysis Analysis to free + */ +void synor_compiler_analysis_free(synor_contract_analysis_t* analysis); + +/* ============================================================================ + * Validation Operations + * ============================================================================ */ + +/** + * Validate WASM bytecode + * + * @param client Client handle + * @param wasm WASM bytecode + * @param wasm_len Length of WASM bytecode + * @param result Output parameter for validation result + * @return Error code + */ +synor_compiler_error_t synor_compiler_validate( + synor_compiler_client_t* client, + const uint8_t* wasm, + size_t wasm_len, + synor_validation_result_t** result +); + +/** + * Check if validation passed + * + * @param result Validation result handle + * @param valid Output parameter for validity + * @return Error code + */ +synor_compiler_error_t synor_compiler_validation_is_valid( + const synor_validation_result_t* result, + bool* valid +); + +/** + * Get validation error count + * + * @param result Validation result handle + * @param count Output parameter for error count + * @return Error code + */ +synor_compiler_error_t synor_compiler_validation_get_error_count( + const synor_validation_result_t* result, + size_t* count +); + +/** + * Get validation error by index + * + * @param result Validation result handle + * @param index Error index + * @param error Output parameter for error + * @return Error code + */ +synor_compiler_error_t synor_compiler_validation_get_error( + const synor_validation_result_t* result, + size_t index, + synor_compiler_validation_error_t* error +); + +/** + * Free validation result + * + * @param result Result to free + */ +void synor_compiler_validation_free(synor_validation_result_t* result); + +/* ============================================================================ + * Utility Functions + * ============================================================================ */ + +/** + * Get error message + * + * @param error Error code + * @return Error message string + */ +const char* synor_compiler_error_message(synor_compiler_error_t error); + +/** + * Free metadata + * + * @param metadata Metadata to free + */ +void synor_compiler_metadata_free(synor_compiler_metadata_t* metadata); + +/** + * Free security analysis + * + * @param security Security analysis to free + */ +void synor_compiler_security_free(synor_compiler_security_analysis_t* security); + +/* ============================================================================ + * Constants + * ============================================================================ */ + +#define SYNOR_COMPILER_MAX_CONTRACT_SIZE 262144 /* 256 KB */ +#define SYNOR_COMPILER_MAX_MEMORY_PAGES 16 + +#ifdef __cplusplus +} +#endif + +#endif /* SYNOR_COMPILER_H */ diff --git a/sdk/cpp/include/synor/compiler.hpp b/sdk/cpp/include/synor/compiler.hpp new file mode 100644 index 0000000..66b5f62 --- /dev/null +++ b/sdk/cpp/include/synor/compiler.hpp @@ -0,0 +1,423 @@ +/** + * Synor Compiler SDK for C++ + * + * Smart contract compilation, optimization, and ABI generation. + */ + +#ifndef SYNOR_COMPILER_HPP +#define SYNOR_COMPILER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace synor { +namespace compiler { + +/* ============================================================================ + * Enumerations + * ============================================================================ */ + +/** Optimization level */ +enum class OptimizationLevel { + None, ///< No optimization + Basic, ///< Basic optimizations (stripping only) + Size, ///< Optimize for size (-Os equivalent) + Aggressive ///< Aggressive optimization (-O3 -Os equivalent) +}; + +/* ============================================================================ + * Exception + * ============================================================================ */ + +class CompilerError : public std::runtime_error { +public: + CompilerError(const std::string& message, + const std::optional& code = std::nullopt, + const std::optional& status = std::nullopt) + : std::runtime_error(message), code_(code), status_(status) {} + + const std::optional& code() const { return code_; } + const std::optional& status() const { return status_; } + +private: + std::optional code_; + std::optional status_; +}; + +/* ============================================================================ + * Data Types + * ============================================================================ */ + +/** Strip options */ +struct StripOptions { + bool strip_debug = true; + bool strip_producers = true; + bool strip_names = true; + bool strip_custom = true; + std::vector preserve_sections; + bool strip_unused = true; +}; + +/** Compiler configuration */ +struct CompilerConfig { + std::string api_key; + std::string endpoint = "https://compiler.synor.io/v1"; + std::chrono::milliseconds timeout{60000}; + int retries = 3; + bool debug = false; + OptimizationLevel optimization_level = OptimizationLevel::Size; + std::optional strip_options; + size_t max_contract_size = 256 * 1024; + bool use_wasm_opt = true; + bool validate = true; + bool extract_metadata = true; + bool generate_abi = true; +}; + +/** Validation error */ +struct ValidationError { + std::string code; + std::string message; + std::optional location; +}; + +/** Validation result */ +struct ValidationResult { + bool valid; + std::vector errors; + std::vector warnings; + size_t export_count = 0; + size_t import_count = 0; + size_t function_count = 0; + std::pair> memory_pages = {0, std::nullopt}; +}; + +/** Contract metadata */ +struct ContractMetadata { + std::optional name; + std::optional version; + std::vector authors; + std::optional description; + std::optional license; + std::optional repository; + std::optional build_timestamp; + std::optional rust_version; + std::optional sdk_version; + std::map custom; +}; + +/** Type information */ +struct TypeInfo { + std::string kind; + std::optional size; + std::unique_ptr element; + std::unique_ptr inner; + std::vector elements; + std::optional name; + std::vector> fields; + + std::string type_name() const; +}; + +/** Parameter ABI */ +struct ParamAbi { + std::string name; + TypeInfo type; + bool indexed = false; +}; + +/** Function ABI */ +struct FunctionAbi { + std::string name; + std::string selector; + std::vector inputs; + std::vector outputs; + bool view = false; + bool payable = false; + std::optional doc; +}; + +/** Event ABI */ +struct EventAbi { + std::string name; + std::string topic; + std::vector params; + std::optional doc; +}; + +/** Error ABI */ +struct ErrorAbi { + std::string name; + std::string selector; + std::vector params; + std::optional doc; +}; + +/** Contract ABI */ +struct ContractAbi { + std::string name; + std::string version; + std::vector functions; + std::vector events; + std::vector errors; + + const FunctionAbi* find_function(const std::string& name) const; + const FunctionAbi* find_by_selector(const std::string& selector) const; + std::string to_json() const; +}; + +/** Compilation result */ +struct CompilationResult { + std::string contract_id; + std::vector code; + std::string code_hash; + size_t original_size; + size_t optimized_size; + double size_reduction; + std::optional metadata; + std::optional abi; + uint64_t estimated_deploy_gas = 0; + std::optional validation; + std::vector warnings; + + std::string size_stats() const; +}; + +/** Size breakdown */ +struct SizeBreakdown { + size_t code = 0; + size_t data = 0; + size_t types = 0; + size_t functions = 0; + size_t memory = 0; + size_t table = 0; + size_t exports = 0; + size_t imports = 0; + size_t custom = 0; + size_t total = 0; +}; + +/** Function analysis */ +struct FunctionAnalysis { + std::string name; + size_t size; + size_t instruction_count; + size_t local_count; + bool exported; + uint64_t estimated_gas; +}; + +/** Import analysis */ +struct ImportAnalysis { + std::string module; + std::string name; + std::string kind; + std::optional signature; +}; + +/** Security issue */ +struct SecurityIssue { + std::string severity; + std::string type; + std::string description; + std::optional location; +}; + +/** Security analysis */ +struct SecurityAnalysis { + int score = 0; + std::vector issues; + std::vector recommendations; +}; + +/** Gas analysis */ +struct GasAnalysis { + uint64_t deployment_gas = 0; + std::map function_gas; + uint64_t memory_init_gas = 0; + uint64_t data_section_gas = 0; +}; + +/** Contract analysis */ +struct ContractAnalysis { + SizeBreakdown size_breakdown; + std::vector functions; + std::vector imports; + std::optional security; + std::optional gas_analysis; +}; + +/** Compilation request */ +struct CompilationRequest { + std::optional optimization_level; + std::optional strip_options; + std::optional use_wasm_opt; + std::optional validate; + std::optional extract_metadata; + std::optional generate_abi; +}; + +/* ============================================================================ + * Client Classes + * ============================================================================ */ + +class SynorCompiler; + +/** Contracts sub-client */ +class ContractsClient { +public: + explicit ContractsClient(SynorCompiler& compiler) : compiler_(compiler) {} + + std::future compile(const std::vector& wasm, + const std::optional& request = std::nullopt); + std::future compile_dev(const std::vector& wasm); + std::future compile_production(const std::vector& wasm); + std::future get(const std::string& contract_id); + std::future> list(std::optional limit = std::nullopt, + std::optional offset = std::nullopt); + std::future> get_code(const std::string& contract_id); + +private: + SynorCompiler& compiler_; +}; + +/** ABI sub-client */ +class AbiClient { +public: + explicit AbiClient(SynorCompiler& compiler) : compiler_(compiler) {} + + std::future extract(const std::vector& wasm); + std::future get(const std::string& contract_id); + std::future encode_call(const FunctionAbi& func, const std::vector& args); + std::future> decode_result(const FunctionAbi& func, const std::string& data); + +private: + SynorCompiler& compiler_; +}; + +/** Analysis sub-client */ +class AnalysisClient { +public: + explicit AnalysisClient(SynorCompiler& compiler) : compiler_(compiler) {} + + std::future analyze(const std::vector& wasm); + std::future get(const std::string& contract_id); + std::future extract_metadata(const std::vector& wasm); + std::future estimate_deploy_gas(const std::vector& wasm); + std::future security_scan(const std::vector& wasm); + +private: + SynorCompiler& compiler_; +}; + +/** Validation sub-client */ +class ValidationClient { +public: + explicit ValidationClient(SynorCompiler& compiler) : compiler_(compiler) {} + + std::future validate(const std::vector& wasm); + std::future is_valid(const std::vector& wasm); + std::future> get_errors(const std::vector& wasm); + std::future validate_exports(const std::vector& wasm, + const std::vector& required_exports); + std::future validate_memory(const std::vector& wasm, uint32_t max_pages); + +private: + SynorCompiler& compiler_; +}; + +/** + * Synor Compiler SDK Client + * + * Smart contract compilation, optimization, and ABI generation. + * + * Example: + * @code + * synor::compiler::CompilerConfig config; + * config.api_key = "your-api-key"; + * synor::compiler::SynorCompiler compiler(config); + * + * auto result = compiler.compile(wasm_bytes).get(); + * std::cout << "Optimized size: " << result.optimized_size << " bytes" << std::endl; + * @endcode + */ +class SynorCompiler { +public: + explicit SynorCompiler(const CompilerConfig& config); + ~SynorCompiler(); + + // Non-copyable + SynorCompiler(const SynorCompiler&) = delete; + SynorCompiler& operator=(const SynorCompiler&) = delete; + + // Movable + SynorCompiler(SynorCompiler&&) noexcept; + SynorCompiler& operator=(SynorCompiler&&) noexcept; + + /** Get the default optimization level */ + OptimizationLevel default_optimization_level() const { return config_.optimization_level; } + + /** Health check */ + std::future health_check(); + + /** Get service info */ + std::future> get_info(); + + /** Close the client */ + void close(); + + /** Check if client is closed */ + bool is_closed() const { return closed_; } + + /** Compile WASM bytecode */ + std::future compile(const std::vector& wasm, + const std::optional& request = std::nullopt); + + /** Sub-clients */ + ContractsClient& contracts() { return contracts_; } + AbiClient& abi() { return abi_; } + AnalysisClient& analysis() { return analysis_; } + ValidationClient& validation() { return validation_; } + + // Internal HTTP methods (public for sub-clients) + std::future> get(const std::string& path); + std::future> post(const std::string& path, + const std::map& body); + +private: + CompilerConfig config_; + bool closed_ = false; + ContractsClient contracts_; + AbiClient abi_; + AnalysisClient analysis_; + ValidationClient validation_; +}; + +/* ============================================================================ + * Utility Functions + * ============================================================================ */ + +/** Convert optimization level to string */ +std::string optimization_level_to_string(OptimizationLevel level); + +/** Parse optimization level from string */ +OptimizationLevel optimization_level_from_string(const std::string& str); + +/* ============================================================================ + * Constants + * ============================================================================ */ + +constexpr size_t MAX_CONTRACT_SIZE = 256 * 1024; +constexpr uint32_t MAX_MEMORY_PAGES = 16; + +} // namespace compiler +} // namespace synor + +#endif // SYNOR_COMPILER_HPP diff --git a/sdk/csharp/src/Synor.Compiler/SynorCompiler.cs b/sdk/csharp/src/Synor.Compiler/SynorCompiler.cs new file mode 100644 index 0000000..5b0dcf9 --- /dev/null +++ b/sdk/csharp/src/Synor.Compiler/SynorCompiler.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; + +namespace Synor.Compiler; + +/// +/// Synor Compiler SDK for C#/.NET +/// +/// Smart contract compilation, optimization, and ABI generation. +/// +public class SynorCompiler : IDisposable +{ + private readonly CompilerConfig _config; + private readonly HttpClient _httpClient; + private readonly JsonSerializerOptions _jsonOptions; + private bool _closed; + + public ContractsClient Contracts { get; } + public AbiClient Abi { get; } + public AnalysisClient Analysis { get; } + public ValidationClient Validation { get; } + + public SynorCompiler(CompilerConfig config) + { + _config = config; + _httpClient = new HttpClient + { + BaseAddress = new Uri(config.Endpoint), + Timeout = TimeSpan.FromMilliseconds(config.Timeout) + }; + _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}"); + _httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0"); + + _jsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) } + }; + + Contracts = new ContractsClient(this); + Abi = new AbiClient(this); + Analysis = new AnalysisClient(this); + Validation = new ValidationClient(this); + } + + public OptimizationLevel DefaultOptimizationLevel => _config.DefaultOptimizationLevel; + + public async Task HealthCheckAsync(CancellationToken ct = default) + { + try + { + var result = await GetAsync>("/health", ct); + return result.TryGetValue("status", out var status) && status?.ToString() == "healthy"; + } + catch + { + return false; + } + } + + public Task> GetInfoAsync(CancellationToken ct = default) => + GetAsync>("/info", ct); + + public void Close() + { + _closed = true; + _httpClient.Dispose(); + } + + public bool IsClosed => _closed; + + public void Dispose() + { + Close(); + GC.SuppressFinalize(this); + } + + public async Task CompileAsync( + byte[] wasm, + CompilationRequest? request = null, + CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + var body = new Dictionary + { + ["wasm"] = wasmBase64, + ["optimization_level"] = (request?.OptimizationLevel ?? _config.DefaultOptimizationLevel).ToApiString(), + ["use_wasm_opt"] = request?.UseWasmOpt ?? _config.UseWasmOpt, + ["validate"] = request?.Validate ?? _config.Validate, + ["extract_metadata"] = request?.ExtractMetadata ?? _config.ExtractMetadata, + ["generate_abi"] = request?.GenerateAbi ?? _config.GenerateAbi + }; + + if (request?.StripOptions != null) + { + body["strip_options"] = request.StripOptions; + } + + return await PostAsync("/compile", body, ct); + } + + internal async Task GetAsync(string path, CancellationToken ct = default) + { + CheckClosed(); + var response = await _httpClient.GetAsync(path, ct); + return await HandleResponseAsync(response); + } + + internal async Task PostAsync(string path, object? body = null, CancellationToken ct = default) + { + CheckClosed(); + var content = body != null + ? JsonContent.Create(body, options: _jsonOptions) + : JsonContent.Create(new { }); + var response = await _httpClient.PostAsync(path, content, ct); + return await HandleResponseAsync(response); + } + + private async Task HandleResponseAsync(HttpResponseMessage response) + { + var json = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + var error = JsonSerializer.Deserialize>(json, _jsonOptions) ?? new(); + throw new CompilerException( + error.TryGetValue("message", out var msg) ? msg?.ToString() ?? $"HTTP {(int)response.StatusCode}" : $"HTTP {(int)response.StatusCode}", + error.TryGetValue("code", out var code) ? code?.ToString() : null, + (int)response.StatusCode + ); + } + return JsonSerializer.Deserialize(json, _jsonOptions)!; + } + + private void CheckClosed() + { + if (_closed) throw new CompilerException("Client has been closed", "CLIENT_CLOSED"); + } +} + +/// Contracts sub-client +public class ContractsClient +{ + private readonly SynorCompiler _compiler; + + internal ContractsClient(SynorCompiler compiler) => _compiler = compiler; + + public Task CompileAsync(byte[] wasm, CompilationRequest? request = null, CancellationToken ct = default) => + _compiler.CompileAsync(wasm, request, ct); + + public Task CompileDevAsync(byte[] wasm, CancellationToken ct = default) => + _compiler.CompileAsync(wasm, new CompilationRequest + { + OptimizationLevel = OptimizationLevel.None, + UseWasmOpt = false, + Validate = true + }, ct); + + public Task CompileProductionAsync(byte[] wasm, CancellationToken ct = default) => + _compiler.CompileAsync(wasm, new CompilationRequest + { + OptimizationLevel = OptimizationLevel.Aggressive, + UseWasmOpt = true, + Validate = true, + ExtractMetadata = true, + GenerateAbi = true + }, ct); + + public Task GetAsync(string contractId, CancellationToken ct = default) => + _compiler.GetAsync($"/contracts/{contractId}", ct); + + public async Task> ListAsync(int? limit = null, int? offset = null, CancellationToken ct = default) + { + var path = "/contracts"; + var qs = new List(); + if (limit.HasValue) qs.Add($"limit={limit}"); + if (offset.HasValue) qs.Add($"offset={offset}"); + if (qs.Count > 0) path += "?" + string.Join("&", qs); + + var result = await _compiler.GetAsync(path, ct); + return result.Contracts ?? new List(); + } + + public async Task GetCodeAsync(string contractId, CancellationToken ct = default) + { + var result = await _compiler.GetAsync($"/contracts/{contractId}/code", ct); + return Convert.FromBase64String(result.Code); + } +} + +/// ABI sub-client +public class AbiClient +{ + private readonly SynorCompiler _compiler; + + internal AbiClient(SynorCompiler compiler) => _compiler = compiler; + + public async Task ExtractAsync(byte[] wasm, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + return await _compiler.PostAsync("/abi/extract", new { wasm = wasmBase64 }, ct); + } + + public Task GetAsync(string contractId, CancellationToken ct = default) => + _compiler.GetAsync($"/contracts/{contractId}/abi", ct); + + public async Task EncodeCallAsync(FunctionAbi functionAbi, object[] args, CancellationToken ct = default) + { + var result = await _compiler.PostAsync("/abi/encode", new + { + function = functionAbi, + args + }, ct); + return result.Data; + } + + public async Task DecodeResultAsync(FunctionAbi functionAbi, string data, CancellationToken ct = default) + { + var result = await _compiler.PostAsync("/abi/decode", new + { + function = functionAbi, + data + }, ct); + return result.Values ?? Array.Empty(); + } +} + +/// Analysis sub-client +public class AnalysisClient +{ + private readonly SynorCompiler _compiler; + + internal AnalysisClient(SynorCompiler compiler) => _compiler = compiler; + + public async Task AnalyzeAsync(byte[] wasm, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + return await _compiler.PostAsync("/analysis", new { wasm = wasmBase64 }, ct); + } + + public Task GetAsync(string contractId, CancellationToken ct = default) => + _compiler.GetAsync($"/contracts/{contractId}/analysis", ct); + + public async Task ExtractMetadataAsync(byte[] wasm, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + return await _compiler.PostAsync("/analysis/metadata", new { wasm = wasmBase64 }, ct); + } + + public async Task EstimateDeployGasAsync(byte[] wasm, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + var result = await _compiler.PostAsync("/analysis/estimate-gas", new { wasm = wasmBase64 }, ct); + return result.Gas; + } + + public async Task SecurityScanAsync(byte[] wasm, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + var result = await _compiler.PostAsync("/analysis/security", new { wasm = wasmBase64 }, ct); + return result.Security!; + } +} + +/// Validation sub-client +public class ValidationClient +{ + private readonly SynorCompiler _compiler; + + internal ValidationClient(SynorCompiler compiler) => _compiler = compiler; + + public async Task ValidateAsync(byte[] wasm, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + return await _compiler.PostAsync("/validate", new { wasm = wasmBase64 }, ct); + } + + public async Task IsValidAsync(byte[] wasm, CancellationToken ct = default) + { + var result = await ValidateAsync(wasm, ct); + return result.Valid; + } + + public async Task> GetErrorsAsync(byte[] wasm, CancellationToken ct = default) + { + var result = await ValidateAsync(wasm, ct); + return result.Errors?.ConvertAll(e => e.Message) ?? new List(); + } + + public async Task ValidateExportsAsync(byte[] wasm, List requiredExports, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + var result = await _compiler.PostAsync("/validate/exports", new + { + wasm = wasmBase64, + required_exports = requiredExports + }, ct); + return result.Valid; + } + + public async Task ValidateMemoryAsync(byte[] wasm, int maxPages, CancellationToken ct = default) + { + var wasmBase64 = Convert.ToBase64String(wasm); + var result = await _compiler.PostAsync("/validate/memory", new + { + wasm = wasmBase64, + max_pages = maxPages + }, ct); + return result.Valid; + } +} + +// Response helper types +internal record ContractsListResponse(List? Contracts); +internal record CodeResponse(string Code); +internal record DataResponse(string Data); +internal record ValuesResponse(object[]? Values); +internal record GasResponse(long Gas); +internal record SecurityResponse(SecurityAnalysis? Security); +internal record ValidResponse(bool Valid); diff --git a/sdk/csharp/src/Synor.Compiler/Types.cs b/sdk/csharp/src/Synor.Compiler/Types.cs new file mode 100644 index 0000000..937c066 --- /dev/null +++ b/sdk/csharp/src/Synor.Compiler/Types.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Synor.Compiler; + +// ============================================================================ +// Enumerations +// ============================================================================ + +/// Optimization level for WASM compilation. +public enum OptimizationLevel +{ + /// No optimization. + None, + /// Basic optimizations (stripping only). + Basic, + /// Optimize for size (-Os equivalent). + Size, + /// Aggressive optimization (-O3 -Os equivalent). + Aggressive +} + +public static class OptimizationLevelExtensions +{ + public static string ToApiString(this OptimizationLevel level) => level switch + { + OptimizationLevel.None => "none", + OptimizationLevel.Basic => "basic", + OptimizationLevel.Size => "size", + OptimizationLevel.Aggressive => "aggressive", + _ => "size" + }; + + public static OptimizationLevel FromApiString(string value) => value switch + { + "none" => OptimizationLevel.None, + "basic" => OptimizationLevel.Basic, + "size" => OptimizationLevel.Size, + "aggressive" => OptimizationLevel.Aggressive, + _ => OptimizationLevel.Size + }; +} + +// ============================================================================ +// Configuration +// ============================================================================ + +/// Strip options for WASM sections. +public record StripOptions +{ + public bool StripDebug { get; init; } = true; + public bool StripProducers { get; init; } = true; + public bool StripNames { get; init; } = true; + public bool StripCustom { get; init; } = true; + public List PreserveSections { get; init; } = new(); + public bool StripUnused { get; init; } = true; +} + +/// Compiler configuration. +public record CompilerConfig +{ + public required string ApiKey { get; init; } + public string Endpoint { get; init; } = "https://compiler.synor.io/v1"; + public int Timeout { get; init; } = 60000; + public int Retries { get; init; } = 3; + public bool Debug { get; init; } = false; + public OptimizationLevel DefaultOptimizationLevel { get; init; } = OptimizationLevel.Size; + public StripOptions? StripOptions { get; init; } + public int MaxContractSize { get; init; } = 256 * 1024; + public bool UseWasmOpt { get; init; } = true; + public bool Validate { get; init; } = true; + public bool ExtractMetadata { get; init; } = true; + public bool GenerateAbi { get; init; } = true; +} + +/// Compilation request. +public record CompilationRequest +{ + public OptimizationLevel? OptimizationLevel { get; init; } + public StripOptions? StripOptions { get; init; } + public bool? UseWasmOpt { get; init; } + public bool? Validate { get; init; } + public bool? ExtractMetadata { get; init; } + public bool? GenerateAbi { get; init; } +} + +// ============================================================================ +// Validation Types +// ============================================================================ + +/// Validation error. +public record ValidationError +{ + public string Code { get; init; } = ""; + public string Message { get; init; } = ""; + public string? Location { get; init; } +} + +/// Validation result. +public record ValidationResult +{ + public bool Valid { get; init; } + public List? Errors { get; init; } + public List? Warnings { get; init; } + public int ExportCount { get; init; } + public int ImportCount { get; init; } + public int FunctionCount { get; init; } + public int[]? MemoryPages { get; init; } +} + +// ============================================================================ +// Metadata Types +// ============================================================================ + +/// Contract metadata. +public record ContractMetadata +{ + public string? Name { get; init; } + public string? Version { get; init; } + public List? Authors { get; init; } + public string? Description { get; init; } + public string? License { get; init; } + public string? Repository { get; init; } + public long? BuildTimestamp { get; init; } + public string? RustVersion { get; init; } + public string? SdkVersion { get; init; } + public Dictionary? Custom { get; init; } +} + +// ============================================================================ +// ABI Types +// ============================================================================ + +/// Type information. +public record TypeInfo +{ + public string Kind { get; init; } = "unknown"; + public int? Size { get; init; } + public TypeInfo? Element { get; init; } + public TypeInfo? Inner { get; init; } + public List? Elements { get; init; } + public string? Name { get; init; } + public List? Fields { get; init; } + + public string TypeName => Kind switch + { + "u8" or "u16" or "u32" or "u64" or "u128" or + "i8" or "i16" or "i32" or "i64" or "i128" or + "bool" or "string" or "bytes" or "address" or "hash256" => Kind, + "fixedBytes" => $"bytes{Size ?? 0}", + "array" => $"{Element?.TypeName ?? ""}[]", + "fixedArray" => $"{Element?.TypeName ?? ""}[{Size ?? 0}]", + "option" => $"{Inner?.TypeName ?? ""}?", + "tuple" => $"({string.Join(", ", Elements?.ConvertAll(e => e.TypeName) ?? new())})", + "struct" => Name ?? "struct", + _ => Name ?? "unknown" + }; +} + +/// Type field for structs. +public record TypeField(string Name, TypeInfo Type); + +/// Parameter ABI. +public record ParamAbi +{ + public string Name { get; init; } = ""; + public TypeInfo Type { get; init; } = new(); + public bool Indexed { get; init; } +} + +/// Function ABI. +public record FunctionAbi +{ + public string Name { get; init; } = ""; + public string Selector { get; init; } = ""; + public List? Inputs { get; init; } + public List? Outputs { get; init; } + public bool View { get; init; } + public bool Payable { get; init; } + public string? Doc { get; init; } +} + +/// Event ABI. +public record EventAbi +{ + public string Name { get; init; } = ""; + public string Topic { get; init; } = ""; + public List? Params { get; init; } + public string? Doc { get; init; } +} + +/// Error ABI. +public record ErrorAbi +{ + public string Name { get; init; } = ""; + public string Selector { get; init; } = ""; + public List? Params { get; init; } + public string? Doc { get; init; } +} + +/// Contract ABI (Application Binary Interface). +public record ContractAbi +{ + public string Name { get; init; } = ""; + public string Version { get; init; } = "1.0.0"; + public List? Functions { get; init; } + public List? Events { get; init; } + public List? Errors { get; init; } + + public FunctionAbi? FindFunction(string name) => + Functions?.Find(f => f.Name == name); + + public FunctionAbi? FindBySelector(string selector) => + Functions?.Find(f => f.Selector == selector); +} + +// ============================================================================ +// Compilation Types +// ============================================================================ + +/// Compilation result. +public record CompilationResult +{ + public string ContractId { get; init; } = ""; + public string Code { get; init; } = ""; + public string CodeHash { get; init; } = ""; + public int OriginalSize { get; init; } + public int OptimizedSize { get; init; } + public double SizeReduction { get; init; } + public ContractMetadata? Metadata { get; init; } + public ContractAbi? Abi { get; init; } + public long EstimatedDeployGas { get; init; } + public ValidationResult? Validation { get; init; } + public List? Warnings { get; init; } + + public string SizeStats => + $"Original: {OriginalSize} bytes, Optimized: {OptimizedSize} bytes, Reduction: {SizeReduction:F1}%"; + + public byte[] DecodeCode() => Convert.FromBase64String(Code); +} + +// ============================================================================ +// Analysis Types +// ============================================================================ + +/// Size breakdown. +public record SizeBreakdown +{ + public int Code { get; init; } + public int Data { get; init; } + public int Types { get; init; } + public int Functions { get; init; } + public int Memory { get; init; } + public int Table { get; init; } + public int Exports { get; init; } + public int Imports { get; init; } + public int Custom { get; init; } + public int Total { get; init; } +} + +/// Function analysis. +public record FunctionAnalysis +{ + public string Name { get; init; } = ""; + public int Size { get; init; } + public int InstructionCount { get; init; } + public int LocalCount { get; init; } + public bool Exported { get; init; } + public long EstimatedGas { get; init; } +} + +/// Import analysis. +public record ImportAnalysis +{ + public string Module { get; init; } = ""; + public string Name { get; init; } = ""; + public string Kind { get; init; } = "function"; + public string? Signature { get; init; } +} + +/// Security issue. +public record SecurityIssue +{ + public string Severity { get; init; } = "low"; + public string Type { get; init; } = ""; + public string Description { get; init; } = ""; + public string? Location { get; init; } +} + +/// Security analysis. +public record SecurityAnalysis +{ + public int Score { get; init; } + public List? Issues { get; init; } + public List? Recommendations { get; init; } +} + +/// Gas analysis. +public record GasAnalysis +{ + public long DeploymentGas { get; init; } + public Dictionary? FunctionGas { get; init; } + public long MemoryInitGas { get; init; } + public long DataSectionGas { get; init; } +} + +/// Contract analysis result. +public record ContractAnalysis +{ + public SizeBreakdown? SizeBreakdown { get; init; } + public List? Functions { get; init; } + public List? Imports { get; init; } + public SecurityAnalysis? Security { get; init; } + public GasAnalysis? GasAnalysis { get; init; } +} + +// ============================================================================ +// Error Types +// ============================================================================ + +/// Compiler exception. +public class CompilerException : Exception +{ + public string? Code { get; } + public int? HttpStatus { get; } + + public CompilerException(string message, string? code = null, int? httpStatus = null) + : base(message) + { + Code = code; + HttpStatus = httpStatus; + } +} + +// ============================================================================ +// Constants +// ============================================================================ + +/// Compiler constants. +public static class CompilerConstants +{ + public const int MaxContractSize = 256 * 1024; + public const int MaxMemoryPages = 16; +} diff --git a/sdk/flutter/lib/src/compiler/client.dart b/sdk/flutter/lib/src/compiler/client.dart new file mode 100644 index 0000000..078ce84 --- /dev/null +++ b/sdk/flutter/lib/src/compiler/client.dart @@ -0,0 +1,390 @@ +/// Synor Compiler SDK Client +/// +/// Smart contract compilation, optimization, and ABI generation. +library synor_compiler_client; + +import 'dart:convert'; +import 'package:http/http.dart' as http; + +import 'types.dart'; + +export 'types.dart'; + +/// Synor Compiler SDK Client. +/// +/// Smart contract compilation, optimization, and ABI generation. +/// +/// Example: +/// ```dart +/// final compiler = SynorCompiler(CompilerConfig(apiKey: 'your-api-key')); +/// +/// final result = await compiler.compile(wasmBytes); +/// print('Optimized size: ${result.optimizedSize} bytes'); +/// ``` +class SynorCompiler { + final CompilerConfig _config; + final http.Client _httpClient; + bool _closed = false; + + late final ContractsClient contracts; + late final AbiClient abi; + late final AnalysisClient analysis; + late final ValidationClient validation; + + SynorCompiler(this._config, {http.Client? httpClient}) + : _httpClient = httpClient ?? http.Client() { + contracts = ContractsClient(this); + abi = AbiClient(this); + analysis = AnalysisClient(this); + validation = ValidationClient(this); + } + + /// Get the default optimization level. + OptimizationLevel get defaultOptimizationLevel => _config.optimizationLevel; + + /// Check service health. + Future healthCheck() async { + try { + final result = await _get('/health'); + return result['status'] == 'healthy'; + } catch (_) { + return false; + } + } + + /// Get service info. + Future> getInfo() async { + return await _get('/info'); + } + + /// Close the client. + void close() { + _closed = true; + _httpClient.close(); + } + + /// Check if client is closed. + bool get isClosed => _closed; + + /// Compile WASM bytecode. + Future compile( + List wasm, { + OptimizationLevel? optimizationLevel, + StripOptions? stripOptions, + bool? useWasmOpt, + bool? validate, + bool? extractMetadata, + bool? generateAbi, + }) async { + final wasmBase64 = base64Encode(wasm); + + final body = { + 'wasm': wasmBase64, + 'optimization_level': + (optimizationLevel ?? _config.optimizationLevel).toApiString(), + 'strip_options': + (stripOptions ?? _config.stripOptions ?? const StripOptions()) + .toJson(), + 'use_wasm_opt': useWasmOpt ?? _config.useWasmOpt, + 'validate': validate ?? _config.validate, + 'extract_metadata': extractMetadata ?? _config.extractMetadata, + 'generate_abi': generateAbi ?? _config.generateAbi, + }; + + final result = await _post('/compile', body); + return CompilationResult.fromJson(result); + } + + Future> _get(String path) async { + _checkClosed(); + + final response = await _httpClient.get( + Uri.parse('${_config.endpoint}$path'), + headers: _headers, + ); + + return _handleResponse(response); + } + + Future> _post( + String path, Map body) async { + _checkClosed(); + + final response = await _httpClient.post( + Uri.parse('${_config.endpoint}$path'), + headers: _headers, + body: jsonEncode(body), + ); + + return _handleResponse(response); + } + + Map get _headers => { + 'Authorization': 'Bearer ${_config.apiKey}', + 'Content-Type': 'application/json', + 'X-SDK-Version': 'flutter/0.1.0', + }; + + Map _handleResponse(http.Response response) { + final body = response.body; + + if (response.statusCode >= 400) { + Map error = {}; + try { + error = jsonDecode(body); + } catch (_) { + error = {'message': body.isEmpty ? 'HTTP ${response.statusCode}' : body}; + } + throw CompilerError( + error['message'] ?? 'HTTP ${response.statusCode}', + code: error['code'], + httpStatus: response.statusCode, + ); + } + + return jsonDecode(body); + } + + void _checkClosed() { + if (_closed) { + throw const CompilerError('Client has been closed', code: 'CLIENT_CLOSED'); + } + } +} + +/// Contracts sub-client. +class ContractsClient { + final SynorCompiler _compiler; + + ContractsClient(this._compiler); + + /// Compile WASM bytecode with default settings. + Future compile( + List wasm, { + OptimizationLevel? optimizationLevel, + StripOptions? stripOptions, + bool? useWasmOpt, + bool? validate, + bool? extractMetadata, + bool? generateAbi, + }) { + return _compiler.compile( + wasm, + optimizationLevel: optimizationLevel, + stripOptions: stripOptions, + useWasmOpt: useWasmOpt, + validate: validate, + extractMetadata: extractMetadata, + generateAbi: generateAbi, + ); + } + + /// Compile with development settings. + Future compileDev(List wasm) { + return _compiler.compile( + wasm, + optimizationLevel: OptimizationLevel.none, + useWasmOpt: false, + validate: true, + ); + } + + /// Compile with production settings. + Future compileProduction(List wasm) { + return _compiler.compile( + wasm, + optimizationLevel: OptimizationLevel.aggressive, + useWasmOpt: true, + validate: true, + extractMetadata: true, + generateAbi: true, + ); + } + + /// Get a compiled contract by ID. + Future get(String contractId) async { + final result = await _compiler._get('/contracts/$contractId'); + return CompilationResult.fromJson(result); + } + + /// List compiled contracts. + Future> list({int? limit, int? offset}) async { + var path = '/contracts'; + final params = []; + if (limit != null) params.add('limit=$limit'); + if (offset != null) params.add('offset=$offset'); + if (params.isNotEmpty) path += '?${params.join('&')}'; + + final result = await _compiler._get(path); + return (result['contracts'] as List?) + ?.map((c) => CompilationResult.fromJson(c)) + .toList() ?? + []; + } + + /// Get optimized bytecode for a contract. + Future> getCode(String contractId) async { + final result = await _compiler._get('/contracts/$contractId/code'); + return base64Decode(result['code'] ?? ''); + } +} + +/// ABI sub-client. +class AbiClient { + final SynorCompiler _compiler; + + AbiClient(this._compiler); + + /// Extract ABI from WASM bytecode. + Future extract(List wasm) async { + final wasmBase64 = base64Encode(wasm); + final result = + await _compiler._post('/abi/extract', {'wasm': wasmBase64}); + return ContractAbi.fromJson(result); + } + + /// Get ABI for a compiled contract. + Future get(String contractId) async { + final result = await _compiler._get('/contracts/$contractId/abi'); + return ContractAbi.fromJson(result); + } + + /// Encode function call. + Future encodeCall(FunctionAbi functionAbi, List args) async { + final result = await _compiler._post('/abi/encode', { + 'function': { + 'name': functionAbi.name, + 'selector': functionAbi.selector, + 'inputs': functionAbi.inputs + .map((i) => {'name': i.name, 'type': {'kind': i.type.kind}}) + .toList(), + 'outputs': functionAbi.outputs + .map((o) => {'name': o.name, 'type': {'kind': o.type.kind}}) + .toList(), + }, + 'args': args, + }); + return result['data'] ?? ''; + } + + /// Decode function result. + Future> decodeResult( + FunctionAbi functionAbi, String data) async { + final result = await _compiler._post('/abi/decode', { + 'function': { + 'name': functionAbi.name, + 'selector': functionAbi.selector, + 'inputs': functionAbi.inputs + .map((i) => {'name': i.name, 'type': {'kind': i.type.kind}}) + .toList(), + 'outputs': functionAbi.outputs + .map((o) => {'name': o.name, 'type': {'kind': o.type.kind}}) + .toList(), + }, + 'data': data, + }); + return result['values'] ?? []; + } +} + +/// Analysis sub-client. +class AnalysisClient { + final SynorCompiler _compiler; + + AnalysisClient(this._compiler); + + /// Analyze WASM bytecode. + Future analyze(List wasm) async { + final wasmBase64 = base64Encode(wasm); + final result = + await _compiler._post('/analysis', {'wasm': wasmBase64}); + return ContractAnalysis.fromJson(result); + } + + /// Get analysis for a compiled contract. + Future get(String contractId) async { + final result = await _compiler._get('/contracts/$contractId/analysis'); + return ContractAnalysis.fromJson(result); + } + + /// Extract metadata from WASM bytecode. + Future extractMetadata(List wasm) async { + final wasmBase64 = base64Encode(wasm); + final result = + await _compiler._post('/analysis/metadata', {'wasm': wasmBase64}); + return ContractMetadata.fromJson(result); + } + + /// Extract source map from WASM bytecode. + Future extractSourceMap(List wasm) async { + final wasmBase64 = base64Encode(wasm); + final result = + await _compiler._post('/analysis/source-map', {'wasm': wasmBase64}); + final sourceMap = result['source_map']; + return sourceMap != null ? SourceMap.fromJson(sourceMap) : null; + } + + /// Estimate gas for contract deployment. + Future estimateDeployGas(List wasm) async { + final wasmBase64 = base64Encode(wasm); + final result = + await _compiler._post('/analysis/estimate-gas', {'wasm': wasmBase64}); + return result['gas'] ?? 0; + } + + /// Get security analysis. + Future securityScan(List wasm) async { + final wasmBase64 = base64Encode(wasm); + final result = + await _compiler._post('/analysis/security', {'wasm': wasmBase64}); + return SecurityAnalysis.fromJson(result['security'] ?? {}); + } +} + +/// Validation sub-client. +class ValidationClient { + final SynorCompiler _compiler; + + ValidationClient(this._compiler); + + /// Validate WASM bytecode against VM requirements. + Future validate(List wasm) async { + final wasmBase64 = base64Encode(wasm); + final result = + await _compiler._post('/validate', {'wasm': wasmBase64}); + return ValidationResult.fromJson(result); + } + + /// Check if WASM is valid. + Future isValid(List wasm) async { + final result = await validate(wasm); + return result.valid; + } + + /// Get validation errors. + Future> getErrors(List wasm) async { + final result = await validate(wasm); + return result.errors.map((e) => e.message).toList(); + } + + /// Validate contract exports. + Future validateExports( + List wasm, List requiredExports) async { + final wasmBase64 = base64Encode(wasm); + final result = await _compiler._post('/validate/exports', { + 'wasm': wasmBase64, + 'required_exports': requiredExports, + }); + return result['valid'] ?? false; + } + + /// Validate memory limits. + Future validateMemory(List wasm, int maxPages) async { + final wasmBase64 = base64Encode(wasm); + final result = await _compiler._post('/validate/memory', { + 'wasm': wasmBase64, + 'max_pages': maxPages, + }); + return result['valid'] ?? false; + } +} diff --git a/sdk/flutter/lib/src/compiler/types.dart b/sdk/flutter/lib/src/compiler/types.dart new file mode 100644 index 0000000..63613f8 --- /dev/null +++ b/sdk/flutter/lib/src/compiler/types.dart @@ -0,0 +1,788 @@ +/// Synor Compiler SDK Types +/// +/// Smart contract compilation, optimization, and ABI generation. +library synor_compiler_types; + +/// Optimization level for WASM compilation. +enum OptimizationLevel { + /// No optimization. + none, + + /// Basic optimizations (stripping only). + basic, + + /// Optimize for size (-Os equivalent). + size, + + /// Aggressive optimization (-O3 -Os equivalent). + aggressive, +} + +extension OptimizationLevelExtension on OptimizationLevel { + String toApiString() { + switch (this) { + case OptimizationLevel.none: + return 'none'; + case OptimizationLevel.basic: + return 'basic'; + case OptimizationLevel.size: + return 'size'; + case OptimizationLevel.aggressive: + return 'aggressive'; + } + } + + static OptimizationLevel fromApiString(String value) { + switch (value) { + case 'none': + return OptimizationLevel.none; + case 'basic': + return OptimizationLevel.basic; + case 'size': + return OptimizationLevel.size; + case 'aggressive': + return OptimizationLevel.aggressive; + default: + return OptimizationLevel.size; + } + } +} + +/// Options for stripping WASM sections. +class StripOptions { + final bool stripDebug; + final bool stripProducers; + final bool stripNames; + final bool stripCustom; + final List preserveSections; + final bool stripUnused; + + const StripOptions({ + this.stripDebug = true, + this.stripProducers = true, + this.stripNames = true, + this.stripCustom = true, + this.preserveSections = const [], + this.stripUnused = true, + }); + + factory StripOptions.fromJson(Map json) { + return StripOptions( + stripDebug: json['strip_debug'] ?? true, + stripProducers: json['strip_producers'] ?? true, + stripNames: json['strip_names'] ?? true, + stripCustom: json['strip_custom'] ?? true, + preserveSections: List.from(json['preserve_sections'] ?? []), + stripUnused: json['strip_unused'] ?? true, + ); + } + + Map toJson() => { + 'strip_debug': stripDebug, + 'strip_producers': stripProducers, + 'strip_names': stripNames, + 'strip_custom': stripCustom, + 'preserve_sections': preserveSections, + 'strip_unused': stripUnused, + }; +} + +/// Compiler configuration. +class CompilerConfig { + final String apiKey; + final String endpoint; + final int timeout; + final int retries; + final bool debug; + final OptimizationLevel optimizationLevel; + final StripOptions? stripOptions; + final int maxContractSize; + final bool useWasmOpt; + final bool validate; + final bool extractMetadata; + final bool generateAbi; + + const CompilerConfig({ + required this.apiKey, + this.endpoint = 'https://compiler.synor.io/v1', + this.timeout = 60000, + this.retries = 3, + this.debug = false, + this.optimizationLevel = OptimizationLevel.size, + this.stripOptions, + this.maxContractSize = 256 * 1024, + this.useWasmOpt = true, + this.validate = true, + this.extractMetadata = true, + this.generateAbi = true, + }); +} + +/// Validation error. +class ValidationError { + final String code; + final String message; + final String? location; + + const ValidationError({ + required this.code, + required this.message, + this.location, + }); + + factory ValidationError.fromJson(Map json) { + return ValidationError( + code: json['code'] ?? '', + message: json['message'] ?? '', + location: json['location'], + ); + } +} + +/// Validation result. +class ValidationResult { + final bool valid; + final List errors; + final List warnings; + final int exportCount; + final int importCount; + final int functionCount; + final List memoryPages; + + const ValidationResult({ + required this.valid, + this.errors = const [], + this.warnings = const [], + this.exportCount = 0, + this.importCount = 0, + this.functionCount = 0, + this.memoryPages = const [0, null], + }); + + factory ValidationResult.fromJson(Map json) { + return ValidationResult( + valid: json['valid'] ?? false, + errors: (json['errors'] as List?) + ?.map((e) => ValidationError.fromJson(e)) + .toList() ?? + [], + warnings: List.from(json['warnings'] ?? []), + exportCount: json['export_count'] ?? 0, + importCount: json['import_count'] ?? 0, + functionCount: json['function_count'] ?? 0, + memoryPages: List.from(json['memory_pages'] ?? [0, null]), + ); + } +} + +/// Contract metadata. +class ContractMetadata { + final String? name; + final String? version; + final List authors; + final String? description; + final String? license; + final String? repository; + final int? buildTimestamp; + final String? rustVersion; + final String? sdkVersion; + final Map custom; + + const ContractMetadata({ + this.name, + this.version, + this.authors = const [], + this.description, + this.license, + this.repository, + this.buildTimestamp, + this.rustVersion, + this.sdkVersion, + this.custom = const {}, + }); + + factory ContractMetadata.fromJson(Map json) { + return ContractMetadata( + name: json['name'], + version: json['version'], + authors: List.from(json['authors'] ?? []), + description: json['description'], + license: json['license'], + repository: json['repository'], + buildTimestamp: json['build_timestamp'], + rustVersion: json['rust_version'], + sdkVersion: json['sdk_version'], + custom: Map.from(json['custom'] ?? {}), + ); + } +} + +/// Type information. +class TypeInfo { + final String kind; + final int? size; + final TypeInfo? element; + final TypeInfo? inner; + final List? elements; + final String? name; + final List? fields; + + const TypeInfo({ + required this.kind, + this.size, + this.element, + this.inner, + this.elements, + this.name, + this.fields, + }); + + factory TypeInfo.fromJson(Map json) { + return TypeInfo( + kind: json['kind'] ?? 'unknown', + size: json['size'], + element: json['element'] != null ? TypeInfo.fromJson(json['element']) : null, + inner: json['inner'] != null ? TypeInfo.fromJson(json['inner']) : null, + elements: (json['elements'] as List?) + ?.map((e) => TypeInfo.fromJson(e)) + .toList(), + name: json['name'], + fields: (json['fields'] as List?) + ?.map((f) => TypeField.fromJson(f)) + .toList(), + ); + } + + String get typeName { + switch (kind) { + case 'u8': + case 'u16': + case 'u32': + case 'u64': + case 'u128': + case 'i8': + case 'i16': + case 'i32': + case 'i64': + case 'i128': + case 'bool': + case 'string': + case 'bytes': + case 'address': + case 'hash256': + return kind; + case 'fixedBytes': + return 'bytes${size ?? 0}'; + case 'array': + return '${element?.typeName ?? ''}[]'; + case 'fixedArray': + return '${element?.typeName ?? ''}[${size ?? 0}]'; + case 'option': + return '${inner?.typeName ?? ''}?'; + case 'tuple': + final names = elements?.map((e) => e.typeName).join(', ') ?? ''; + return '($names)'; + case 'struct': + return name ?? 'struct'; + default: + return name ?? 'unknown'; + } + } +} + +/// Type field for structs. +class TypeField { + final String name; + final TypeInfo type; + + const TypeField({required this.name, required this.type}); + + factory TypeField.fromJson(Map json) { + return TypeField( + name: json['name'] ?? '', + type: TypeInfo.fromJson(json['type'] ?? {'kind': 'unknown'}), + ); + } +} + +/// Parameter ABI. +class ParamAbi { + final String name; + final TypeInfo type; + final bool indexed; + + const ParamAbi({ + required this.name, + required this.type, + this.indexed = false, + }); + + factory ParamAbi.fromJson(Map json) { + return ParamAbi( + name: json['name'] ?? '', + type: TypeInfo.fromJson(json['type'] ?? {'kind': 'unknown'}), + indexed: json['indexed'] ?? false, + ); + } +} + +/// Function ABI. +class FunctionAbi { + final String name; + final String selector; + final List inputs; + final List outputs; + final bool view; + final bool payable; + final String? doc; + + const FunctionAbi({ + required this.name, + required this.selector, + this.inputs = const [], + this.outputs = const [], + this.view = false, + this.payable = false, + this.doc, + }); + + factory FunctionAbi.fromJson(Map json) { + return FunctionAbi( + name: json['name'] ?? '', + selector: json['selector'] ?? '', + inputs: (json['inputs'] as List?) + ?.map((i) => ParamAbi.fromJson(i)) + .toList() ?? + [], + outputs: (json['outputs'] as List?) + ?.map((o) => ParamAbi.fromJson(o)) + .toList() ?? + [], + view: json['view'] ?? false, + payable: json['payable'] ?? false, + doc: json['doc'], + ); + } +} + +/// Event ABI. +class EventAbi { + final String name; + final String topic; + final List params; + final String? doc; + + const EventAbi({ + required this.name, + required this.topic, + this.params = const [], + this.doc, + }); + + factory EventAbi.fromJson(Map json) { + return EventAbi( + name: json['name'] ?? '', + topic: json['topic'] ?? '', + params: (json['params'] as List?) + ?.map((p) => ParamAbi.fromJson(p)) + .toList() ?? + [], + doc: json['doc'], + ); + } +} + +/// Error ABI. +class ErrorAbi { + final String name; + final String selector; + final List params; + final String? doc; + + const ErrorAbi({ + required this.name, + required this.selector, + this.params = const [], + this.doc, + }); + + factory ErrorAbi.fromJson(Map json) { + return ErrorAbi( + name: json['name'] ?? '', + selector: json['selector'] ?? '', + params: (json['params'] as List?) + ?.map((p) => ParamAbi.fromJson(p)) + .toList() ?? + [], + doc: json['doc'], + ); + } +} + +/// Contract ABI (Application Binary Interface). +class ContractAbi { + final String name; + final String version; + final List functions; + final List events; + final List errors; + + const ContractAbi({ + required this.name, + required this.version, + this.functions = const [], + this.events = const [], + this.errors = const [], + }); + + factory ContractAbi.fromJson(Map json) { + return ContractAbi( + name: json['name'] ?? '', + version: json['version'] ?? '1.0.0', + functions: (json['functions'] as List?) + ?.map((f) => FunctionAbi.fromJson(f)) + .toList() ?? + [], + events: (json['events'] as List?) + ?.map((e) => EventAbi.fromJson(e)) + .toList() ?? + [], + errors: (json['errors'] as List?) + ?.map((e) => ErrorAbi.fromJson(e)) + .toList() ?? + [], + ); + } + + FunctionAbi? findFunction(String name) { + return functions.cast().firstWhere( + (f) => f?.name == name, + orElse: () => null, + ); + } + + FunctionAbi? findBySelector(String selector) { + return functions.cast().firstWhere( + (f) => f?.selector == selector, + orElse: () => null, + ); + } +} + +/// Compilation result. +class CompilationResult { + final String contractId; + final String code; + final String codeHash; + final int originalSize; + final int optimizedSize; + final double sizeReduction; + final ContractMetadata? metadata; + final ContractAbi? abi; + final int estimatedDeployGas; + final ValidationResult? validation; + final List warnings; + + const CompilationResult({ + required this.contractId, + required this.code, + required this.codeHash, + required this.originalSize, + required this.optimizedSize, + required this.sizeReduction, + this.metadata, + this.abi, + this.estimatedDeployGas = 0, + this.validation, + this.warnings = const [], + }); + + factory CompilationResult.fromJson(Map json) { + return CompilationResult( + contractId: json['contract_id'] ?? '', + code: json['code'] ?? '', + codeHash: json['code_hash'] ?? '', + originalSize: json['original_size'] ?? 0, + optimizedSize: json['optimized_size'] ?? 0, + sizeReduction: (json['size_reduction'] ?? 0).toDouble(), + metadata: json['metadata'] != null + ? ContractMetadata.fromJson(json['metadata']) + : null, + abi: json['abi'] != null ? ContractAbi.fromJson(json['abi']) : null, + estimatedDeployGas: json['estimated_deploy_gas'] ?? 0, + validation: json['validation'] != null + ? ValidationResult.fromJson(json['validation']) + : null, + warnings: List.from(json['warnings'] ?? []), + ); + } + + String get sizeStats => + 'Original: $originalSize bytes, Optimized: $optimizedSize bytes, Reduction: ${sizeReduction.toStringAsFixed(1)}%'; +} + +/// Source mapping entry. +class SourceMapping { + final int wasmOffset; + final int line; + final int column; + final String? function; + + const SourceMapping({ + required this.wasmOffset, + required this.line, + required this.column, + this.function, + }); + + factory SourceMapping.fromJson(Map json) { + return SourceMapping( + wasmOffset: json['wasm_offset'] ?? 0, + line: json['line'] ?? 0, + column: json['column'] ?? 0, + function: json['function'], + ); + } +} + +/// Source map. +class SourceMap { + final String file; + final List mappings; + + const SourceMap({ + required this.file, + this.mappings = const [], + }); + + factory SourceMap.fromJson(Map json) { + return SourceMap( + file: json['file'] ?? '', + mappings: (json['mappings'] as List?) + ?.map((m) => SourceMapping.fromJson(m)) + .toList() ?? + [], + ); + } +} + +/// Size breakdown. +class SizeBreakdown { + final int code; + final int data; + final int types; + final int functions; + final int memory; + final int table; + final int exports; + final int imports; + final int custom; + final int total; + + const SizeBreakdown({ + this.code = 0, + this.data = 0, + this.types = 0, + this.functions = 0, + this.memory = 0, + this.table = 0, + this.exports = 0, + this.imports = 0, + this.custom = 0, + this.total = 0, + }); + + factory SizeBreakdown.fromJson(Map json) { + return SizeBreakdown( + code: json['code'] ?? 0, + data: json['data'] ?? 0, + types: json['types'] ?? 0, + functions: json['functions'] ?? 0, + memory: json['memory'] ?? 0, + table: json['table'] ?? 0, + exports: json['exports'] ?? 0, + imports: json['imports'] ?? 0, + custom: json['custom'] ?? 0, + total: json['total'] ?? 0, + ); + } +} + +/// Function analysis. +class FunctionAnalysis { + final String name; + final int size; + final int instructionCount; + final int localCount; + final bool exported; + final int estimatedGas; + + const FunctionAnalysis({ + required this.name, + required this.size, + required this.instructionCount, + required this.localCount, + required this.exported, + required this.estimatedGas, + }); + + factory FunctionAnalysis.fromJson(Map json) { + return FunctionAnalysis( + name: json['name'] ?? '', + size: json['size'] ?? 0, + instructionCount: json['instruction_count'] ?? 0, + localCount: json['local_count'] ?? 0, + exported: json['exported'] ?? false, + estimatedGas: json['estimated_gas'] ?? 0, + ); + } +} + +/// Import analysis. +class ImportAnalysis { + final String module; + final String name; + final String kind; + final String? signature; + + const ImportAnalysis({ + required this.module, + required this.name, + required this.kind, + this.signature, + }); + + factory ImportAnalysis.fromJson(Map json) { + return ImportAnalysis( + module: json['module'] ?? '', + name: json['name'] ?? '', + kind: json['kind'] ?? 'function', + signature: json['signature'], + ); + } +} + +/// Security issue. +class SecurityIssue { + final String severity; + final String type; + final String description; + final String? location; + + const SecurityIssue({ + required this.severity, + required this.type, + required this.description, + this.location, + }); + + factory SecurityIssue.fromJson(Map json) { + return SecurityIssue( + severity: json['severity'] ?? 'low', + type: json['type'] ?? '', + description: json['description'] ?? '', + location: json['location'], + ); + } +} + +/// Security analysis. +class SecurityAnalysis { + final int score; + final List issues; + final List recommendations; + + const SecurityAnalysis({ + required this.score, + this.issues = const [], + this.recommendations = const [], + }); + + factory SecurityAnalysis.fromJson(Map json) { + return SecurityAnalysis( + score: json['score'] ?? 0, + issues: (json['issues'] as List?) + ?.map((i) => SecurityIssue.fromJson(i)) + .toList() ?? + [], + recommendations: List.from(json['recommendations'] ?? []), + ); + } +} + +/// Gas analysis. +class GasAnalysis { + final int deploymentGas; + final Map functionGas; + final int memoryInitGas; + final int dataSectionGas; + + const GasAnalysis({ + required this.deploymentGas, + this.functionGas = const {}, + this.memoryInitGas = 0, + this.dataSectionGas = 0, + }); + + factory GasAnalysis.fromJson(Map json) { + return GasAnalysis( + deploymentGas: json['deployment_gas'] ?? 0, + functionGas: Map.from(json['function_gas'] ?? {}), + memoryInitGas: json['memory_init_gas'] ?? 0, + dataSectionGas: json['data_section_gas'] ?? 0, + ); + } +} + +/// Contract analysis result. +class ContractAnalysis { + final SizeBreakdown sizeBreakdown; + final List functions; + final List imports; + final SecurityAnalysis? security; + final GasAnalysis? gasAnalysis; + + const ContractAnalysis({ + required this.sizeBreakdown, + this.functions = const [], + this.imports = const [], + this.security, + this.gasAnalysis, + }); + + factory ContractAnalysis.fromJson(Map json) { + return ContractAnalysis( + sizeBreakdown: SizeBreakdown.fromJson(json['size_breakdown'] ?? {}), + functions: (json['functions'] as List?) + ?.map((f) => FunctionAnalysis.fromJson(f)) + .toList() ?? + [], + imports: (json['imports'] as List?) + ?.map((i) => ImportAnalysis.fromJson(i)) + .toList() ?? + [], + security: json['security'] != null + ? SecurityAnalysis.fromJson(json['security']) + : null, + gasAnalysis: json['gas_analysis'] != null + ? GasAnalysis.fromJson(json['gas_analysis']) + : null, + ); + } +} + +/// Compiler error. +class CompilerError implements Exception { + final String message; + final String? code; + final int? httpStatus; + + const CompilerError(this.message, {this.code, this.httpStatus}); + + @override + String toString() => code != null ? '$message ($code)' : message; +} + +/// Constants +const int maxContractSize = 256 * 1024; // 256 KB +const int maxMemoryPages = 16; diff --git a/sdk/go/compiler/client.go b/sdk/go/compiler/client.go new file mode 100644 index 0000000..964342d --- /dev/null +++ b/sdk/go/compiler/client.go @@ -0,0 +1,483 @@ +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 +} diff --git a/sdk/go/compiler/types.go b/sdk/go/compiler/types.go new file mode 100644 index 0000000..05bb24f --- /dev/null +++ b/sdk/go/compiler/types.go @@ -0,0 +1,382 @@ +// Package compiler provides the Synor Compiler SDK for Go. +// +// Smart contract compilation, optimization, and ABI generation. +package compiler + +import ( + "encoding/json" + "fmt" +) + +// OptimizationLevel represents the optimization level for WASM compilation. +type OptimizationLevel string + +const ( + OptimizationNone OptimizationLevel = "none" + OptimizationBasic OptimizationLevel = "basic" + OptimizationSize OptimizationLevel = "size" + OptimizationAggressive OptimizationLevel = "aggressive" +) + +// StripOptions defines options for stripping WASM sections. +type StripOptions struct { + StripDebug bool `json:"strip_debug"` + StripProducers bool `json:"strip_producers"` + StripNames bool `json:"strip_names"` + StripCustom bool `json:"strip_custom"` + PreserveSections []string `json:"preserve_sections"` + StripUnused bool `json:"strip_unused"` +} + +// DefaultStripOptions returns the default strip options. +func DefaultStripOptions() StripOptions { + return StripOptions{ + StripDebug: true, + StripProducers: true, + StripNames: true, + StripCustom: true, + PreserveSections: []string{}, + StripUnused: true, + } +} + +// CompilerConfig holds the configuration for the compiler client. +type CompilerConfig struct { + APIKey string `json:"api_key"` + Endpoint string `json:"endpoint"` + Timeout int `json:"timeout"` // milliseconds + Retries int `json:"retries"` + Debug bool `json:"debug"` + OptimizationLevel OptimizationLevel `json:"optimization_level"` + StripOptions *StripOptions `json:"strip_options,omitempty"` + MaxContractSize int `json:"max_contract_size"` + UseWasmOpt bool `json:"use_wasm_opt"` + Validate bool `json:"validate"` + ExtractMetadata bool `json:"extract_metadata"` + GenerateAbi bool `json:"generate_abi"` +} + +// DefaultConfig returns a default configuration. +func DefaultConfig(apiKey string) CompilerConfig { + return CompilerConfig{ + APIKey: apiKey, + Endpoint: "https://compiler.synor.io/v1", + Timeout: 60000, + Retries: 3, + Debug: false, + OptimizationLevel: OptimizationSize, + StripOptions: nil, + MaxContractSize: 256 * 1024, + UseWasmOpt: true, + Validate: true, + ExtractMetadata: true, + GenerateAbi: true, + } +} + +// CompilationRequest represents a compilation request. +type CompilationRequest struct { + Wasm string `json:"wasm"` // Base64 encoded + OptimizationLevel *OptimizationLevel `json:"optimization_level,omitempty"` + StripOptions *StripOptions `json:"strip_options,omitempty"` + UseWasmOpt *bool `json:"use_wasm_opt,omitempty"` + Validate *bool `json:"validate,omitempty"` + ExtractMetadata *bool `json:"extract_metadata,omitempty"` + GenerateAbi *bool `json:"generate_abi,omitempty"` +} + +// ValidationError represents a validation error. +type ValidationError struct { + Code string `json:"code"` + Message string `json:"message"` + Location *string `json:"location,omitempty"` +} + +// ValidationResult represents the result of validation. +type ValidationResult struct { + Valid bool `json:"valid"` + Errors []ValidationError `json:"errors"` + Warnings []string `json:"warnings"` + ExportCount int `json:"export_count"` + ImportCount int `json:"import_count"` + FunctionCount int `json:"function_count"` + MemoryPages [2]*int `json:"memory_pages"` // [min, max] +} + +// ContractMetadata represents contract metadata. +type ContractMetadata struct { + Name *string `json:"name,omitempty"` + Version *string `json:"version,omitempty"` + Authors []string `json:"authors"` + Description *string `json:"description,omitempty"` + License *string `json:"license,omitempty"` + Repository *string `json:"repository,omitempty"` + BuildTimestamp *int64 `json:"build_timestamp,omitempty"` + RustVersion *string `json:"rust_version,omitempty"` + SdkVersion *string `json:"sdk_version,omitempty"` + Custom map[string]string `json:"custom"` +} + +// TypeInfo represents type information. +type TypeInfo struct { + Kind string `json:"kind"` + Size *int `json:"size,omitempty"` + Element *TypeInfo `json:"element,omitempty"` + Inner *TypeInfo `json:"inner,omitempty"` + Elements []TypeInfo `json:"elements,omitempty"` + Name *string `json:"name,omitempty"` + Fields []TypeField `json:"fields,omitempty"` +} + +// TypeField represents a struct field. +type TypeField struct { + Name string `json:"name"` + Type TypeInfo `json:"type"` +} + +// TypeName returns the type name as a string. +func (t TypeInfo) TypeName() string { + switch t.Kind { + case "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", + "bool", "string", "bytes", "address", "hash256": + return t.Kind + case "fixedBytes": + if t.Size != nil { + return fmt.Sprintf("bytes%d", *t.Size) + } + return "bytes" + case "array": + if t.Element != nil { + return t.Element.TypeName() + "[]" + } + return "[]" + case "fixedArray": + if t.Element != nil && t.Size != nil { + return fmt.Sprintf("%s[%d]", t.Element.TypeName(), *t.Size) + } + return "[]" + case "option": + if t.Inner != nil { + return t.Inner.TypeName() + "?" + } + return "?" + case "tuple": + names := make([]string, len(t.Elements)) + for i, e := range t.Elements { + names[i] = e.TypeName() + } + return "(" + join(names, ", ") + ")" + case "struct": + if t.Name != nil { + return *t.Name + } + return "struct" + default: + if t.Name != nil { + return *t.Name + } + return "unknown" + } +} + +func join(strs []string, sep string) string { + if len(strs) == 0 { + return "" + } + result := strs[0] + for i := 1; i < len(strs); i++ { + result += sep + strs[i] + } + return result +} + +// ParamAbi represents a parameter ABI. +type ParamAbi struct { + Name string `json:"name"` + Type TypeInfo `json:"type"` + Indexed bool `json:"indexed"` +} + +// FunctionAbi represents a function ABI. +type FunctionAbi struct { + Name string `json:"name"` + Selector string `json:"selector"` + Inputs []ParamAbi `json:"inputs"` + Outputs []ParamAbi `json:"outputs"` + View bool `json:"view"` + Payable bool `json:"payable"` + Doc *string `json:"doc,omitempty"` +} + +// EventAbi represents an event ABI. +type EventAbi struct { + Name string `json:"name"` + Topic string `json:"topic"` + Params []ParamAbi `json:"params"` + Doc *string `json:"doc,omitempty"` +} + +// ErrorAbi represents an error ABI. +type ErrorAbi struct { + Name string `json:"name"` + Selector string `json:"selector"` + Params []ParamAbi `json:"params"` + Doc *string `json:"doc,omitempty"` +} + +// ContractAbi represents the contract ABI. +type ContractAbi struct { + Name string `json:"name"` + Version string `json:"version"` + Functions []FunctionAbi `json:"functions"` + Events []EventAbi `json:"events"` + Errors []ErrorAbi `json:"errors"` +} + +// FindFunction finds a function by name. +func (a *ContractAbi) FindFunction(name string) *FunctionAbi { + for i := range a.Functions { + if a.Functions[i].Name == name { + return &a.Functions[i] + } + } + return nil +} + +// FindBySelector finds a function by selector. +func (a *ContractAbi) FindBySelector(selector string) *FunctionAbi { + for i := range a.Functions { + if a.Functions[i].Selector == selector { + return &a.Functions[i] + } + } + return nil +} + +// ToJSON serializes the ABI to JSON. +func (a *ContractAbi) ToJSON() (string, error) { + data, err := json.MarshalIndent(a, "", " ") + if err != nil { + return "", err + } + return string(data), nil +} + +// CompilationResult represents the result of compilation. +type CompilationResult struct { + ContractID string `json:"contract_id"` + Code string `json:"code"` // Base64 encoded + CodeHash string `json:"code_hash"` + OriginalSize int `json:"original_size"` + OptimizedSize int `json:"optimized_size"` + SizeReduction float64 `json:"size_reduction"` + Metadata *ContractMetadata `json:"metadata,omitempty"` + Abi *ContractAbi `json:"abi,omitempty"` + EstimatedDeployGas int64 `json:"estimated_deploy_gas"` + Validation *ValidationResult `json:"validation,omitempty"` + Warnings []string `json:"warnings"` +} + +// SizeStats returns size statistics as a formatted string. +func (r *CompilationResult) SizeStats() string { + return fmt.Sprintf("Original: %d bytes, Optimized: %d bytes, Reduction: %.1f%%", + r.OriginalSize, r.OptimizedSize, r.SizeReduction) +} + +// SourceMapping represents a source mapping entry. +type SourceMapping struct { + WasmOffset int `json:"wasm_offset"` + Line int `json:"line"` + Column int `json:"column"` + Function *string `json:"function,omitempty"` +} + +// SourceMap represents a source map. +type SourceMap struct { + File string `json:"file"` + Mappings []SourceMapping `json:"mappings"` +} + +// SizeBreakdown represents size breakdown. +type SizeBreakdown struct { + Code int `json:"code"` + Data int `json:"data"` + Types int `json:"types"` + Functions int `json:"functions"` + Memory int `json:"memory"` + Table int `json:"table"` + Exports int `json:"exports"` + Imports int `json:"imports"` + Custom int `json:"custom"` + Total int `json:"total"` +} + +// FunctionAnalysis represents function analysis. +type FunctionAnalysis struct { + Name string `json:"name"` + Size int `json:"size"` + InstructionCount int `json:"instruction_count"` + LocalCount int `json:"local_count"` + Exported bool `json:"exported"` + EstimatedGas int64 `json:"estimated_gas"` +} + +// ImportAnalysis represents import analysis. +type ImportAnalysis struct { + Module string `json:"module"` + Name string `json:"name"` + Kind string `json:"kind"` // function, memory, table, global + Signature *string `json:"signature,omitempty"` +} + +// SecurityIssue represents a security issue. +type SecurityIssue struct { + Severity string `json:"severity"` // low, medium, high, critical + Type string `json:"type"` + Description string `json:"description"` + Location *string `json:"location,omitempty"` +} + +// SecurityAnalysis represents security analysis. +type SecurityAnalysis struct { + Score int `json:"score"` // 0-100 + Issues []SecurityIssue `json:"issues"` + Recommendations []string `json:"recommendations"` +} + +// GasAnalysis represents gas analysis. +type GasAnalysis struct { + DeploymentGas int64 `json:"deployment_gas"` + FunctionGas map[string]int `json:"function_gas"` + MemoryInitGas int64 `json:"memory_init_gas"` + DataSectionGas int64 `json:"data_section_gas"` +} + +// ContractAnalysis represents contract analysis result. +type ContractAnalysis struct { + SizeBreakdown SizeBreakdown `json:"size_breakdown"` + Functions []FunctionAnalysis `json:"functions"` + Imports []ImportAnalysis `json:"imports"` + Security *SecurityAnalysis `json:"security,omitempty"` + GasAnalysis *GasAnalysis `json:"gas_analysis,omitempty"` +} + +// CompilerError represents a compiler error. +type CompilerError struct { + Message string + Code string + HTTPStatus int +} + +func (e *CompilerError) Error() string { + if e.Code != "" { + return fmt.Sprintf("%s (%s)", e.Message, e.Code) + } + return e.Message +} + +// Constants +const ( + MaxContractSize = 256 * 1024 // 256 KB + MaxMemoryPages = 16 +) diff --git a/sdk/java/src/main/java/io/synor/compiler/SynorCompiler.java b/sdk/java/src/main/java/io/synor/compiler/SynorCompiler.java new file mode 100644 index 0000000..dc374fe --- /dev/null +++ b/sdk/java/src/main/java/io/synor/compiler/SynorCompiler.java @@ -0,0 +1,329 @@ +package io.synor.compiler; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +/** + * Synor Compiler SDK Client + * + * Smart contract compilation, optimization, and ABI generation. + * + * Example: + *
{@code
+ * var config = new CompilerConfig("your-api-key");
+ * var compiler = new SynorCompiler(config);
+ *
+ * var result = compiler.compile(wasmBytes).join();
+ * System.out.println("Optimized size: " + result.getOptimizedSize() + " bytes");
+ * }
+ */ +public class SynorCompiler implements AutoCloseable { + private final CompilerConfig config; + private final HttpClient httpClient; + private final Gson gson; + private final AtomicBoolean closed = new AtomicBoolean(false); + + public final ContractsClient contracts; + public final AbiClient abi; + public final AnalysisClient analysis; + public final ValidationClient validation; + + public SynorCompiler(CompilerConfig config) { + this.config = config; + this.httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(config.getTimeout())) + .build(); + this.gson = new GsonBuilder() + .setFieldNamingPolicy(com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + this.contracts = new ContractsClient(this); + this.abi = new AbiClient(this); + this.analysis = new AnalysisClient(this); + this.validation = new ValidationClient(this); + } + + public OptimizationLevel getDefaultOptimizationLevel() { + return config.getOptimizationLevel(); + } + + public CompletableFuture healthCheck() { + return get("/health", new TypeToken>() {}) + .thenApply(result -> "healthy".equals(result.get("status"))) + .exceptionally(e -> false); + } + + public CompletableFuture> getInfo() { + return get("/info", new TypeToken>() {}); + } + + @Override + public void close() { + closed.set(true); + } + + public boolean isClosed() { + return closed.get(); + } + + public CompletableFuture compile(byte[] wasm) { + return compile(wasm, null); + } + + public CompletableFuture compile(byte[] wasm, CompilationRequest request) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + + Map body = new HashMap<>(); + body.put("wasm", wasmBase64); + body.put("optimization_level", (request != null && request.getOptimizationLevel() != null + ? request.getOptimizationLevel() : config.getOptimizationLevel()).toApiString()); + body.put("use_wasm_opt", request != null && request.getUseWasmOpt() != null + ? request.getUseWasmOpt() : config.isUseWasmOpt()); + body.put("validate", request != null && request.getValidate() != null + ? request.getValidate() : config.isValidate()); + body.put("extract_metadata", request != null && request.getExtractMetadata() != null + ? request.getExtractMetadata() : config.isExtractMetadata()); + body.put("generate_abi", request != null && request.getGenerateAbi() != null + ? request.getGenerateAbi() : config.isGenerateAbi()); + + if (request != null && request.getStripOptions() != null) { + body.put("strip_options", request.getStripOptions().toMap()); + } + + return post("/compile", body, new TypeToken() {}); + } + + CompletableFuture get(String path, TypeToken typeToken) { + checkClosed(); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(config.getEndpoint() + path)) + .header("Authorization", "Bearer " + config.getApiKey()) + .header("Content-Type", "application/json") + .header("X-SDK-Version", "java/0.1.0") + .GET() + .timeout(Duration.ofMillis(config.getTimeout())) + .build(); + + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(response -> handleResponse(response, typeToken)); + } + + CompletableFuture post(String path, Object body, TypeToken typeToken) { + checkClosed(); + + String jsonBody = gson.toJson(body); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(config.getEndpoint() + path)) + .header("Authorization", "Bearer " + config.getApiKey()) + .header("Content-Type", "application/json") + .header("X-SDK-Version", "java/0.1.0") + .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .timeout(Duration.ofMillis(config.getTimeout())) + .build(); + + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(response -> handleResponse(response, typeToken)); + } + + private T handleResponse(HttpResponse response, TypeToken typeToken) { + if (response.statusCode() >= 400) { + Map error; + try { + error = gson.fromJson(response.body(), new TypeToken>() {}.getType()); + } catch (Exception e) { + error = Map.of("message", response.body().isEmpty() ? "HTTP " + response.statusCode() : response.body()); + } + throw new CompilerException( + (String) error.getOrDefault("message", "HTTP " + response.statusCode()), + (String) error.get("code"), + response.statusCode() + ); + } + + return gson.fromJson(response.body(), typeToken.getType()); + } + + private void checkClosed() { + if (closed.get()) { + throw new CompilerException("Client has been closed", "CLIENT_CLOSED", null); + } + } + + Gson getGson() { + return gson; + } +} + +class ContractsClient { + private final SynorCompiler compiler; + + ContractsClient(SynorCompiler compiler) { + this.compiler = compiler; + } + + public CompletableFuture compile(byte[] wasm) { + return compiler.compile(wasm, null); + } + + public CompletableFuture compile(byte[] wasm, CompilationRequest request) { + return compiler.compile(wasm, request); + } + + public CompletableFuture compileDev(byte[] wasm) { + CompilationRequest request = new CompilationRequest(); + request.setOptimizationLevel(OptimizationLevel.NONE); + request.setUseWasmOpt(false); + request.setValidate(true); + return compiler.compile(wasm, request); + } + + public CompletableFuture compileProduction(byte[] wasm) { + CompilationRequest request = new CompilationRequest(); + request.setOptimizationLevel(OptimizationLevel.AGGRESSIVE); + request.setUseWasmOpt(true); + request.setValidate(true); + request.setExtractMetadata(true); + request.setGenerateAbi(true); + return compiler.compile(wasm, request); + } + + public CompletableFuture get(String contractId) { + return compiler.get("/contracts/" + contractId, new TypeToken() {}); + } + + public CompletableFuture> list(Integer limit, Integer offset) { + StringBuilder path = new StringBuilder("/contracts"); + List params = new ArrayList<>(); + if (limit != null) params.add("limit=" + limit); + if (offset != null) params.add("offset=" + offset); + if (!params.isEmpty()) path.append("?").append(String.join("&", params)); + + return compiler.get(path.toString(), new TypeToken>>() {}) + .thenApply(result -> result.getOrDefault("contracts", List.of())); + } + + public CompletableFuture getCode(String contractId) { + return compiler.get("/contracts/" + contractId + "/code", new TypeToken>() {}) + .thenApply(result -> Base64.getDecoder().decode(result.getOrDefault("code", ""))); + } +} + +class AbiClient { + private final SynorCompiler compiler; + + AbiClient(SynorCompiler compiler) { + this.compiler = compiler; + } + + public CompletableFuture extract(byte[] wasm) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + return compiler.post("/abi/extract", Map.of("wasm", wasmBase64), new TypeToken() {}); + } + + public CompletableFuture get(String contractId) { + return compiler.get("/contracts/" + contractId + "/abi", new TypeToken() {}); + } + + public CompletableFuture encodeCall(FunctionAbi functionAbi, List args) { + Map body = new HashMap<>(); + body.put("function", functionAbi); + body.put("args", args); + return compiler.post("/abi/encode", body, new TypeToken>() {}) + .thenApply(result -> result.getOrDefault("data", "")); + } + + public CompletableFuture> decodeResult(FunctionAbi functionAbi, String data) { + Map body = new HashMap<>(); + body.put("function", functionAbi); + body.put("data", data); + return compiler.post("/abi/decode", body, new TypeToken>>() {}) + .thenApply(result -> result.getOrDefault("values", List.of())); + } +} + +class AnalysisClient { + private final SynorCompiler compiler; + + AnalysisClient(SynorCompiler compiler) { + this.compiler = compiler; + } + + public CompletableFuture analyze(byte[] wasm) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + return compiler.post("/analysis", Map.of("wasm", wasmBase64), new TypeToken() {}); + } + + public CompletableFuture get(String contractId) { + return compiler.get("/contracts/" + contractId + "/analysis", new TypeToken() {}); + } + + public CompletableFuture extractMetadata(byte[] wasm) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + return compiler.post("/analysis/metadata", Map.of("wasm", wasmBase64), new TypeToken() {}); + } + + public CompletableFuture estimateDeployGas(byte[] wasm) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + return compiler.post("/analysis/estimate-gas", Map.of("wasm", wasmBase64), new TypeToken>() {}) + .thenApply(result -> result.getOrDefault("gas", 0L)); + } + + public CompletableFuture securityScan(byte[] wasm) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + return compiler.post("/analysis/security", Map.of("wasm", wasmBase64), new TypeToken>() {}) + .thenApply(result -> result.get("security")); + } +} + +class ValidationClient { + private final SynorCompiler compiler; + + ValidationClient(SynorCompiler compiler) { + this.compiler = compiler; + } + + public CompletableFuture validate(byte[] wasm) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + return compiler.post("/validate", Map.of("wasm", wasmBase64), new TypeToken() {}); + } + + public CompletableFuture isValid(byte[] wasm) { + return validate(wasm).thenApply(ValidationResult::isValid); + } + + public CompletableFuture> getErrors(byte[] wasm) { + return validate(wasm).thenApply(result -> + result.getErrors().stream().map(ValidationError::getMessage).toList() + ); + } + + public CompletableFuture validateExports(byte[] wasm, List requiredExports) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + Map body = new HashMap<>(); + body.put("wasm", wasmBase64); + body.put("required_exports", requiredExports); + return compiler.post("/validate/exports", body, new TypeToken>() {}) + .thenApply(result -> result.getOrDefault("valid", false)); + } + + public CompletableFuture validateMemory(byte[] wasm, int maxPages) { + String wasmBase64 = Base64.getEncoder().encodeToString(wasm); + Map body = new HashMap<>(); + body.put("wasm", wasmBase64); + body.put("max_pages", maxPages); + return compiler.post("/validate/memory", body, new TypeToken>() {}) + .thenApply(result -> result.getOrDefault("valid", false)); + } +} diff --git a/sdk/java/src/main/java/io/synor/compiler/Types.java b/sdk/java/src/main/java/io/synor/compiler/Types.java new file mode 100644 index 0000000..9f80439 --- /dev/null +++ b/sdk/java/src/main/java/io/synor/compiler/Types.java @@ -0,0 +1,494 @@ +package io.synor.compiler; + +import java.util.*; + +/** + * Synor Compiler SDK Types + */ + +// Enumerations + +enum OptimizationLevel { + NONE("none"), + BASIC("basic"), + SIZE("size"), + AGGRESSIVE("aggressive"); + + private final String value; + + OptimizationLevel(String value) { + this.value = value; + } + + public String toApiString() { + return value; + } + + public static OptimizationLevel fromApiString(String value) { + for (OptimizationLevel level : values()) { + if (level.value.equals(value)) { + return level; + } + } + return SIZE; + } +} + +// Configuration + +class StripOptions { + private boolean stripDebug = true; + private boolean stripProducers = true; + private boolean stripNames = true; + private boolean stripCustom = true; + private List preserveSections = new ArrayList<>(); + private boolean stripUnused = true; + + public boolean isStripDebug() { return stripDebug; } + public void setStripDebug(boolean stripDebug) { this.stripDebug = stripDebug; } + + public boolean isStripProducers() { return stripProducers; } + public void setStripProducers(boolean stripProducers) { this.stripProducers = stripProducers; } + + public boolean isStripNames() { return stripNames; } + public void setStripNames(boolean stripNames) { this.stripNames = stripNames; } + + public boolean isStripCustom() { return stripCustom; } + public void setStripCustom(boolean stripCustom) { this.stripCustom = stripCustom; } + + public List getPreserveSections() { return preserveSections; } + public void setPreserveSections(List preserveSections) { this.preserveSections = preserveSections; } + + public boolean isStripUnused() { return stripUnused; } + public void setStripUnused(boolean stripUnused) { this.stripUnused = stripUnused; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("strip_debug", stripDebug); + map.put("strip_producers", stripProducers); + map.put("strip_names", stripNames); + map.put("strip_custom", stripCustom); + map.put("preserve_sections", preserveSections); + map.put("strip_unused", stripUnused); + return map; + } +} + +class CompilerConfig { + private final String apiKey; + private String endpoint = "https://compiler.synor.io/v1"; + private long timeout = 60000; + private int retries = 3; + private boolean debug = false; + private OptimizationLevel optimizationLevel = OptimizationLevel.SIZE; + private StripOptions stripOptions; + private int maxContractSize = 256 * 1024; + private boolean useWasmOpt = true; + private boolean validate = true; + private boolean extractMetadata = true; + private boolean generateAbi = true; + + public CompilerConfig(String apiKey) { + this.apiKey = apiKey; + } + + public String getApiKey() { return apiKey; } + public String getEndpoint() { return endpoint; } + public void setEndpoint(String endpoint) { this.endpoint = endpoint; } + public long getTimeout() { return timeout; } + public void setTimeout(long timeout) { this.timeout = timeout; } + public int getRetries() { return retries; } + public void setRetries(int retries) { this.retries = retries; } + public boolean isDebug() { return debug; } + public void setDebug(boolean debug) { this.debug = debug; } + public OptimizationLevel getOptimizationLevel() { return optimizationLevel; } + public void setOptimizationLevel(OptimizationLevel level) { this.optimizationLevel = level; } + public StripOptions getStripOptions() { return stripOptions; } + public void setStripOptions(StripOptions stripOptions) { this.stripOptions = stripOptions; } + public int getMaxContractSize() { return maxContractSize; } + public void setMaxContractSize(int maxContractSize) { this.maxContractSize = maxContractSize; } + public boolean isUseWasmOpt() { return useWasmOpt; } + public void setUseWasmOpt(boolean useWasmOpt) { this.useWasmOpt = useWasmOpt; } + public boolean isValidate() { return validate; } + public void setValidate(boolean validate) { this.validate = validate; } + public boolean isExtractMetadata() { return extractMetadata; } + public void setExtractMetadata(boolean extractMetadata) { this.extractMetadata = extractMetadata; } + public boolean isGenerateAbi() { return generateAbi; } + public void setGenerateAbi(boolean generateAbi) { this.generateAbi = generateAbi; } +} + +class CompilationRequest { + private OptimizationLevel optimizationLevel; + private StripOptions stripOptions; + private Boolean useWasmOpt; + private Boolean validate; + private Boolean extractMetadata; + private Boolean generateAbi; + + public OptimizationLevel getOptimizationLevel() { return optimizationLevel; } + public void setOptimizationLevel(OptimizationLevel optimizationLevel) { this.optimizationLevel = optimizationLevel; } + public StripOptions getStripOptions() { return stripOptions; } + public void setStripOptions(StripOptions stripOptions) { this.stripOptions = stripOptions; } + public Boolean getUseWasmOpt() { return useWasmOpt; } + public void setUseWasmOpt(Boolean useWasmOpt) { this.useWasmOpt = useWasmOpt; } + public Boolean getValidate() { return validate; } + public void setValidate(Boolean validate) { this.validate = validate; } + public Boolean getExtractMetadata() { return extractMetadata; } + public void setExtractMetadata(Boolean extractMetadata) { this.extractMetadata = extractMetadata; } + public Boolean getGenerateAbi() { return generateAbi; } + public void setGenerateAbi(Boolean generateAbi) { this.generateAbi = generateAbi; } +} + +// Validation + +class ValidationError { + private String code; + private String message; + private String location; + + public String getCode() { return code; } + public String getMessage() { return message; } + public String getLocation() { return location; } +} + +class ValidationResult { + private boolean valid; + private List errors = new ArrayList<>(); + private List warnings = new ArrayList<>(); + private int exportCount; + private int importCount; + private int functionCount; + private int[] memoryPages = {0, 0}; + + public boolean isValid() { return valid; } + public List getErrors() { return errors; } + public List getWarnings() { return warnings; } + public int getExportCount() { return exportCount; } + public int getImportCount() { return importCount; } + public int getFunctionCount() { return functionCount; } + public int[] getMemoryPages() { return memoryPages; } +} + +// Metadata + +class ContractMetadata { + private String name; + private String version; + private List authors = new ArrayList<>(); + private String description; + private String license; + private String repository; + private Long buildTimestamp; + private String rustVersion; + private String sdkVersion; + private Map custom = new HashMap<>(); + + public String getName() { return name; } + public String getVersion() { return version; } + public List getAuthors() { return authors; } + public String getDescription() { return description; } + public String getLicense() { return license; } + public String getRepository() { return repository; } + public Long getBuildTimestamp() { return buildTimestamp; } + public String getRustVersion() { return rustVersion; } + public String getSdkVersion() { return sdkVersion; } + public Map getCustom() { return custom; } +} + +// ABI Types + +class TypeInfo { + private String kind; + private Integer size; + private TypeInfo element; + private TypeInfo inner; + private List elements; + private String name; + private List fields; + + public String getKind() { return kind; } + public Integer getSize() { return size; } + public TypeInfo getElement() { return element; } + public TypeInfo getInner() { return inner; } + public List getElements() { return elements; } + public String getName() { return name; } + public List getFields() { return fields; } + + public String getTypeName() { + switch (kind) { + case "u8": case "u16": case "u32": case "u64": case "u128": + case "i8": case "i16": case "i32": case "i64": case "i128": + case "bool": case "string": case "bytes": case "address": case "hash256": + return kind; + case "fixedBytes": + return "bytes" + (size != null ? size : 0); + case "array": + return (element != null ? element.getTypeName() : "") + "[]"; + case "fixedArray": + return (element != null ? element.getTypeName() : "") + "[" + (size != null ? size : 0) + "]"; + case "option": + return (inner != null ? inner.getTypeName() : "") + "?"; + case "tuple": + if (elements != null) { + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < elements.size(); i++) { + if (i > 0) sb.append(", "); + sb.append(elements.get(i).getTypeName()); + } + sb.append(")"); + return sb.toString(); + } + return "()"; + case "struct": + return name != null ? name : "struct"; + default: + return name != null ? name : "unknown"; + } + } +} + +class TypeField { + private String name; + private TypeInfo type; + + public String getName() { return name; } + public TypeInfo getType() { return type; } +} + +class ParamAbi { + private String name; + private TypeInfo type; + private boolean indexed; + + public String getName() { return name; } + public TypeInfo getType() { return type; } + public boolean isIndexed() { return indexed; } +} + +class FunctionAbi { + private String name; + private String selector; + private List inputs = new ArrayList<>(); + private List outputs = new ArrayList<>(); + private boolean view; + private boolean payable; + private String doc; + + public String getName() { return name; } + public String getSelector() { return selector; } + public List getInputs() { return inputs; } + public List getOutputs() { return outputs; } + public boolean isView() { return view; } + public boolean isPayable() { return payable; } + public String getDoc() { return doc; } +} + +class EventAbi { + private String name; + private String topic; + private List params = new ArrayList<>(); + private String doc; + + public String getName() { return name; } + public String getTopic() { return topic; } + public List getParams() { return params; } + public String getDoc() { return doc; } +} + +class ErrorAbi { + private String name; + private String selector; + private List params = new ArrayList<>(); + private String doc; + + public String getName() { return name; } + public String getSelector() { return selector; } + public List getParams() { return params; } + public String getDoc() { return doc; } +} + +class ContractAbi { + private String name; + private String version; + private List functions = new ArrayList<>(); + private List events = new ArrayList<>(); + private List errors = new ArrayList<>(); + + public String getName() { return name; } + public String getVersion() { return version; } + public List getFunctions() { return functions; } + public List getEvents() { return events; } + public List getErrors() { return errors; } + + public FunctionAbi findFunction(String name) { + return functions.stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null); + } + + public FunctionAbi findBySelector(String selector) { + return functions.stream().filter(f -> f.getSelector().equals(selector)).findFirst().orElse(null); + } +} + +// Compilation Result + +class CompilationResult { + private String contractId; + private String code; + private String codeHash; + private int originalSize; + private int optimizedSize; + private double sizeReduction; + private ContractMetadata metadata; + private ContractAbi abi; + private long estimatedDeployGas; + private ValidationResult validation; + private List warnings = new ArrayList<>(); + + public String getContractId() { return contractId; } + public String getCode() { return code; } + public String getCodeHash() { return codeHash; } + public int getOriginalSize() { return originalSize; } + public int getOptimizedSize() { return optimizedSize; } + public double getSizeReduction() { return sizeReduction; } + public ContractMetadata getMetadata() { return metadata; } + public ContractAbi getAbi() { return abi; } + public long getEstimatedDeployGas() { return estimatedDeployGas; } + public ValidationResult getValidation() { return validation; } + public List getWarnings() { return warnings; } + + public String getSizeStats() { + return String.format("Original: %d bytes, Optimized: %d bytes, Reduction: %.1f%%", + originalSize, optimizedSize, sizeReduction); + } + + public byte[] decodeCode() { + return Base64.getDecoder().decode(code); + } +} + +// Analysis + +class SizeBreakdown { + private int code; + private int data; + private int types; + private int functions; + private int memory; + private int table; + private int exports; + private int imports; + private int custom; + private int total; + + public int getCode() { return code; } + public int getData() { return data; } + public int getTypes() { return types; } + public int getFunctions() { return functions; } + public int getMemory() { return memory; } + public int getTable() { return table; } + public int getExports() { return exports; } + public int getImports() { return imports; } + public int getCustom() { return custom; } + public int getTotal() { return total; } +} + +class FunctionAnalysis { + private String name; + private int size; + private int instructionCount; + private int localCount; + private boolean exported; + private long estimatedGas; + + public String getName() { return name; } + public int getSize() { return size; } + public int getInstructionCount() { return instructionCount; } + public int getLocalCount() { return localCount; } + public boolean isExported() { return exported; } + public long getEstimatedGas() { return estimatedGas; } +} + +class ImportAnalysis { + private String module; + private String name; + private String kind; + private String signature; + + public String getModule() { return module; } + public String getName() { return name; } + public String getKind() { return kind; } + public String getSignature() { return signature; } +} + +class SecurityIssue { + private String severity; + private String type; + private String description; + private String location; + + public String getSeverity() { return severity; } + public String getType() { return type; } + public String getDescription() { return description; } + public String getLocation() { return location; } +} + +class SecurityAnalysis { + private int score; + private List issues = new ArrayList<>(); + private List recommendations = new ArrayList<>(); + + public int getScore() { return score; } + public List getIssues() { return issues; } + public List getRecommendations() { return recommendations; } +} + +class GasAnalysis { + private long deploymentGas; + private Map functionGas = new HashMap<>(); + private long memoryInitGas; + private long dataSectionGas; + + public long getDeploymentGas() { return deploymentGas; } + public Map getFunctionGas() { return functionGas; } + public long getMemoryInitGas() { return memoryInitGas; } + public long getDataSectionGas() { return dataSectionGas; } +} + +class ContractAnalysis { + private SizeBreakdown sizeBreakdown; + private List functions = new ArrayList<>(); + private List imports = new ArrayList<>(); + private SecurityAnalysis security; + private GasAnalysis gasAnalysis; + + public SizeBreakdown getSizeBreakdown() { return sizeBreakdown; } + public List getFunctions() { return functions; } + public List getImports() { return imports; } + public SecurityAnalysis getSecurity() { return security; } + public GasAnalysis getGasAnalysis() { return gasAnalysis; } +} + +// Error + +class CompilerException extends RuntimeException { + private final String code; + private final Integer httpStatus; + + public CompilerException(String message, String code, Integer httpStatus) { + super(message); + this.code = code; + this.httpStatus = httpStatus; + } + + public String getCode() { return code; } + public Integer getHttpStatus() { return httpStatus; } +} + +// Constants + +final class CompilerConstants { + public static final int MAX_CONTRACT_SIZE = 256 * 1024; + public static final int MAX_MEMORY_PAGES = 16; + + private CompilerConstants() {} +} diff --git a/sdk/js/src/compiler/index.ts b/sdk/js/src/compiler/index.ts new file mode 100644 index 0000000..58ef09c --- /dev/null +++ b/sdk/js/src/compiler/index.ts @@ -0,0 +1,539 @@ +/** + * Synor Compiler SDK + * + * Smart contract compilation, optimization, and ABI generation. + */ + +import { + CompilerConfig, + CompilerError, + CompilationRequest, + CompilationResult, + ContractAbi, + ContractAnalysis, + ContractMetadata, + FunctionAbi, + OptimizationLevel, + SourceMap, + StripOptions, + ValidationResult, + DEFAULT_COMPILER_CONFIG, + DEFAULT_STRIP_OPTIONS, +} from './types'; + +export * from './types'; + +/* ============================================================================ + * Main Client + * ============================================================================ */ + +/** + * Synor Compiler SDK Client + * + * Smart contract compilation, optimization, and ABI generation. + * + * @example + * ```typescript + * const compiler = new SynorCompiler({ apiKey: 'your-api-key' }); + * + * // Compile WASM bytecode + * const result = await compiler.compile(wasmBytes, { + * optimizationLevel: OptimizationLevel.Size, + * }); + * + * console.log(`Optimized size: ${result.optimizedSize} bytes`); + * console.log(`Size reduction: ${result.sizeReduction.toFixed(1)}%`); + * ``` + */ +export class SynorCompiler { + private readonly config: Required< + Omit & { stripOptions: StripOptions } + >; + private closed = false; + + /** Contracts sub-client */ + public readonly contracts: ContractsClient; + /** ABI sub-client */ + public readonly abi: AbiClient; + /** Analysis sub-client */ + public readonly analysis: AnalysisClient; + /** Validation sub-client */ + public readonly validation: ValidationClient; + + constructor(config: CompilerConfig) { + this.config = { + ...DEFAULT_COMPILER_CONFIG, + ...config, + endpoint: config.endpoint ?? DEFAULT_COMPILER_CONFIG.endpoint!, + timeout: config.timeout ?? DEFAULT_COMPILER_CONFIG.timeout!, + retries: config.retries ?? DEFAULT_COMPILER_CONFIG.retries!, + debug: config.debug ?? DEFAULT_COMPILER_CONFIG.debug!, + optimizationLevel: config.optimizationLevel ?? DEFAULT_COMPILER_CONFIG.optimizationLevel!, + useWasmOpt: config.useWasmOpt ?? DEFAULT_COMPILER_CONFIG.useWasmOpt!, + validate: config.validate ?? DEFAULT_COMPILER_CONFIG.validate!, + extractMetadata: config.extractMetadata ?? DEFAULT_COMPILER_CONFIG.extractMetadata!, + generateAbi: config.generateAbi ?? DEFAULT_COMPILER_CONFIG.generateAbi!, + maxContractSize: config.maxContractSize ?? 256 * 1024, + stripOptions: { + ...DEFAULT_STRIP_OPTIONS, + ...config.stripOptions, + }, + }; + + this.contracts = new ContractsClient(this); + this.abi = new AbiClient(this); + this.analysis = new AnalysisClient(this); + this.validation = new ValidationClient(this); + } + + /** Get the default optimization level */ + get defaultOptimizationLevel(): OptimizationLevel { + return this.config.optimizationLevel; + } + + /** Check service health */ + async healthCheck(): Promise { + try { + const result = await this.get<{ status: string }>('/health'); + return result.status === 'healthy'; + } catch { + return false; + } + } + + /** Get service info */ + async getInfo(): Promise> { + return this.get('/info'); + } + + /** Close the client */ + close(): void { + this.closed = true; + } + + /** Check if client is closed */ + get isClosed(): boolean { + return this.closed; + } + + /** + * Compile WASM bytecode + * + * @param wasm - WASM bytecode as Uint8Array or base64 string + * @param options - Compilation options + * @returns Compilation result + */ + async compile( + wasm: Uint8Array | string, + options?: Partial + ): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + + return this.post('/compile', { + wasm: wasmBase64, + optimization_level: options?.optimizationLevel ?? this.config.optimizationLevel, + strip_options: options?.stripOptions ?? this.config.stripOptions, + use_wasm_opt: options?.useWasmOpt ?? this.config.useWasmOpt, + validate: options?.validate ?? this.config.validate, + extract_metadata: options?.extractMetadata ?? this.config.extractMetadata, + generate_abi: options?.generateAbi ?? this.config.generateAbi, + }); + } + + /** Internal GET request */ + async get(path: string): Promise { + this.checkClosed(); + const response = await this.request('GET', path); + return this.handleResponse(response); + } + + /** Internal POST request */ + async post(path: string, body?: unknown): Promise { + this.checkClosed(); + const response = await this.request('POST', path, body); + return this.handleResponse(response); + } + + private async request(method: string, path: string, body?: unknown): Promise { + const url = `${this.config.endpoint}${path}`; + const headers: Record = { + Authorization: `Bearer ${this.config.apiKey}`, + 'Content-Type': 'application/json', + 'X-SDK-Version': 'js/0.1.0', + }; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); + + try { + const response = await fetch(url, { + method, + headers, + body: body ? JSON.stringify(body) : undefined, + signal: controller.signal, + }); + return response; + } finally { + clearTimeout(timeoutId); + } + } + + private async handleResponse(response: Response): Promise { + const text = await response.text(); + + if (!response.ok) { + let error: { message?: string; code?: string } = {}; + try { + error = JSON.parse(text); + } catch { + error = { message: text || `HTTP ${response.status}` }; + } + throw new CompilerError( + error.message ?? `HTTP ${response.status}`, + error.code, + response.status + ); + } + + return JSON.parse(text, this.reviver) as T; + } + + private reviver(_key: string, value: unknown): unknown { + if (typeof value === 'string') { + // Convert snake_case to camelCase for keys handled during JSON parse + return value; + } + if (value && typeof value === 'object' && !Array.isArray(value)) { + const converted: Record = {}; + for (const [k, v] of Object.entries(value)) { + const camelKey = k.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); + converted[camelKey] = v; + } + return converted; + } + return value; + } + + private checkClosed(): void { + if (this.closed) { + throw new CompilerError('Client has been closed', 'CLIENT_CLOSED'); + } + } + + private toBase64(data: Uint8Array): string { + if (typeof Buffer !== 'undefined') { + return Buffer.from(data).toString('base64'); + } + return btoa(String.fromCharCode(...data)); + } +} + +/* ============================================================================ + * Contracts Sub-Client + * ============================================================================ */ + +/** Contracts sub-client for contract compilation */ +export class ContractsClient { + constructor(private readonly compiler: SynorCompiler) {} + + /** + * Compile WASM bytecode with default settings + */ + async compile( + wasm: Uint8Array | string, + options?: Partial + ): Promise { + return this.compiler.compile(wasm, options); + } + + /** + * Compile with development settings (fast, minimal optimization) + */ + async compileDev(wasm: Uint8Array | string): Promise { + return this.compiler.compile(wasm, { + optimizationLevel: OptimizationLevel.None, + useWasmOpt: false, + validate: true, + }); + } + + /** + * Compile with production settings (aggressive optimization) + */ + async compileProduction(wasm: Uint8Array | string): Promise { + return this.compiler.compile(wasm, { + optimizationLevel: OptimizationLevel.Aggressive, + useWasmOpt: true, + validate: true, + extractMetadata: true, + generateAbi: true, + }); + } + + /** + * Get a compiled contract by ID + */ + async get(contractId: string): Promise { + return this.compiler.get(`/contracts/${contractId}`); + } + + /** + * List compiled contracts + */ + async list(limit?: number, offset?: number): Promise { + const params = new URLSearchParams(); + if (limit !== undefined) params.set('limit', limit.toString()); + if (offset !== undefined) params.set('offset', offset.toString()); + const query = params.toString() ? `?${params}` : ''; + + const result = await this.compiler.get<{ contracts: CompilationResult[] }>(`/contracts${query}`); + return result.contracts ?? []; + } + + /** + * Get optimized bytecode for a contract + */ + async getCode(contractId: string): Promise { + const result = await this.compiler.get<{ code: string }>(`/contracts/${contractId}/code`); + return this.fromBase64(result.code); + } + + private fromBase64(data: string): Uint8Array { + if (typeof Buffer !== 'undefined') { + return new Uint8Array(Buffer.from(data, 'base64')); + } + return new Uint8Array( + atob(data) + .split('') + .map((c) => c.charCodeAt(0)) + ); + } +} + +/* ============================================================================ + * ABI Sub-Client + * ============================================================================ */ + +/** ABI sub-client for ABI generation and encoding */ +export class AbiClient { + constructor(private readonly compiler: SynorCompiler) {} + + /** + * Extract ABI from WASM bytecode + */ + async extract(wasm: Uint8Array | string): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + return this.compiler.post('/abi/extract', { wasm: wasmBase64 }); + } + + /** + * Get ABI for a compiled contract + */ + async get(contractId: string): Promise { + return this.compiler.get(`/contracts/${contractId}/abi`); + } + + /** + * Encode function call + */ + async encodeCall(functionAbi: FunctionAbi, args: unknown[]): Promise { + const result = await this.compiler.post<{ data: string }>('/abi/encode', { + function: functionAbi, + args, + }); + return result.data; + } + + /** + * Decode function result + */ + async decodeResult(functionAbi: FunctionAbi, data: string): Promise { + const result = await this.compiler.post<{ values: unknown[] }>('/abi/decode', { + function: functionAbi, + data, + }); + return result.values; + } + + /** + * Compute function selector + */ + computeSelector(functionName: string): string { + // Use blake3 hash and take first 4 bytes + // This is a simplified version - actual implementation would use proper hashing + let hash = 0; + for (let i = 0; i < functionName.length; i++) { + const char = functionName.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + const bytes = new Uint8Array(4); + bytes[0] = (hash >> 24) & 0xff; + bytes[1] = (hash >> 16) & 0xff; + bytes[2] = (hash >> 8) & 0xff; + bytes[3] = hash & 0xff; + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); + } + + /** + * Generate TypeScript types from ABI + */ + async generateTypes(abi: ContractAbi): Promise { + const result = await this.compiler.post<{ typescript: string }>('/abi/generate-types', { + abi, + language: 'typescript', + }); + return result.typescript; + } + + private toBase64(data: Uint8Array): string { + if (typeof Buffer !== 'undefined') { + return Buffer.from(data).toString('base64'); + } + return btoa(String.fromCharCode(...data)); + } +} + +/* ============================================================================ + * Analysis Sub-Client + * ============================================================================ */ + +/** Analysis sub-client for contract analysis */ +export class AnalysisClient { + constructor(private readonly compiler: SynorCompiler) {} + + /** + * Analyze WASM bytecode + */ + async analyze(wasm: Uint8Array | string): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + return this.compiler.post('/analysis', { wasm: wasmBase64 }); + } + + /** + * Get analysis for a compiled contract + */ + async get(contractId: string): Promise { + return this.compiler.get(`/contracts/${contractId}/analysis`); + } + + /** + * Extract metadata from WASM bytecode + */ + async extractMetadata(wasm: Uint8Array | string): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + return this.compiler.post('/analysis/metadata', { wasm: wasmBase64 }); + } + + /** + * Extract source map from WASM bytecode + */ + async extractSourceMap(wasm: Uint8Array | string): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + const result = await this.compiler.post<{ sourceMap: SourceMap | null }>( + '/analysis/source-map', + { + wasm: wasmBase64, + } + ); + return result.sourceMap; + } + + /** + * Estimate gas for contract deployment + */ + async estimateDeployGas(wasm: Uint8Array | string): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + const result = await this.compiler.post<{ gas: number }>('/analysis/estimate-gas', { + wasm: wasmBase64, + }); + return result.gas; + } + + /** + * Get security analysis + */ + async securityScan(wasm: Uint8Array | string): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + const result = await this.compiler.post<{ security: ContractAnalysis['security'] }>( + '/analysis/security', + { + wasm: wasmBase64, + } + ); + return result.security; + } + + private toBase64(data: Uint8Array): string { + if (typeof Buffer !== 'undefined') { + return Buffer.from(data).toString('base64'); + } + return btoa(String.fromCharCode(...data)); + } +} + +/* ============================================================================ + * Validation Sub-Client + * ============================================================================ */ + +/** Validation sub-client for WASM validation */ +export class ValidationClient { + constructor(private readonly compiler: SynorCompiler) {} + + /** + * Validate WASM bytecode against VM requirements + */ + async validate(wasm: Uint8Array | string): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + return this.compiler.post('/validate', { wasm: wasmBase64 }); + } + + /** + * Check if WASM is valid + */ + async isValid(wasm: Uint8Array | string): Promise { + const result = await this.validate(wasm); + return result.valid; + } + + /** + * Get validation errors + */ + async getErrors(wasm: Uint8Array | string): Promise { + const result = await this.validate(wasm); + return result.errors.map((e) => e.message); + } + + /** + * Validate contract exports + */ + async validateExports(wasm: Uint8Array | string, requiredExports: string[]): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + const result = await this.compiler.post<{ valid: boolean }>('/validate/exports', { + wasm: wasmBase64, + required_exports: requiredExports, + }); + return result.valid; + } + + /** + * Validate memory limits + */ + async validateMemory(wasm: Uint8Array | string, maxPages: number): Promise { + const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm); + const result = await this.compiler.post<{ valid: boolean }>('/validate/memory', { + wasm: wasmBase64, + max_pages: maxPages, + }); + return result.valid; + } + + private toBase64(data: Uint8Array): string { + if (typeof Buffer !== 'undefined') { + return Buffer.from(data).toString('base64'); + } + return btoa(String.fromCharCode(...data)); + } +} diff --git a/sdk/js/src/compiler/types.ts b/sdk/js/src/compiler/types.ts new file mode 100644 index 0000000..8e7e976 --- /dev/null +++ b/sdk/js/src/compiler/types.ts @@ -0,0 +1,514 @@ +/** + * Synor Compiler SDK Types + * + * Smart contract compilation, optimization, and ABI generation. + */ + +/* ============================================================================ + * Enumerations + * ============================================================================ */ + +/** Optimization level for WASM compilation */ +export enum OptimizationLevel { + /** No optimization */ + None = 'none', + /** Basic optimizations (stripping only) */ + Basic = 'basic', + /** Optimize for size (-Os equivalent) */ + Size = 'size', + /** Aggressive optimization (-O3 -Os equivalent) */ + Aggressive = 'aggressive', +} + +/* ============================================================================ + * Configuration Types + * ============================================================================ */ + +/** Options for stripping WASM sections */ +export interface StripOptions { + /** Strip debug sections (.debug_*) */ + stripDebug: boolean; + /** Strip producer sections */ + stripProducers: boolean; + /** Strip name sections */ + stripNames: boolean; + /** Strip custom sections (all non-standard sections) */ + stripCustom: boolean; + /** Specific custom sections to preserve */ + preserveSections: string[]; + /** Strip unused functions */ + stripUnused: boolean; +} + +/** Compiler configuration */ +export interface CompilerConfig { + /** API key for authentication */ + apiKey: string; + /** Compiler service endpoint */ + endpoint?: string; + /** Request timeout in milliseconds */ + timeout?: number; + /** Maximum retry attempts */ + retries?: number; + /** Enable debug logging */ + debug?: boolean; + /** Optimization level */ + optimizationLevel?: OptimizationLevel; + /** Strip options */ + stripOptions?: Partial; + /** Maximum contract size in bytes */ + maxContractSize?: number; + /** Whether to use wasm-opt */ + useWasmOpt?: boolean; + /** Whether to validate against VM requirements */ + validate?: boolean; + /** Whether to extract metadata */ + extractMetadata?: boolean; + /** Whether to generate ABI */ + generateAbi?: boolean; +} + +/* ============================================================================ + * Compilation Types + * ============================================================================ */ + +/** Compilation request */ +export interface CompilationRequest { + /** WASM bytecode (base64 encoded) */ + wasm: string; + /** Optimization level */ + optimizationLevel?: OptimizationLevel; + /** Strip options */ + stripOptions?: Partial; + /** Whether to use wasm-opt */ + useWasmOpt?: boolean; + /** Whether to validate */ + validate?: boolean; + /** Whether to extract metadata */ + extractMetadata?: boolean; + /** Whether to generate ABI */ + generateAbi?: boolean; +} + +/** Compilation result */ +export interface CompilationResult { + /** Contract ID (hash of optimized code) */ + contractId: string; + /** Optimized WASM bytecode (base64 encoded) */ + code: string; + /** Code hash */ + codeHash: string; + /** Original code size in bytes */ + originalSize: number; + /** Optimized code size in bytes */ + optimizedSize: number; + /** Size reduction percentage */ + sizeReduction: number; + /** Contract metadata */ + metadata?: ContractMetadata; + /** Contract ABI */ + abi?: ContractAbi; + /** Estimated deployment gas */ + estimatedDeployGas: number; + /** Validation result */ + validation: ValidationResult; + /** Compilation warnings */ + warnings: string[]; +} + +/** Validation result */ +export interface ValidationResult { + /** Whether validation passed */ + valid: boolean; + /** Validation errors */ + errors: ValidationError[]; + /** Validation warnings */ + warnings: string[]; + /** Number of exports */ + exportCount: number; + /** Number of imports */ + importCount: number; + /** Number of functions */ + functionCount: number; + /** Memory pages (min, max) */ + memoryPages: [number, number | null]; +} + +/** Validation error */ +export interface ValidationError { + /** Error code */ + code: string; + /** Error message */ + message: string; + /** Location in WASM (if applicable) */ + location?: string; +} + +/* ============================================================================ + * Metadata Types + * ============================================================================ */ + +/** Contract metadata */ +export interface ContractMetadata { + /** Contract name */ + name?: string; + /** Contract version */ + version?: string; + /** Contract authors */ + authors: string[]; + /** Contract description */ + description?: string; + /** Contract license */ + license?: string; + /** Repository URL */ + repository?: string; + /** Build timestamp */ + buildTimestamp?: number; + /** Rust version used */ + rustVersion?: string; + /** Synor SDK version */ + sdkVersion?: string; + /** Custom metadata */ + custom: Record; +} + +/* ============================================================================ + * ABI Types + * ============================================================================ */ + +/** Contract ABI (Application Binary Interface) */ +export interface ContractAbi { + /** Contract name */ + name: string; + /** ABI version */ + version: string; + /** Functions */ + functions: FunctionAbi[]; + /** Events */ + events: EventAbi[]; + /** Errors */ + errors: ErrorAbi[]; +} + +/** Function ABI */ +export interface FunctionAbi { + /** Function name */ + name: string; + /** 4-byte selector (hex) */ + selector: string; + /** Input parameters */ + inputs: ParamAbi[]; + /** Output parameters */ + outputs: ParamAbi[]; + /** Whether function is read-only (view) */ + view: boolean; + /** Whether function is payable */ + payable: boolean; + /** Function documentation */ + doc?: string; +} + +/** Event ABI */ +export interface EventAbi { + /** Event name */ + name: string; + /** Event topic (hex) */ + topic: string; + /** Event parameters */ + params: ParamAbi[]; + /** Event documentation */ + doc?: string; +} + +/** Error ABI */ +export interface ErrorAbi { + /** Error name */ + name: string; + /** Error selector (hex) */ + selector: string; + /** Error parameters */ + params: ParamAbi[]; + /** Error documentation */ + doc?: string; +} + +/** Parameter ABI */ +export interface ParamAbi { + /** Parameter name */ + name: string; + /** Parameter type */ + type: TypeInfo; + /** Whether parameter is indexed (for events) */ + indexed: boolean; +} + +/** Type information */ +export type TypeInfo = + | { kind: 'u8' } + | { kind: 'u16' } + | { kind: 'u32' } + | { kind: 'u64' } + | { kind: 'u128' } + | { kind: 'i8' } + | { kind: 'i16' } + | { kind: 'i32' } + | { kind: 'i64' } + | { kind: 'i128' } + | { kind: 'bool' } + | { kind: 'string' } + | { kind: 'bytes' } + | { kind: 'fixedBytes'; size: number } + | { kind: 'address' } + | { kind: 'hash256' } + | { kind: 'array'; element: TypeInfo } + | { kind: 'fixedArray'; element: TypeInfo; size: number } + | { kind: 'option'; inner: TypeInfo } + | { kind: 'tuple'; elements: TypeInfo[] } + | { kind: 'struct'; name: string; fields: Array<{ name: string; type: TypeInfo }> } + | { kind: 'unknown'; name: string }; + +/* ============================================================================ + * Source Map Types + * ============================================================================ */ + +/** Source map for debugging */ +export interface SourceMap { + /** Source file name */ + file: string; + /** Mappings */ + mappings: SourceMapping[]; +} + +/** Source mapping entry */ +export interface SourceMapping { + /** WASM offset */ + wasmOffset: number; + /** Source line */ + line: number; + /** Source column */ + column: number; + /** Function name */ + function?: string; +} + +/* ============================================================================ + * Analysis Types + * ============================================================================ */ + +/** Contract analysis result */ +export interface ContractAnalysis { + /** Code size breakdown */ + sizeBreakdown: SizeBreakdown; + /** Function analysis */ + functions: FunctionAnalysis[]; + /** Import analysis */ + imports: ImportAnalysis[]; + /** Security analysis */ + security: SecurityAnalysis; + /** Gas analysis */ + gasAnalysis: GasAnalysis; +} + +/** Size breakdown */ +export interface SizeBreakdown { + /** Code section size */ + code: number; + /** Data section size */ + data: number; + /** Type section size */ + types: number; + /** Function section size */ + functions: number; + /** Memory section size */ + memory: number; + /** Table section size */ + table: number; + /** Export section size */ + exports: number; + /** Import section size */ + imports: number; + /** Custom sections size */ + custom: number; + /** Total size */ + total: number; +} + +/** Function analysis */ +export interface FunctionAnalysis { + /** Function name or index */ + name: string; + /** Function size in bytes */ + size: number; + /** Instruction count */ + instructionCount: number; + /** Local variable count */ + localCount: number; + /** Is exported */ + exported: boolean; + /** Estimated gas cost */ + estimatedGas: number; +} + +/** Import analysis */ +export interface ImportAnalysis { + /** Module name */ + module: string; + /** Import name */ + name: string; + /** Import kind */ + kind: 'function' | 'memory' | 'table' | 'global'; + /** Type signature (for functions) */ + signature?: string; +} + +/** Security analysis */ +export interface SecurityAnalysis { + /** Security score (0-100) */ + score: number; + /** Issues found */ + issues: SecurityIssue[]; + /** Recommendations */ + recommendations: string[]; +} + +/** Security issue */ +export interface SecurityIssue { + /** Severity level */ + severity: 'low' | 'medium' | 'high' | 'critical'; + /** Issue type */ + type: string; + /** Issue description */ + description: string; + /** Location in code */ + location?: string; +} + +/** Gas analysis */ +export interface GasAnalysis { + /** Base deployment gas */ + deploymentGas: number; + /** Per-function gas estimates */ + functionGas: Record; + /** Memory initialization gas */ + memoryInitGas: number; + /** Data section gas */ + dataSectionGas: number; +} + +/* ============================================================================ + * Error Types + * ============================================================================ */ + +/** Compiler error codes */ +export enum CompilerErrorCode { + /** WASM parsing error */ + ParseError = 'PARSE_ERROR', + /** Validation error */ + ValidationError = 'VALIDATION_ERROR', + /** Optimization error */ + OptimizationError = 'OPTIMIZATION_ERROR', + /** Contract too large */ + ContractTooLarge = 'CONTRACT_TOO_LARGE', + /** Missing required export */ + MissingExport = 'MISSING_EXPORT', + /** Invalid export signature */ + InvalidExportSignature = 'INVALID_EXPORT_SIGNATURE', + /** IO error */ + IoError = 'IO_ERROR', + /** Encoding error */ + EncodingError = 'ENCODING_ERROR', + /** External tool error */ + ExternalToolError = 'EXTERNAL_TOOL_ERROR', + /** Network error */ + NetworkError = 'NETWORK_ERROR', + /** Authentication error */ + AuthError = 'AUTH_ERROR', + /** Client closed */ + ClientClosed = 'CLIENT_CLOSED', +} + +/** Compiler error */ +export class CompilerError extends Error { + constructor( + message: string, + public readonly code?: string, + public readonly httpStatus?: number, + public readonly details?: Record + ) { + super(message); + this.name = 'CompilerError'; + } +} + +/* ============================================================================ + * Constants + * ============================================================================ */ + +/** Maximum contract size (256 KB) */ +export const MAX_CONTRACT_SIZE = 256 * 1024; + +/** Maximum memory pages */ +export const MAX_MEMORY_PAGES = 16; + +/** Default strip options */ +export const DEFAULT_STRIP_OPTIONS: StripOptions = { + stripDebug: true, + stripProducers: true, + stripNames: true, + stripCustom: true, + preserveSections: [], + stripUnused: true, +}; + +/** Default compiler config */ +export const DEFAULT_COMPILER_CONFIG: Partial = { + endpoint: 'https://compiler.synor.io/v1', + timeout: 60000, + retries: 3, + debug: false, + optimizationLevel: OptimizationLevel.Size, + useWasmOpt: true, + validate: true, + extractMetadata: true, + generateAbi: true, +}; + +/* ============================================================================ + * Utility Types + * ============================================================================ */ + +/** Get type name from TypeInfo */ +export function getTypeName(type: TypeInfo): string { + switch (type.kind) { + case 'u8': + case 'u16': + case 'u32': + case 'u64': + case 'u128': + case 'i8': + case 'i16': + case 'i32': + case 'i64': + case 'i128': + case 'bool': + case 'string': + case 'bytes': + case 'address': + case 'hash256': + return type.kind; + case 'fixedBytes': + return `bytes${type.size}`; + case 'array': + return `${getTypeName(type.element)}[]`; + case 'fixedArray': + return `${getTypeName(type.element)}[${type.size}]`; + case 'option': + return `${getTypeName(type.inner)}?`; + case 'tuple': + return `(${type.elements.map(getTypeName).join(', ')})`; + case 'struct': + return type.name; + case 'unknown': + return type.name; + } +} diff --git a/sdk/kotlin/src/main/kotlin/io/synor/compiler/SynorCompiler.kt b/sdk/kotlin/src/main/kotlin/io/synor/compiler/SynorCompiler.kt new file mode 100644 index 0000000..ac64ef3 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/io/synor/compiler/SynorCompiler.kt @@ -0,0 +1,500 @@ +/** + * Synor Compiler SDK for Kotlin + * + * Smart contract compilation, optimization, and ABI generation. + */ +package io.synor.compiler + +import kotlinx.coroutines.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.time.Duration +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +// ============================================================================ +// Enumerations +// ============================================================================ + +@Serializable +enum class OptimizationLevel { + @SerialName("none") NONE, + @SerialName("basic") BASIC, + @SerialName("size") SIZE, + @SerialName("aggressive") AGGRESSIVE; + + fun toApiString(): String = when (this) { + NONE -> "none" + BASIC -> "basic" + SIZE -> "size" + AGGRESSIVE -> "aggressive" + } + + companion object { + fun fromApiString(value: String): OptimizationLevel = when (value) { + "none" -> NONE + "basic" -> BASIC + "size" -> SIZE + "aggressive" -> AGGRESSIVE + else -> SIZE + } + } +} + +// ============================================================================ +// Configuration +// ============================================================================ + +@Serializable +data class StripOptions( + val stripDebug: Boolean = true, + val stripProducers: Boolean = true, + val stripNames: Boolean = true, + val stripCustom: Boolean = true, + val preserveSections: List = emptyList(), + val stripUnused: Boolean = true +) + +data class CompilerConfig( + val apiKey: String, + val endpoint: String = "https://compiler.synor.io/v1", + val timeout: Long = 60000, + val retries: Int = 3, + val debug: Boolean = false, + val optimizationLevel: OptimizationLevel = OptimizationLevel.SIZE, + val stripOptions: StripOptions? = null, + val maxContractSize: Int = 256 * 1024, + val useWasmOpt: Boolean = true, + val validate: Boolean = true, + val extractMetadata: Boolean = true, + val generateAbi: Boolean = true +) + +// ============================================================================ +// Types +// ============================================================================ + +@Serializable +data class ValidationError( + val code: String, + val message: String, + val location: String? = null +) + +@Serializable +data class ValidationResult( + val valid: Boolean, + val errors: List = emptyList(), + val warnings: List = emptyList(), + val exportCount: Int = 0, + val importCount: Int = 0, + val functionCount: Int = 0, + val memoryPages: List = listOf(0, null) +) + +@Serializable +data class ContractMetadata( + val name: String? = null, + val version: String? = null, + val authors: List = emptyList(), + val description: String? = null, + val license: String? = null, + val repository: String? = null, + val buildTimestamp: Long? = null, + val rustVersion: String? = null, + val sdkVersion: String? = null, + val custom: Map = emptyMap() +) + +@Serializable +data class TypeInfo( + val kind: String, + val size: Int? = null, + val element: TypeInfo? = null, + val inner: TypeInfo? = null, + val elements: List? = null, + val name: String? = null, + val fields: List? = null +) { + val typeName: String get() = when (kind) { + "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", + "bool", "string", "bytes", "address", "hash256" -> kind + "fixedBytes" -> "bytes${size ?: 0}" + "array" -> "${element?.typeName ?: ""}[]" + "fixedArray" -> "${element?.typeName ?: ""}[${size ?: 0}]" + "option" -> "${inner?.typeName ?: ""}?" + "tuple" -> "(${elements?.joinToString(", ") { it.typeName } ?: ""})" + "struct" -> name ?: "struct" + else -> name ?: "unknown" + } +} + +@Serializable +data class TypeField( + val name: String, + val type: TypeInfo +) + +@Serializable +data class ParamAbi( + val name: String, + val type: TypeInfo, + val indexed: Boolean = false +) + +@Serializable +data class FunctionAbi( + val name: String, + val selector: String, + val inputs: List = emptyList(), + val outputs: List = emptyList(), + val view: Boolean = false, + val payable: Boolean = false, + val doc: String? = null +) + +@Serializable +data class EventAbi( + val name: String, + val topic: String, + val params: List = emptyList(), + val doc: String? = null +) + +@Serializable +data class ErrorAbi( + val name: String, + val selector: String, + val params: List = emptyList(), + val doc: String? = null +) + +@Serializable +data class ContractAbi( + val name: String, + val version: String, + val functions: List = emptyList(), + val events: List = emptyList(), + val errors: List = emptyList() +) { + fun findFunction(name: String): FunctionAbi? = functions.find { it.name == name } + fun findBySelector(selector: String): FunctionAbi? = functions.find { it.selector == selector } +} + +@Serializable +data class CompilationResult( + val contractId: String, + val code: String, + val codeHash: String, + val originalSize: Int, + val optimizedSize: Int, + val sizeReduction: Double, + val metadata: ContractMetadata? = null, + val abi: ContractAbi? = null, + val estimatedDeployGas: Long = 0, + val validation: ValidationResult? = null, + val warnings: List = emptyList() +) { + val sizeStats: String get() = + "Original: $originalSize bytes, Optimized: $optimizedSize bytes, Reduction: ${"%.1f".format(sizeReduction)}%" + + fun decodeCode(): ByteArray = Base64.getDecoder().decode(code) +} + +@Serializable +data class SizeBreakdown( + val code: Int = 0, + val data: Int = 0, + val types: Int = 0, + val functions: Int = 0, + val memory: Int = 0, + val table: Int = 0, + val exports: Int = 0, + val imports: Int = 0, + val custom: Int = 0, + val total: Int = 0 +) + +@Serializable +data class FunctionAnalysis( + val name: String, + val size: Int, + val instructionCount: Int, + val localCount: Int, + val exported: Boolean, + val estimatedGas: Long +) + +@Serializable +data class ImportAnalysis( + val module: String, + val name: String, + val kind: String, + val signature: String? = null +) + +@Serializable +data class SecurityIssue( + val severity: String, + val type: String, + val description: String, + val location: String? = null +) + +@Serializable +data class SecurityAnalysis( + val score: Int, + val issues: List = emptyList(), + val recommendations: List = emptyList() +) + +@Serializable +data class GasAnalysis( + val deploymentGas: Long, + val functionGas: Map = emptyMap(), + val memoryInitGas: Long = 0, + val dataSectionGas: Long = 0 +) + +@Serializable +data class ContractAnalysis( + val sizeBreakdown: SizeBreakdown, + val functions: List = emptyList(), + val imports: List = emptyList(), + val security: SecurityAnalysis? = null, + val gasAnalysis: GasAnalysis? = null +) + +class CompilerException( + message: String, + val code: String? = null, + val httpStatus: Int? = null +) : RuntimeException(message) + +// ============================================================================ +// Client +// ============================================================================ + +class SynorCompiler(private val config: CompilerConfig) : AutoCloseable { + private val httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(config.timeout)) + .build() + private val json = Json { ignoreUnknownKeys = true; isLenient = true } + private val closed = AtomicBoolean(false) + + val contracts = ContractsClient(this) + val abi = AbiClient(this) + val analysis = AnalysisClient(this) + val validation = ValidationClient(this) + + val defaultOptimizationLevel: OptimizationLevel get() = config.optimizationLevel + + suspend fun healthCheck(): Boolean = try { + val result = get>("/health") + result["status"] == "healthy" + } catch (e: Exception) { + false + } + + suspend fun getInfo(): Map = get("/info") + + override fun close() { + closed.set(true) + } + + val isClosed: Boolean get() = closed.get() + + suspend fun compile( + wasm: ByteArray, + optimizationLevel: OptimizationLevel? = null, + stripOptions: StripOptions? = null, + useWasmOpt: Boolean? = null, + validate: Boolean? = null, + extractMetadata: Boolean? = null, + generateAbi: Boolean? = null + ): CompilationResult { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + val body = buildJsonObject { + put("wasm", wasmBase64) + put("optimization_level", (optimizationLevel ?: config.optimizationLevel).toApiString()) + put("use_wasm_opt", useWasmOpt ?: config.useWasmOpt) + put("validate", validate ?: config.validate) + put("extract_metadata", extractMetadata ?: config.extractMetadata) + put("generate_abi", generateAbi ?: config.generateAbi) + stripOptions?.let { + put("strip_options", json.encodeToJsonElement(it)) + } + } + return post("/compile", body) + } + + internal suspend inline fun get(path: String): T { + checkClosed() + val request = HttpRequest.newBuilder() + .uri(URI.create("${config.endpoint}$path")) + .header("Authorization", "Bearer ${config.apiKey}") + .header("Content-Type", "application/json") + .header("X-SDK-Version", "kotlin/0.1.0") + .GET() + .timeout(Duration.ofMillis(config.timeout)) + .build() + + return withContext(Dispatchers.IO) { + val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()) + handleResponse(response) + } + } + + internal suspend inline fun post(path: String, body: JsonElement): T { + checkClosed() + val request = HttpRequest.newBuilder() + .uri(URI.create("${config.endpoint}$path")) + .header("Authorization", "Bearer ${config.apiKey}") + .header("Content-Type", "application/json") + .header("X-SDK-Version", "kotlin/0.1.0") + .POST(HttpRequest.BodyPublishers.ofString(body.toString())) + .timeout(Duration.ofMillis(config.timeout)) + .build() + + return withContext(Dispatchers.IO) { + val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()) + handleResponse(response) + } + } + + private inline fun handleResponse(response: HttpResponse): T { + if (response.statusCode() >= 400) { + val error = try { + json.decodeFromString>(response.body()) + } catch (e: Exception) { + mapOf("message" to if (response.body().isEmpty()) "HTTP ${response.statusCode()}" else response.body()) + } + throw CompilerException( + error["message"] ?: "HTTP ${response.statusCode()}", + error["code"], + response.statusCode() + ) + } + return json.decodeFromString(response.body()) + } + + private fun checkClosed() { + if (closed.get()) { + throw CompilerException("Client has been closed", "CLIENT_CLOSED") + } + } +} + +class ContractsClient(private val compiler: SynorCompiler) { + suspend fun compile(wasm: ByteArray, vararg opts: Pair): CompilationResult = + compiler.compile(wasm) + + suspend fun compileDev(wasm: ByteArray): CompilationResult = + compiler.compile(wasm, OptimizationLevel.NONE, null, false, true) + + suspend fun compileProduction(wasm: ByteArray): CompilationResult = + compiler.compile(wasm, OptimizationLevel.AGGRESSIVE, null, true, true, true, true) + + suspend fun get(contractId: String): CompilationResult = + compiler.get("/contracts/$contractId") + + suspend fun list(limit: Int? = null, offset: Int? = null): List { + val params = mutableListOf() + limit?.let { params.add("limit=$it") } + offset?.let { params.add("offset=$it") } + val query = if (params.isNotEmpty()) "?${params.joinToString("&")}" else "" + val result: Map> = compiler.get("/contracts$query") + return result["contracts"] ?: emptyList() + } + + suspend fun getCode(contractId: String): ByteArray { + val result: Map = compiler.get("/contracts/$contractId/code") + return Base64.getDecoder().decode(result["code"] ?: "") + } +} + +class AbiClient(private val compiler: SynorCompiler) { + suspend fun extract(wasm: ByteArray): ContractAbi { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + return compiler.post("/abi/extract", buildJsonObject { put("wasm", wasmBase64) }) + } + + suspend fun get(contractId: String): ContractAbi = + compiler.get("/contracts/$contractId/abi") + + suspend fun encodeCall(functionAbi: FunctionAbi, args: List): String { + val body = buildJsonObject { + put("function", Json.encodeToJsonElement(functionAbi)) + put("args", Json.encodeToJsonElement(args)) + } + val result: Map = compiler.post("/abi/encode", body) + return result["data"] ?: "" + } +} + +class AnalysisClient(private val compiler: SynorCompiler) { + suspend fun analyze(wasm: ByteArray): ContractAnalysis { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + return compiler.post("/analysis", buildJsonObject { put("wasm", wasmBase64) }) + } + + suspend fun get(contractId: String): ContractAnalysis = + compiler.get("/contracts/$contractId/analysis") + + suspend fun extractMetadata(wasm: ByteArray): ContractMetadata { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + return compiler.post("/analysis/metadata", buildJsonObject { put("wasm", wasmBase64) }) + } + + suspend fun estimateDeployGas(wasm: ByteArray): Long { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + val result: Map = compiler.post("/analysis/estimate-gas", buildJsonObject { put("wasm", wasmBase64) }) + return result["gas"] ?: 0 + } + + suspend fun securityScan(wasm: ByteArray): SecurityAnalysis { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + val result: Map = compiler.post("/analysis/security", buildJsonObject { put("wasm", wasmBase64) }) + return result["security"] ?: SecurityAnalysis(0) + } +} + +class ValidationClient(private val compiler: SynorCompiler) { + suspend fun validate(wasm: ByteArray): ValidationResult { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + return compiler.post("/validate", buildJsonObject { put("wasm", wasmBase64) }) + } + + suspend fun isValid(wasm: ByteArray): Boolean = validate(wasm).valid + + suspend fun getErrors(wasm: ByteArray): List = + validate(wasm).errors.map { it.message } + + suspend fun validateExports(wasm: ByteArray, requiredExports: List): Boolean { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + val body = buildJsonObject { + put("wasm", wasmBase64) + put("required_exports", Json.encodeToJsonElement(requiredExports)) + } + val result: Map = compiler.post("/validate/exports", body) + return result["valid"] ?: false + } + + suspend fun validateMemory(wasm: ByteArray, maxPages: Int): Boolean { + val wasmBase64 = Base64.getEncoder().encodeToString(wasm) + val body = buildJsonObject { + put("wasm", wasmBase64) + put("max_pages", maxPages) + } + val result: Map = compiler.post("/validate/memory", body) + return result["valid"] ?: false + } +} + +// Constants +const val MAX_CONTRACT_SIZE = 256 * 1024 +const val MAX_MEMORY_PAGES = 16 diff --git a/sdk/python/src/synor_compiler/__init__.py b/sdk/python/src/synor_compiler/__init__.py new file mode 100644 index 0000000..cd040c8 --- /dev/null +++ b/sdk/python/src/synor_compiler/__init__.py @@ -0,0 +1,70 @@ +""" +Synor Compiler SDK for Python + +Smart contract compilation, optimization, and ABI generation. +""" + +from .types import ( + OptimizationLevel, + StripOptions, + CompilerConfig, + CompilationRequest, + CompilationResult, + ValidationResult, + ValidationError, + ContractMetadata, + ContractAbi, + FunctionAbi, + EventAbi, + ErrorAbi, + ParamAbi, + TypeInfo, + SourceMap, + SourceMapping, + ContractAnalysis, + SizeBreakdown, + FunctionAnalysis, + ImportAnalysis, + SecurityAnalysis, + SecurityIssue, + GasAnalysis, + CompilerError, + DEFAULT_STRIP_OPTIONS, + DEFAULT_CONFIG, + MAX_CONTRACT_SIZE, + MAX_MEMORY_PAGES, +) +from .client import SynorCompiler + +__version__ = "0.1.0" +__all__ = [ + "SynorCompiler", + "OptimizationLevel", + "StripOptions", + "CompilerConfig", + "CompilationRequest", + "CompilationResult", + "ValidationResult", + "ValidationError", + "ContractMetadata", + "ContractAbi", + "FunctionAbi", + "EventAbi", + "ErrorAbi", + "ParamAbi", + "TypeInfo", + "SourceMap", + "SourceMapping", + "ContractAnalysis", + "SizeBreakdown", + "FunctionAnalysis", + "ImportAnalysis", + "SecurityAnalysis", + "SecurityIssue", + "GasAnalysis", + "CompilerError", + "DEFAULT_STRIP_OPTIONS", + "DEFAULT_CONFIG", + "MAX_CONTRACT_SIZE", + "MAX_MEMORY_PAGES", +] diff --git a/sdk/python/src/synor_compiler/client.py b/sdk/python/src/synor_compiler/client.py new file mode 100644 index 0000000..84bb065 --- /dev/null +++ b/sdk/python/src/synor_compiler/client.py @@ -0,0 +1,378 @@ +""" +Synor Compiler SDK Client + +Smart contract compilation, optimization, and ABI generation. +""" + +import base64 +from typing import Any, Dict, List, Optional, Union + +import httpx + +from .types import ( + OptimizationLevel, + StripOptions, + CompilerConfig, + CompilationResult, + ValidationResult, + ContractMetadata, + ContractAbi, + FunctionAbi, + SourceMap, + ContractAnalysis, + SecurityAnalysis, + CompilerError, + DEFAULT_STRIP_OPTIONS, +) + + +class SynorCompiler: + """ + Synor Compiler SDK Client + + Smart contract compilation, optimization, and ABI generation. + + Example: + >>> compiler = SynorCompiler(CompilerConfig(api_key="your-api-key")) + >>> result = await compiler.compile(wasm_bytes) + >>> print(f"Optimized size: {result.optimized_size} bytes") + """ + + def __init__(self, config: CompilerConfig): + self._config = config + self._closed = False + + self._client = httpx.AsyncClient( + base_url=config.endpoint, + timeout=config.timeout / 1000, # Convert ms to seconds + headers={ + "Authorization": f"Bearer {config.api_key}", + "Content-Type": "application/json", + "X-SDK-Version": "python/0.1.0", + }, + ) + + # Sub-clients + self.contracts = ContractsClient(self) + self.abi = AbiClient(self) + self.analysis = AnalysisClient(self) + self.validation = ValidationClient(self) + + @property + def default_optimization_level(self) -> OptimizationLevel: + """Get the default optimization level.""" + return self._config.optimization_level + + async def health_check(self) -> bool: + """Check service health.""" + try: + result = await self._get("/health") + return result.get("status") == "healthy" + except Exception: + return False + + async def get_info(self) -> Dict[str, Any]: + """Get service info.""" + return await self._get("/info") + + async def close(self) -> None: + """Close the client.""" + self._closed = True + await self._client.aclose() + + @property + def is_closed(self) -> bool: + """Check if client is closed.""" + return self._closed + + async def compile( + self, + wasm: Union[bytes, str], + optimization_level: Optional[OptimizationLevel] = None, + strip_options: Optional[StripOptions] = None, + use_wasm_opt: Optional[bool] = None, + validate: Optional[bool] = None, + extract_metadata: Optional[bool] = None, + generate_abi: Optional[bool] = None, + ) -> CompilationResult: + """ + Compile WASM bytecode. + + Args: + wasm: WASM bytecode as bytes or base64 string + optimization_level: Optimization level + strip_options: Strip options + use_wasm_opt: Whether to use wasm-opt + validate: Whether to validate + extract_metadata: Whether to extract metadata + generate_abi: Whether to generate ABI + + Returns: + Compilation result + """ + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + + body = { + "wasm": wasm_base64, + "optimization_level": (optimization_level or self._config.optimization_level).value, + "strip_options": (strip_options or self._config.strip_options or DEFAULT_STRIP_OPTIONS).to_dict(), + "use_wasm_opt": use_wasm_opt if use_wasm_opt is not None else self._config.use_wasm_opt, + "validate": validate if validate is not None else self._config.validate, + "extract_metadata": extract_metadata if extract_metadata is not None else self._config.extract_metadata, + "generate_abi": generate_abi if generate_abi is not None else self._config.generate_abi, + } + + result = await self._post("/compile", body) + return CompilationResult.from_dict(result) + + async def _get(self, path: str) -> Dict[str, Any]: + """Internal GET request.""" + self._check_closed() + response = await self._client.get(path) + return self._handle_response(response) + + async def _post(self, path: str, body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """Internal POST request.""" + self._check_closed() + response = await self._client.post(path, json=body or {}) + return self._handle_response(response) + + def _handle_response(self, response: httpx.Response) -> Dict[str, Any]: + """Handle HTTP response.""" + if not response.is_success: + try: + error = response.json() + except Exception: + error = {"message": response.text or f"HTTP {response.status_code}"} + raise CompilerError( + message=error.get("message", f"HTTP {response.status_code}"), + code=error.get("code"), + http_status=response.status_code, + ) + return response.json() + + def _check_closed(self) -> None: + """Check if client is closed.""" + if self._closed: + raise CompilerError("Client has been closed", code="CLIENT_CLOSED") + + async def __aenter__(self) -> "SynorCompiler": + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + await self.close() + + +class ContractsClient: + """Contracts sub-client for contract compilation.""" + + def __init__(self, compiler: SynorCompiler): + self._compiler = compiler + + async def compile( + self, + wasm: Union[bytes, str], + **kwargs, + ) -> CompilationResult: + """Compile WASM bytecode with default settings.""" + return await self._compiler.compile(wasm, **kwargs) + + async def compile_dev(self, wasm: Union[bytes, str]) -> CompilationResult: + """Compile with development settings (fast, minimal optimization).""" + return await self._compiler.compile( + wasm, + optimization_level=OptimizationLevel.NONE, + use_wasm_opt=False, + validate=True, + ) + + async def compile_production(self, wasm: Union[bytes, str]) -> CompilationResult: + """Compile with production settings (aggressive optimization).""" + return await self._compiler.compile( + wasm, + optimization_level=OptimizationLevel.AGGRESSIVE, + use_wasm_opt=True, + validate=True, + extract_metadata=True, + generate_abi=True, + ) + + async def get(self, contract_id: str) -> CompilationResult: + """Get a compiled contract by ID.""" + result = await self._compiler._get(f"/contracts/{contract_id}") + return CompilationResult.from_dict(result) + + async def list( + self, + limit: Optional[int] = None, + offset: Optional[int] = None, + ) -> List[CompilationResult]: + """List compiled contracts.""" + params = [] + if limit is not None: + params.append(f"limit={limit}") + if offset is not None: + params.append(f"offset={offset}") + query = "?" + "&".join(params) if params else "" + + result = await self._compiler._get(f"/contracts{query}") + return [CompilationResult.from_dict(c) for c in result.get("contracts", [])] + + async def get_code(self, contract_id: str) -> bytes: + """Get optimized bytecode for a contract.""" + result = await self._compiler._get(f"/contracts/{contract_id}/code") + return base64.b64decode(result.get("code", "")) + + +class AbiClient: + """ABI sub-client for ABI generation and encoding.""" + + def __init__(self, compiler: SynorCompiler): + self._compiler = compiler + + async def extract(self, wasm: Union[bytes, str]) -> ContractAbi: + """Extract ABI from WASM bytecode.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/abi/extract", {"wasm": wasm_base64}) + return ContractAbi.from_dict(result) + + async def get(self, contract_id: str) -> ContractAbi: + """Get ABI for a compiled contract.""" + result = await self._compiler._get(f"/contracts/{contract_id}/abi") + return ContractAbi.from_dict(result) + + async def encode_call(self, function_abi: FunctionAbi, args: List[Any]) -> str: + """Encode function call.""" + result = await self._compiler._post("/abi/encode", { + "function": { + "name": function_abi.name, + "selector": function_abi.selector, + "inputs": [{"name": i.name, "type": {"kind": i.type.kind}} for i in function_abi.inputs], + "outputs": [{"name": o.name, "type": {"kind": o.type.kind}} for o in function_abi.outputs], + }, + "args": args, + }) + return result.get("data", "") + + async def decode_result(self, function_abi: FunctionAbi, data: str) -> List[Any]: + """Decode function result.""" + result = await self._compiler._post("/abi/decode", { + "function": { + "name": function_abi.name, + "selector": function_abi.selector, + "inputs": [{"name": i.name, "type": {"kind": i.type.kind}} for i in function_abi.inputs], + "outputs": [{"name": o.name, "type": {"kind": o.type.kind}} for o in function_abi.outputs], + }, + "data": data, + }) + return result.get("values", []) + + def compute_selector(self, function_name: str) -> str: + """Compute function selector.""" + import hashlib + h = hashlib.blake2b(function_name.encode(), digest_size=4) + return h.hexdigest() + + async def generate_types(self, abi: ContractAbi, language: str = "python") -> str: + """Generate types from ABI.""" + result = await self._compiler._post("/abi/generate-types", { + "abi": { + "name": abi.name, + "version": abi.version, + "functions": [{"name": f.name, "selector": f.selector} for f in abi.functions], + }, + "language": language, + }) + return result.get(language, "") + + +class AnalysisClient: + """Analysis sub-client for contract analysis.""" + + def __init__(self, compiler: SynorCompiler): + self._compiler = compiler + + async def analyze(self, wasm: Union[bytes, str]) -> ContractAnalysis: + """Analyze WASM bytecode.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/analysis", {"wasm": wasm_base64}) + return ContractAnalysis.from_dict(result) + + async def get(self, contract_id: str) -> ContractAnalysis: + """Get analysis for a compiled contract.""" + result = await self._compiler._get(f"/contracts/{contract_id}/analysis") + return ContractAnalysis.from_dict(result) + + async def extract_metadata(self, wasm: Union[bytes, str]) -> ContractMetadata: + """Extract metadata from WASM bytecode.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/analysis/metadata", {"wasm": wasm_base64}) + return ContractMetadata.from_dict(result) + + async def extract_source_map(self, wasm: Union[bytes, str]) -> Optional[SourceMap]: + """Extract source map from WASM bytecode.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/analysis/source-map", {"wasm": wasm_base64}) + source_map = result.get("source_map") + return SourceMap.from_dict(source_map) if source_map else None + + async def estimate_deploy_gas(self, wasm: Union[bytes, str]) -> int: + """Estimate gas for contract deployment.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/analysis/estimate-gas", {"wasm": wasm_base64}) + return result.get("gas", 0) + + async def security_scan(self, wasm: Union[bytes, str]) -> SecurityAnalysis: + """Get security analysis.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/analysis/security", {"wasm": wasm_base64}) + return SecurityAnalysis.from_dict(result.get("security", {})) + + +class ValidationClient: + """Validation sub-client for WASM validation.""" + + def __init__(self, compiler: SynorCompiler): + self._compiler = compiler + + async def validate(self, wasm: Union[bytes, str]) -> ValidationResult: + """Validate WASM bytecode against VM requirements.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/validate", {"wasm": wasm_base64}) + return ValidationResult.from_dict(result) + + async def is_valid(self, wasm: Union[bytes, str]) -> bool: + """Check if WASM is valid.""" + result = await self.validate(wasm) + return result.valid + + async def get_errors(self, wasm: Union[bytes, str]) -> List[str]: + """Get validation errors.""" + result = await self.validate(wasm) + return [e.message for e in result.errors] + + async def validate_exports( + self, + wasm: Union[bytes, str], + required_exports: List[str], + ) -> bool: + """Validate contract exports.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/validate/exports", { + "wasm": wasm_base64, + "required_exports": required_exports, + }) + return result.get("valid", False) + + async def validate_memory( + self, + wasm: Union[bytes, str], + max_pages: int, + ) -> bool: + """Validate memory limits.""" + wasm_base64 = wasm if isinstance(wasm, str) else base64.b64encode(wasm).decode() + result = await self._compiler._post("/validate/memory", { + "wasm": wasm_base64, + "max_pages": max_pages, + }) + return result.get("valid", False) diff --git a/sdk/python/src/synor_compiler/types.py b/sdk/python/src/synor_compiler/types.py new file mode 100644 index 0000000..1955ea1 --- /dev/null +++ b/sdk/python/src/synor_compiler/types.py @@ -0,0 +1,588 @@ +""" +Synor Compiler SDK Types + +Smart contract compilation, optimization, and ABI generation. +""" + +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Dict, List, Optional, Tuple, Union + + +# ============================================================================ +# Enumerations +# ============================================================================ + +class OptimizationLevel(Enum): + """Optimization level for WASM compilation.""" + NONE = "none" + BASIC = "basic" + SIZE = "size" + AGGRESSIVE = "aggressive" + + +# ============================================================================ +# Configuration Types +# ============================================================================ + +@dataclass +class StripOptions: + """Options for stripping WASM sections.""" + strip_debug: bool = True + strip_producers: bool = True + strip_names: bool = True + strip_custom: bool = True + preserve_sections: List[str] = field(default_factory=list) + strip_unused: bool = True + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "StripOptions": + return StripOptions( + strip_debug=data.get("strip_debug", True), + strip_producers=data.get("strip_producers", True), + strip_names=data.get("strip_names", True), + strip_custom=data.get("strip_custom", True), + preserve_sections=data.get("preserve_sections", []), + strip_unused=data.get("strip_unused", True), + ) + + def to_dict(self) -> Dict[str, Any]: + return { + "strip_debug": self.strip_debug, + "strip_producers": self.strip_producers, + "strip_names": self.strip_names, + "strip_custom": self.strip_custom, + "preserve_sections": self.preserve_sections, + "strip_unused": self.strip_unused, + } + + +@dataclass +class CompilerConfig: + """Compiler configuration.""" + api_key: str + endpoint: str = "https://compiler.synor.io/v1" + timeout: int = 60000 + retries: int = 3 + debug: bool = False + optimization_level: OptimizationLevel = OptimizationLevel.SIZE + strip_options: Optional[StripOptions] = None + max_contract_size: int = 256 * 1024 + use_wasm_opt: bool = True + validate: bool = True + extract_metadata: bool = True + generate_abi: bool = True + + +# ============================================================================ +# Compilation Types +# ============================================================================ + +@dataclass +class CompilationRequest: + """Compilation request.""" + wasm: str # Base64 encoded + optimization_level: Optional[OptimizationLevel] = None + strip_options: Optional[StripOptions] = None + use_wasm_opt: Optional[bool] = None + validate: Optional[bool] = None + extract_metadata: Optional[bool] = None + generate_abi: Optional[bool] = None + + def to_dict(self) -> Dict[str, Any]: + result: Dict[str, Any] = {"wasm": self.wasm} + if self.optimization_level: + result["optimization_level"] = self.optimization_level.value + if self.strip_options: + result["strip_options"] = self.strip_options.to_dict() + if self.use_wasm_opt is not None: + result["use_wasm_opt"] = self.use_wasm_opt + if self.validate is not None: + result["validate"] = self.validate + if self.extract_metadata is not None: + result["extract_metadata"] = self.extract_metadata + if self.generate_abi is not None: + result["generate_abi"] = self.generate_abi + return result + + +@dataclass +class ValidationError: + """Validation error.""" + code: str + message: str + location: Optional[str] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ValidationError": + return ValidationError( + code=data.get("code", ""), + message=data.get("message", ""), + location=data.get("location"), + ) + + +@dataclass +class ValidationResult: + """Validation result.""" + valid: bool + errors: List[ValidationError] = field(default_factory=list) + warnings: List[str] = field(default_factory=list) + export_count: int = 0 + import_count: int = 0 + function_count: int = 0 + memory_pages: Tuple[int, Optional[int]] = (0, None) + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ValidationResult": + return ValidationResult( + valid=data.get("valid", False), + errors=[ValidationError.from_dict(e) for e in data.get("errors", [])], + warnings=data.get("warnings", []), + export_count=data.get("export_count", 0), + import_count=data.get("import_count", 0), + function_count=data.get("function_count", 0), + memory_pages=tuple(data.get("memory_pages", [0, None])), + ) + + +@dataclass +class ContractMetadata: + """Contract metadata.""" + name: Optional[str] = None + version: Optional[str] = None + authors: List[str] = field(default_factory=list) + description: Optional[str] = None + license: Optional[str] = None + repository: Optional[str] = None + build_timestamp: Optional[int] = None + rust_version: Optional[str] = None + sdk_version: Optional[str] = None + custom: Dict[str, str] = field(default_factory=dict) + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ContractMetadata": + return ContractMetadata( + name=data.get("name"), + version=data.get("version"), + authors=data.get("authors", []), + description=data.get("description"), + license=data.get("license"), + repository=data.get("repository"), + build_timestamp=data.get("build_timestamp"), + rust_version=data.get("rust_version"), + sdk_version=data.get("sdk_version"), + custom=data.get("custom", {}), + ) + + +# ============================================================================ +# ABI Types +# ============================================================================ + +@dataclass +class TypeInfo: + """Type information.""" + kind: str + size: Optional[int] = None + element: Optional["TypeInfo"] = None + inner: Optional["TypeInfo"] = None + elements: Optional[List["TypeInfo"]] = None + name: Optional[str] = None + fields: Optional[List[Tuple[str, "TypeInfo"]]] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "TypeInfo": + kind = data.get("kind", "unknown") + return TypeInfo( + kind=kind, + size=data.get("size"), + element=TypeInfo.from_dict(data["element"]) if "element" in data else None, + inner=TypeInfo.from_dict(data["inner"]) if "inner" in data else None, + elements=[TypeInfo.from_dict(e) for e in data.get("elements", [])] if "elements" in data else None, + name=data.get("name"), + fields=[(f[0], TypeInfo.from_dict(f[1])) for f in data.get("fields", [])] if "fields" in data else None, + ) + + def type_name(self) -> str: + """Get the type name as a string.""" + if self.kind in ("u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", + "bool", "string", "bytes", "address", "hash256"): + return self.kind + if self.kind == "fixedBytes": + return f"bytes{self.size}" + if self.kind == "array" and self.element: + return f"{self.element.type_name()}[]" + if self.kind == "fixedArray" and self.element: + return f"{self.element.type_name()}[{self.size}]" + if self.kind == "option" and self.inner: + return f"{self.inner.type_name()}?" + if self.kind == "tuple" and self.elements: + return f"({', '.join(e.type_name() for e in self.elements)})" + if self.kind == "struct" and self.name: + return self.name + return self.name or "unknown" + + +@dataclass +class ParamAbi: + """Parameter ABI.""" + name: str + type: TypeInfo + indexed: bool = False + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ParamAbi": + return ParamAbi( + name=data.get("name", ""), + type=TypeInfo.from_dict(data.get("type", {"kind": "unknown"})), + indexed=data.get("indexed", False), + ) + + +@dataclass +class FunctionAbi: + """Function ABI.""" + name: str + selector: str + inputs: List[ParamAbi] = field(default_factory=list) + outputs: List[ParamAbi] = field(default_factory=list) + view: bool = False + payable: bool = False + doc: Optional[str] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "FunctionAbi": + return FunctionAbi( + name=data.get("name", ""), + selector=data.get("selector", ""), + inputs=[ParamAbi.from_dict(i) for i in data.get("inputs", [])], + outputs=[ParamAbi.from_dict(o) for o in data.get("outputs", [])], + view=data.get("view", False), + payable=data.get("payable", False), + doc=data.get("doc"), + ) + + +@dataclass +class EventAbi: + """Event ABI.""" + name: str + topic: str + params: List[ParamAbi] = field(default_factory=list) + doc: Optional[str] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "EventAbi": + return EventAbi( + name=data.get("name", ""), + topic=data.get("topic", ""), + params=[ParamAbi.from_dict(p) for p in data.get("params", [])], + doc=data.get("doc"), + ) + + +@dataclass +class ErrorAbi: + """Error ABI.""" + name: str + selector: str + params: List[ParamAbi] = field(default_factory=list) + doc: Optional[str] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ErrorAbi": + return ErrorAbi( + name=data.get("name", ""), + selector=data.get("selector", ""), + params=[ParamAbi.from_dict(p) for p in data.get("params", [])], + doc=data.get("doc"), + ) + + +@dataclass +class ContractAbi: + """Contract ABI (Application Binary Interface).""" + name: str + version: str + functions: List[FunctionAbi] = field(default_factory=list) + events: List[EventAbi] = field(default_factory=list) + errors: List[ErrorAbi] = field(default_factory=list) + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ContractAbi": + return ContractAbi( + name=data.get("name", ""), + version=data.get("version", "1.0.0"), + functions=[FunctionAbi.from_dict(f) for f in data.get("functions", [])], + events=[EventAbi.from_dict(e) for e in data.get("events", [])], + errors=[ErrorAbi.from_dict(e) for e in data.get("errors", [])], + ) + + def find_function(self, name: str) -> Optional[FunctionAbi]: + """Find a function by name.""" + return next((f for f in self.functions if f.name == name), None) + + def find_by_selector(self, selector: str) -> Optional[FunctionAbi]: + """Find a function by selector.""" + return next((f for f in self.functions if f.selector == selector), None) + + +@dataclass +class CompilationResult: + """Compilation result.""" + contract_id: str + code: str # Base64 encoded + code_hash: str + original_size: int + optimized_size: int + size_reduction: float + metadata: Optional[ContractMetadata] = None + abi: Optional[ContractAbi] = None + estimated_deploy_gas: int = 0 + validation: Optional[ValidationResult] = None + warnings: List[str] = field(default_factory=list) + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "CompilationResult": + return CompilationResult( + contract_id=data.get("contract_id", ""), + code=data.get("code", ""), + code_hash=data.get("code_hash", ""), + original_size=data.get("original_size", 0), + optimized_size=data.get("optimized_size", 0), + size_reduction=data.get("size_reduction", 0.0), + metadata=ContractMetadata.from_dict(data["metadata"]) if "metadata" in data and data["metadata"] else None, + abi=ContractAbi.from_dict(data["abi"]) if "abi" in data and data["abi"] else None, + estimated_deploy_gas=data.get("estimated_deploy_gas", 0), + validation=ValidationResult.from_dict(data["validation"]) if "validation" in data and data["validation"] else None, + warnings=data.get("warnings", []), + ) + + def size_stats(self) -> str: + """Returns size statistics as a formatted string.""" + return f"Original: {self.original_size} bytes, Optimized: {self.optimized_size} bytes, Reduction: {self.size_reduction:.1f}%" + + +# ============================================================================ +# Source Map Types +# ============================================================================ + +@dataclass +class SourceMapping: + """Source mapping entry.""" + wasm_offset: int + line: int + column: int + function: Optional[str] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "SourceMapping": + return SourceMapping( + wasm_offset=data.get("wasm_offset", 0), + line=data.get("line", 0), + column=data.get("column", 0), + function=data.get("function"), + ) + + +@dataclass +class SourceMap: + """Source map for debugging.""" + file: str + mappings: List[SourceMapping] = field(default_factory=list) + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "SourceMap": + return SourceMap( + file=data.get("file", ""), + mappings=[SourceMapping.from_dict(m) for m in data.get("mappings", [])], + ) + + +# ============================================================================ +# Analysis Types +# ============================================================================ + +@dataclass +class SizeBreakdown: + """Size breakdown.""" + code: int = 0 + data: int = 0 + types: int = 0 + functions: int = 0 + memory: int = 0 + table: int = 0 + exports: int = 0 + imports: int = 0 + custom: int = 0 + total: int = 0 + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "SizeBreakdown": + return SizeBreakdown( + code=data.get("code", 0), + data=data.get("data", 0), + types=data.get("types", 0), + functions=data.get("functions", 0), + memory=data.get("memory", 0), + table=data.get("table", 0), + exports=data.get("exports", 0), + imports=data.get("imports", 0), + custom=data.get("custom", 0), + total=data.get("total", 0), + ) + + +@dataclass +class FunctionAnalysis: + """Function analysis.""" + name: str + size: int + instruction_count: int + local_count: int + exported: bool + estimated_gas: int + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "FunctionAnalysis": + return FunctionAnalysis( + name=data.get("name", ""), + size=data.get("size", 0), + instruction_count=data.get("instruction_count", 0), + local_count=data.get("local_count", 0), + exported=data.get("exported", False), + estimated_gas=data.get("estimated_gas", 0), + ) + + +@dataclass +class ImportAnalysis: + """Import analysis.""" + module: str + name: str + kind: str # function, memory, table, global + signature: Optional[str] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ImportAnalysis": + return ImportAnalysis( + module=data.get("module", ""), + name=data.get("name", ""), + kind=data.get("kind", "function"), + signature=data.get("signature"), + ) + + +@dataclass +class SecurityIssue: + """Security issue.""" + severity: str # low, medium, high, critical + type: str + description: str + location: Optional[str] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "SecurityIssue": + return SecurityIssue( + severity=data.get("severity", "low"), + type=data.get("type", ""), + description=data.get("description", ""), + location=data.get("location"), + ) + + +@dataclass +class SecurityAnalysis: + """Security analysis.""" + score: int # 0-100 + issues: List[SecurityIssue] = field(default_factory=list) + recommendations: List[str] = field(default_factory=list) + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "SecurityAnalysis": + return SecurityAnalysis( + score=data.get("score", 0), + issues=[SecurityIssue.from_dict(i) for i in data.get("issues", [])], + recommendations=data.get("recommendations", []), + ) + + +@dataclass +class GasAnalysis: + """Gas analysis.""" + deployment_gas: int + function_gas: Dict[str, int] = field(default_factory=dict) + memory_init_gas: int = 0 + data_section_gas: int = 0 + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "GasAnalysis": + return GasAnalysis( + deployment_gas=data.get("deployment_gas", 0), + function_gas=data.get("function_gas", {}), + memory_init_gas=data.get("memory_init_gas", 0), + data_section_gas=data.get("data_section_gas", 0), + ) + + +@dataclass +class ContractAnalysis: + """Contract analysis result.""" + size_breakdown: SizeBreakdown + functions: List[FunctionAnalysis] = field(default_factory=list) + imports: List[ImportAnalysis] = field(default_factory=list) + security: Optional[SecurityAnalysis] = None + gas_analysis: Optional[GasAnalysis] = None + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "ContractAnalysis": + return ContractAnalysis( + size_breakdown=SizeBreakdown.from_dict(data.get("size_breakdown", {})), + functions=[FunctionAnalysis.from_dict(f) for f in data.get("functions", [])], + imports=[ImportAnalysis.from_dict(i) for i in data.get("imports", [])], + security=SecurityAnalysis.from_dict(data["security"]) if "security" in data and data["security"] else None, + gas_analysis=GasAnalysis.from_dict(data["gas_analysis"]) if "gas_analysis" in data and data["gas_analysis"] else None, + ) + + +# ============================================================================ +# Error Types +# ============================================================================ + +class CompilerError(Exception): + """Compiler error.""" + + def __init__( + self, + message: str, + code: Optional[str] = None, + http_status: Optional[int] = None, + details: Optional[Dict[str, Any]] = None, + ): + super().__init__(message) + self.code = code + self.http_status = http_status + self.details = details or {} + + +# ============================================================================ +# Constants +# ============================================================================ + +MAX_CONTRACT_SIZE = 256 * 1024 # 256 KB +MAX_MEMORY_PAGES = 16 + +DEFAULT_STRIP_OPTIONS = StripOptions() + +DEFAULT_CONFIG: Dict[str, Any] = { + "endpoint": "https://compiler.synor.io/v1", + "timeout": 60000, + "retries": 3, + "debug": False, + "optimization_level": OptimizationLevel.SIZE, + "use_wasm_opt": True, + "validate": True, + "extract_metadata": True, + "generate_abi": True, +} diff --git a/sdk/ruby/lib/synor/compiler.rb b/sdk/ruby/lib/synor/compiler.rb new file mode 100644 index 0000000..f6cf69d --- /dev/null +++ b/sdk/ruby/lib/synor/compiler.rb @@ -0,0 +1,643 @@ +# frozen_string_literal: true + +require 'net/http' +require 'json' +require 'uri' +require 'base64' + +module Synor + module Compiler + # Constants + MAX_CONTRACT_SIZE = 256 * 1024 + MAX_MEMORY_PAGES = 16 + + # Optimization levels + module OptimizationLevel + NONE = 'none' + BASIC = 'basic' + SIZE = 'size' + AGGRESSIVE = 'aggressive' + end + + # Strip options for WASM sections + class StripOptions + attr_accessor :strip_debug, :strip_producers, :strip_names, + :strip_custom, :preserve_sections, :strip_unused + + def initialize( + strip_debug: true, + strip_producers: true, + strip_names: true, + strip_custom: true, + preserve_sections: [], + strip_unused: true + ) + @strip_debug = strip_debug + @strip_producers = strip_producers + @strip_names = strip_names + @strip_custom = strip_custom + @preserve_sections = preserve_sections + @strip_unused = strip_unused + end + + def to_h + { + strip_debug: @strip_debug, + strip_producers: @strip_producers, + strip_names: @strip_names, + strip_custom: @strip_custom, + preserve_sections: @preserve_sections, + strip_unused: @strip_unused + } + end + end + + # Compiler configuration + class Config + attr_accessor :api_key, :endpoint, :timeout, :retries, :debug, + :optimization_level, :strip_options, :max_contract_size, + :use_wasm_opt, :validate, :extract_metadata, :generate_abi + + def initialize( + api_key:, + endpoint: 'https://compiler.synor.io/v1', + timeout: 60, + retries: 3, + debug: false, + optimization_level: OptimizationLevel::SIZE, + strip_options: nil, + max_contract_size: MAX_CONTRACT_SIZE, + use_wasm_opt: true, + validate: true, + extract_metadata: true, + generate_abi: true + ) + @api_key = api_key + @endpoint = endpoint + @timeout = timeout + @retries = retries + @debug = debug + @optimization_level = optimization_level + @strip_options = strip_options + @max_contract_size = max_contract_size + @use_wasm_opt = use_wasm_opt + @validate = validate + @extract_metadata = extract_metadata + @generate_abi = generate_abi + end + end + + # Compiler error + class CompilerError < StandardError + attr_reader :code, :http_status + + def initialize(message, code: nil, http_status: nil) + super(message) + @code = code + @http_status = http_status + end + end + + # Type information + class TypeInfo + attr_accessor :kind, :size, :element, :inner, :elements, :name, :fields + + def initialize(data) + @kind = data['kind'] || 'unknown' + @size = data['size'] + @element = data['element'] ? TypeInfo.new(data['element']) : nil + @inner = data['inner'] ? TypeInfo.new(data['inner']) : nil + @elements = data['elements']&.map { |e| TypeInfo.new(e) } + @name = data['name'] + @fields = data['fields'] + end + + def type_name + case @kind + when 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', + 'bool', 'string', 'bytes', 'address', 'hash256' + @kind + when 'fixedBytes' + "bytes#{@size || 0}" + when 'array' + "#{@element&.type_name}[]" + when 'fixedArray' + "#{@element&.type_name}[#{@size || 0}]" + when 'option' + "#{@inner&.type_name}?" + when 'tuple' + "(#{@elements&.map(&:type_name)&.join(', ')})" + when 'struct' + @name || 'struct' + else + @name || 'unknown' + end + end + end + + # Parameter ABI + class ParamAbi + attr_accessor :name, :type, :indexed + + def initialize(data) + @name = data['name'] || '' + @type = TypeInfo.new(data['type'] || { 'kind' => 'unknown' }) + @indexed = data['indexed'] || false + end + end + + # Function ABI + class FunctionAbi + attr_accessor :name, :selector, :inputs, :outputs, :view, :payable, :doc + + def initialize(data) + @name = data['name'] || '' + @selector = data['selector'] || '' + @inputs = (data['inputs'] || []).map { |i| ParamAbi.new(i) } + @outputs = (data['outputs'] || []).map { |o| ParamAbi.new(o) } + @view = data['view'] || false + @payable = data['payable'] || false + @doc = data['doc'] + end + end + + # Event ABI + class EventAbi + attr_accessor :name, :topic, :params, :doc + + def initialize(data) + @name = data['name'] || '' + @topic = data['topic'] || '' + @params = (data['params'] || []).map { |p| ParamAbi.new(p) } + @doc = data['doc'] + end + end + + # Error ABI + class ErrorAbi + attr_accessor :name, :selector, :params, :doc + + def initialize(data) + @name = data['name'] || '' + @selector = data['selector'] || '' + @params = (data['params'] || []).map { |p| ParamAbi.new(p) } + @doc = data['doc'] + end + end + + # Contract ABI + class ContractAbi + attr_accessor :name, :version, :functions, :events, :errors + + def initialize(data) + @name = data['name'] || '' + @version = data['version'] || '1.0.0' + @functions = (data['functions'] || []).map { |f| FunctionAbi.new(f) } + @events = (data['events'] || []).map { |e| EventAbi.new(e) } + @errors = (data['errors'] || []).map { |e| ErrorAbi.new(e) } + end + + def find_function(name) + @functions.find { |f| f.name == name } + end + + def find_by_selector(selector) + @functions.find { |f| f.selector == selector } + end + end + + # Validation error + class ValidationError + attr_accessor :code, :message, :location + + def initialize(data) + @code = data['code'] || '' + @message = data['message'] || '' + @location = data['location'] + end + end + + # Validation result + class ValidationResult + attr_accessor :valid, :errors, :warnings, :export_count, + :import_count, :function_count, :memory_pages + + def initialize(data) + @valid = data['valid'] || false + @errors = (data['errors'] || []).map { |e| ValidationError.new(e) } + @warnings = data['warnings'] || [] + @export_count = data['export_count'] || 0 + @import_count = data['import_count'] || 0 + @function_count = data['function_count'] || 0 + @memory_pages = data['memory_pages'] || [0, nil] + end + end + + # Contract metadata + class ContractMetadata + attr_accessor :name, :version, :authors, :description, :license, + :repository, :build_timestamp, :rust_version, :sdk_version, :custom + + def initialize(data) + @name = data['name'] + @version = data['version'] + @authors = data['authors'] || [] + @description = data['description'] + @license = data['license'] + @repository = data['repository'] + @build_timestamp = data['build_timestamp'] + @rust_version = data['rust_version'] + @sdk_version = data['sdk_version'] + @custom = data['custom'] || {} + end + end + + # Compilation result + class CompilationResult + attr_accessor :contract_id, :code, :code_hash, :original_size, :optimized_size, + :size_reduction, :metadata, :abi, :estimated_deploy_gas, + :validation, :warnings + + def initialize(data) + @contract_id = data['contract_id'] || '' + @code = data['code'] || '' + @code_hash = data['code_hash'] || '' + @original_size = data['original_size'] || 0 + @optimized_size = data['optimized_size'] || 0 + @size_reduction = data['size_reduction'] || 0.0 + @metadata = data['metadata'] ? ContractMetadata.new(data['metadata']) : nil + @abi = data['abi'] ? ContractAbi.new(data['abi']) : nil + @estimated_deploy_gas = data['estimated_deploy_gas'] || 0 + @validation = data['validation'] ? ValidationResult.new(data['validation']) : nil + @warnings = data['warnings'] || [] + end + + def size_stats + "Original: #{@original_size} bytes, Optimized: #{@optimized_size} bytes, Reduction: #{format('%.1f', @size_reduction)}%" + end + + def decode_code + Base64.decode64(@code) + end + end + + # Size breakdown + class SizeBreakdown + attr_accessor :code, :data, :types, :functions, :memory, + :table, :exports, :imports, :custom, :total + + def initialize(data) + @code = data['code'] || 0 + @data = data['data'] || 0 + @types = data['types'] || 0 + @functions = data['functions'] || 0 + @memory = data['memory'] || 0 + @table = data['table'] || 0 + @exports = data['exports'] || 0 + @imports = data['imports'] || 0 + @custom = data['custom'] || 0 + @total = data['total'] || 0 + end + end + + # Function analysis + class FunctionAnalysis + attr_accessor :name, :size, :instruction_count, :local_count, :exported, :estimated_gas + + def initialize(data) + @name = data['name'] || '' + @size = data['size'] || 0 + @instruction_count = data['instruction_count'] || 0 + @local_count = data['local_count'] || 0 + @exported = data['exported'] || false + @estimated_gas = data['estimated_gas'] || 0 + end + end + + # Import analysis + class ImportAnalysis + attr_accessor :mod, :name, :kind, :signature + + def initialize(data) + @mod = data['module'] || '' + @name = data['name'] || '' + @kind = data['kind'] || 'function' + @signature = data['signature'] + end + end + + # Security issue + class SecurityIssue + attr_accessor :severity, :type, :description, :location + + def initialize(data) + @severity = data['severity'] || 'low' + @type = data['type'] || '' + @description = data['description'] || '' + @location = data['location'] + end + end + + # Security analysis + class SecurityAnalysis + attr_accessor :score, :issues, :recommendations + + def initialize(data) + @score = data['score'] || 0 + @issues = (data['issues'] || []).map { |i| SecurityIssue.new(i) } + @recommendations = data['recommendations'] || [] + end + end + + # Gas analysis + class GasAnalysis + attr_accessor :deployment_gas, :function_gas, :memory_init_gas, :data_section_gas + + def initialize(data) + @deployment_gas = data['deployment_gas'] || 0 + @function_gas = data['function_gas'] || {} + @memory_init_gas = data['memory_init_gas'] || 0 + @data_section_gas = data['data_section_gas'] || 0 + end + end + + # Contract analysis + class ContractAnalysis + attr_accessor :size_breakdown, :functions, :imports, :security, :gas_analysis + + def initialize(data) + @size_breakdown = SizeBreakdown.new(data['size_breakdown'] || {}) + @functions = (data['functions'] || []).map { |f| FunctionAnalysis.new(f) } + @imports = (data['imports'] || []).map { |i| ImportAnalysis.new(i) } + @security = data['security'] ? SecurityAnalysis.new(data['security']) : nil + @gas_analysis = data['gas_analysis'] ? GasAnalysis.new(data['gas_analysis']) : nil + end + end + + # Main compiler client + class Client + attr_reader :contracts, :abi, :analysis, :validation + + def initialize(config) + @config = config + @closed = false + + @contracts = ContractsClient.new(self) + @abi = AbiClient.new(self) + @analysis = AnalysisClient.new(self) + @validation = ValidationClient.new(self) + end + + def default_optimization_level + @config.optimization_level + end + + def health_check + result = get('/health') + result['status'] == 'healthy' + rescue StandardError + false + end + + def info + get('/info') + end + + def close + @closed = true + end + + def closed? + @closed + end + + def compile(wasm, **options) + wasm_base64 = Base64.strict_encode64(wasm) + + body = { + wasm: wasm_base64, + optimization_level: options[:optimization_level] || @config.optimization_level, + use_wasm_opt: options.key?(:use_wasm_opt) ? options[:use_wasm_opt] : @config.use_wasm_opt, + validate: options.key?(:validate) ? options[:validate] : @config.validate, + extract_metadata: options.key?(:extract_metadata) ? options[:extract_metadata] : @config.extract_metadata, + generate_abi: options.key?(:generate_abi) ? options[:generate_abi] : @config.generate_abi + } + + body[:strip_options] = options[:strip_options].to_h if options[:strip_options] + + result = post('/compile', body) + CompilationResult.new(result) + end + + def get(path) + check_closed! + uri = URI("#{@config.endpoint}#{path}") + request = Net::HTTP::Get.new(uri) + execute_request(uri, request) + end + + def post(path, body) + check_closed! + uri = URI("#{@config.endpoint}#{path}") + request = Net::HTTP::Post.new(uri) + request.body = body.to_json + execute_request(uri, request) + end + + private + + def execute_request(uri, request) + request['Authorization'] = "Bearer #{@config.api_key}" + request['Content-Type'] = 'application/json' + request['X-SDK-Version'] = 'ruby/0.1.0' + + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = uri.scheme == 'https' + http.read_timeout = @config.timeout + http.open_timeout = @config.timeout + + response = http.request(request) + handle_response(response) + end + + def handle_response(response) + body = JSON.parse(response.body) + + unless response.is_a?(Net::HTTPSuccess) + raise CompilerError.new( + body['message'] || "HTTP #{response.code}", + code: body['code'], + http_status: response.code.to_i + ) + end + + body + end + + def check_closed! + raise CompilerError.new('Client has been closed', code: 'CLIENT_CLOSED') if @closed + end + end + + # Contracts sub-client + class ContractsClient + def initialize(client) + @client = client + end + + def compile(wasm, **options) + @client.compile(wasm, **options) + end + + def compile_dev(wasm) + @client.compile(wasm, optimization_level: OptimizationLevel::NONE, use_wasm_opt: false, validate: true) + end + + def compile_production(wasm) + @client.compile(wasm, + optimization_level: OptimizationLevel::AGGRESSIVE, + use_wasm_opt: true, + validate: true, + extract_metadata: true, + generate_abi: true) + end + + def get(contract_id) + result = @client.get("/contracts/#{contract_id}") + CompilationResult.new(result) + end + + def list(limit: nil, offset: nil) + params = [] + params << "limit=#{limit}" if limit + params << "offset=#{offset}" if offset + path = "/contracts#{params.empty? ? '' : "?#{params.join('&')}"}" + + result = @client.get(path) + (result['contracts'] || []).map { |c| CompilationResult.new(c) } + end + + def get_code(contract_id) + result = @client.get("/contracts/#{contract_id}/code") + Base64.decode64(result['code'] || '') + end + end + + # ABI sub-client + class AbiClient + def initialize(client) + @client = client + end + + def extract(wasm) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/abi/extract', { wasm: wasm_base64 }) + ContractAbi.new(result) + end + + def get(contract_id) + result = @client.get("/contracts/#{contract_id}/abi") + ContractAbi.new(result) + end + + def encode_call(function_abi, args) + result = @client.post('/abi/encode', { + function: { + name: function_abi.name, + selector: function_abi.selector, + inputs: function_abi.inputs.map { |i| { name: i.name, type: { kind: i.type.kind } } }, + outputs: function_abi.outputs.map { |o| { name: o.name, type: { kind: o.type.kind } } } + }, + args: args + }) + result['data'] || '' + end + + def decode_result(function_abi, data) + result = @client.post('/abi/decode', { + function: { + name: function_abi.name, + selector: function_abi.selector + }, + data: data + }) + result['values'] || [] + end + end + + # Analysis sub-client + class AnalysisClient + def initialize(client) + @client = client + end + + def analyze(wasm) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/analysis', { wasm: wasm_base64 }) + ContractAnalysis.new(result) + end + + def get(contract_id) + result = @client.get("/contracts/#{contract_id}/analysis") + ContractAnalysis.new(result) + end + + def extract_metadata(wasm) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/analysis/metadata', { wasm: wasm_base64 }) + ContractMetadata.new(result) + end + + def estimate_deploy_gas(wasm) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/analysis/estimate-gas', { wasm: wasm_base64 }) + result['gas'] || 0 + end + + def security_scan(wasm) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/analysis/security', { wasm: wasm_base64 }) + SecurityAnalysis.new(result['security'] || {}) + end + end + + # Validation sub-client + class ValidationClient + def initialize(client) + @client = client + end + + def validate(wasm) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/validate', { wasm: wasm_base64 }) + ValidationResult.new(result) + end + + def valid?(wasm) + validate(wasm).valid + end + + def errors(wasm) + validate(wasm).errors.map(&:message) + end + + def validate_exports(wasm, required_exports) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/validate/exports', { + wasm: wasm_base64, + required_exports: required_exports + }) + result['valid'] || false + end + + def validate_memory(wasm, max_pages) + wasm_base64 = Base64.strict_encode64(wasm) + result = @client.post('/validate/memory', { + wasm: wasm_base64, + max_pages: max_pages + }) + result['valid'] || false + end + end + end +end diff --git a/sdk/rust/src/compiler/client.rs b/sdk/rust/src/compiler/client.rs new file mode 100644 index 0000000..cbb4f20 --- /dev/null +++ b/sdk/rust/src/compiler/client.rs @@ -0,0 +1,562 @@ +//! Synor Compiler SDK Client +//! +//! Smart contract compilation, optimization, and ABI generation. + +use base64::Engine; +use reqwest::Client; +use serde::de::DeserializeOwned; +use serde_json::json; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use super::types::*; + +/// Synor Compiler SDK Client. +/// +/// Smart contract compilation, optimization, and ABI generation. +/// +/// # Example +/// +/// ```ignore +/// use synor_sdk::compiler::{SynorCompiler, CompilerConfig, OptimizationLevel}; +/// +/// let config = CompilerConfig::new("your-api-key"); +/// let compiler = SynorCompiler::new(config)?; +/// +/// let result = compiler.compile(&wasm_bytes, None).await?; +/// println!("Optimized size: {} bytes", result.optimized_size); +/// ``` +pub struct SynorCompiler { + config: CompilerConfig, + client: Client, + closed: Arc, +} + +impl SynorCompiler { + /// Create a new compiler client. + pub fn new(config: CompilerConfig) -> Result { + let client = Client::builder() + .timeout(Duration::from_millis(config.timeout_ms)) + .build() + .map_err(|e| CompilerError { + message: e.to_string(), + code: Some("HTTP_CLIENT_ERROR".to_string()), + http_status: None, + })?; + + Ok(SynorCompiler { + config, + client, + closed: Arc::new(AtomicBool::new(false)), + }) + } + + /// Get the default optimization level. + pub fn default_optimization_level(&self) -> OptimizationLevel { + self.config.optimization_level + } + + /// Check service health. + pub async fn health_check(&self) -> bool { + match self.get::("/health").await { + Ok(result) => result.get("status").and_then(|v| v.as_str()) == Some("healthy"), + Err(_) => false, + } + } + + /// Get service info. + pub async fn get_info(&self) -> Result { + self.get("/info").await + } + + /// Close the client. + pub fn close(&self) { + self.closed.store(true, Ordering::SeqCst); + } + + /// Check if client is closed. + pub fn is_closed(&self) -> bool { + self.closed.load(Ordering::SeqCst) + } + + /// Compile WASM bytecode. + pub async fn compile( + &self, + wasm: &[u8], + opts: Option, + ) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + + let body = if let Some(opts) = opts { + json!({ + "wasm": wasm_base64, + "optimization_level": opts.optimization_level.unwrap_or(self.config.optimization_level), + "strip_options": opts.strip_options.or(self.config.strip_options.clone()), + "use_wasm_opt": opts.use_wasm_opt.unwrap_or(self.config.use_wasm_opt), + "validate": opts.validate.unwrap_or(self.config.validate), + "extract_metadata": opts.extract_metadata.unwrap_or(self.config.extract_metadata), + "generate_abi": opts.generate_abi.unwrap_or(self.config.generate_abi), + }) + } else { + json!({ + "wasm": wasm_base64, + "optimization_level": self.config.optimization_level, + "use_wasm_opt": self.config.use_wasm_opt, + "validate": self.config.validate, + "extract_metadata": self.config.extract_metadata, + "generate_abi": self.config.generate_abi, + }) + }; + + self.post("/compile", body).await + } + + /// Get sub-client for contracts. + pub fn contracts(&self) -> ContractsClient<'_> { + ContractsClient { compiler: self } + } + + /// Get sub-client for ABI operations. + pub fn abi(&self) -> AbiClient<'_> { + AbiClient { compiler: self } + } + + /// Get sub-client for analysis. + pub fn analysis(&self) -> AnalysisClient<'_> { + AnalysisClient { compiler: self } + } + + /// Get sub-client for validation. + pub fn validation(&self) -> ValidationClient<'_> { + ValidationClient { compiler: self } + } + + async fn get(&self, path: &str) -> Result { + self.check_closed()?; + + let response = self + .client + .get(format!("{}{}", self.config.endpoint, path)) + .header("Authorization", format!("Bearer {}", self.config.api_key)) + .header("Content-Type", "application/json") + .header("X-SDK-Version", "rust/0.1.0") + .send() + .await + .map_err(|e| CompilerError { + message: e.to_string(), + code: Some("NETWORK_ERROR".to_string()), + http_status: None, + })?; + + self.handle_response(response).await + } + + async fn post( + &self, + path: &str, + body: serde_json::Value, + ) -> Result { + self.check_closed()?; + + let response = self + .client + .post(format!("{}{}", self.config.endpoint, path)) + .header("Authorization", format!("Bearer {}", self.config.api_key)) + .header("Content-Type", "application/json") + .header("X-SDK-Version", "rust/0.1.0") + .json(&body) + .send() + .await + .map_err(|e| CompilerError { + message: e.to_string(), + code: Some("NETWORK_ERROR".to_string()), + http_status: None, + })?; + + self.handle_response(response).await + } + + async fn handle_response( + &self, + response: reqwest::Response, + ) -> Result { + let status = response.status(); + + if !status.is_success() { + let text = response.text().await.unwrap_or_default(); + let error: serde_json::Value = serde_json::from_str(&text).unwrap_or_default(); + + return Err(CompilerError { + message: error + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or(&format!("HTTP {}", status.as_u16())) + .to_string(), + code: error + .get("code") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + http_status: Some(status.as_u16()), + }); + } + + response.json().await.map_err(|e| CompilerError { + message: e.to_string(), + code: Some("PARSE_ERROR".to_string()), + http_status: None, + }) + } + + fn check_closed(&self) -> Result<(), CompilerError> { + if self.closed.load(Ordering::SeqCst) { + return Err(CompilerError { + message: "Client has been closed".to_string(), + code: Some("CLIENT_CLOSED".to_string()), + http_status: None, + }); + } + Ok(()) + } +} + +/// Contracts sub-client. +pub struct ContractsClient<'a> { + compiler: &'a SynorCompiler, +} + +impl<'a> ContractsClient<'a> { + /// Compile WASM bytecode with default settings. + pub async fn compile( + &self, + wasm: &[u8], + opts: Option, + ) -> Result { + self.compiler.compile(wasm, opts).await + } + + /// Compile with development settings. + pub async fn compile_dev(&self, wasm: &[u8]) -> Result { + self.compiler + .compile( + wasm, + Some(CompilationRequest { + wasm: String::new(), + optimization_level: Some(OptimizationLevel::None), + strip_options: None, + use_wasm_opt: Some(false), + validate: Some(true), + extract_metadata: None, + generate_abi: None, + }), + ) + .await + } + + /// Compile with production settings. + pub async fn compile_production( + &self, + wasm: &[u8], + ) -> Result { + self.compiler + .compile( + wasm, + Some(CompilationRequest { + wasm: String::new(), + optimization_level: Some(OptimizationLevel::Aggressive), + strip_options: None, + use_wasm_opt: Some(true), + validate: Some(true), + extract_metadata: Some(true), + generate_abi: Some(true), + }), + ) + .await + } + + /// Get a compiled contract by ID. + pub async fn get(&self, contract_id: &str) -> Result { + self.compiler + .get(&format!("/contracts/{}", contract_id)) + .await + } + + /// List compiled contracts. + pub async fn list( + &self, + limit: Option, + offset: Option, + ) -> Result, CompilerError> { + let mut path = "/contracts".to_string(); + let mut params = Vec::new(); + if let Some(l) = limit { + params.push(format!("limit={}", l)); + } + if let Some(o) = offset { + params.push(format!("offset={}", o)); + } + if !params.is_empty() { + path.push('?'); + path.push_str(¶ms.join("&")); + } + + #[derive(serde::Deserialize)] + struct Response { + contracts: Vec, + } + + let result: Response = self.compiler.get(&path).await?; + Ok(result.contracts) + } + + /// Get optimized bytecode for a contract. + pub async fn get_code(&self, contract_id: &str) -> Result, CompilerError> { + #[derive(serde::Deserialize)] + struct Response { + code: String, + } + + let result: Response = self + .compiler + .get(&format!("/contracts/{}/code", contract_id)) + .await?; + + base64::engine::general_purpose::STANDARD + .decode(&result.code) + .map_err(|e| CompilerError { + message: e.to_string(), + code: Some("DECODE_ERROR".to_string()), + http_status: None, + }) + } +} + +/// ABI sub-client. +pub struct AbiClient<'a> { + compiler: &'a SynorCompiler, +} + +impl<'a> AbiClient<'a> { + /// Extract ABI from WASM bytecode. + pub async fn extract(&self, wasm: &[u8]) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + self.compiler + .post("/abi/extract", json!({ "wasm": wasm_base64 })) + .await + } + + /// Get ABI for a compiled contract. + pub async fn get(&self, contract_id: &str) -> Result { + self.compiler + .get(&format!("/contracts/{}/abi", contract_id)) + .await + } + + /// Encode function call. + pub async fn encode_call( + &self, + function_abi: &FunctionAbi, + args: Vec, + ) -> Result { + #[derive(serde::Deserialize)] + struct Response { + data: String, + } + + let result: Response = self + .compiler + .post( + "/abi/encode", + json!({ + "function": function_abi, + "args": args, + }), + ) + .await?; + + Ok(result.data) + } + + /// Decode function result. + pub async fn decode_result( + &self, + function_abi: &FunctionAbi, + data: &str, + ) -> Result, CompilerError> { + #[derive(serde::Deserialize)] + struct Response { + values: Vec, + } + + let result: Response = self + .compiler + .post( + "/abi/decode", + json!({ + "function": function_abi, + "data": data, + }), + ) + .await?; + + Ok(result.values) + } +} + +/// Analysis sub-client. +pub struct AnalysisClient<'a> { + compiler: &'a SynorCompiler, +} + +impl<'a> AnalysisClient<'a> { + /// Analyze WASM bytecode. + pub async fn analyze(&self, wasm: &[u8]) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + self.compiler + .post("/analysis", json!({ "wasm": wasm_base64 })) + .await + } + + /// Get analysis for a compiled contract. + pub async fn get(&self, contract_id: &str) -> Result { + self.compiler + .get(&format!("/contracts/{}/analysis", contract_id)) + .await + } + + /// Extract metadata from WASM bytecode. + pub async fn extract_metadata(&self, wasm: &[u8]) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + self.compiler + .post("/analysis/metadata", json!({ "wasm": wasm_base64 })) + .await + } + + /// Extract source map from WASM bytecode. + pub async fn extract_source_map(&self, wasm: &[u8]) -> Result, CompilerError> { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + + #[derive(serde::Deserialize)] + struct Response { + source_map: Option, + } + + let result: Response = self + .compiler + .post("/analysis/source-map", json!({ "wasm": wasm_base64 })) + .await?; + + Ok(result.source_map) + } + + /// Estimate gas for contract deployment. + pub async fn estimate_deploy_gas(&self, wasm: &[u8]) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + + #[derive(serde::Deserialize)] + struct Response { + gas: u64, + } + + let result: Response = self + .compiler + .post("/analysis/estimate-gas", json!({ "wasm": wasm_base64 })) + .await?; + + Ok(result.gas) + } + + /// Get security analysis. + pub async fn security_scan(&self, wasm: &[u8]) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + + #[derive(serde::Deserialize)] + struct Response { + security: SecurityAnalysis, + } + + let result: Response = self + .compiler + .post("/analysis/security", json!({ "wasm": wasm_base64 })) + .await?; + + Ok(result.security) + } +} + +/// Validation sub-client. +pub struct ValidationClient<'a> { + compiler: &'a SynorCompiler, +} + +impl<'a> ValidationClient<'a> { + /// Validate WASM bytecode against VM requirements. + pub async fn validate(&self, wasm: &[u8]) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + self.compiler + .post("/validate", json!({ "wasm": wasm_base64 })) + .await + } + + /// Check if WASM is valid. + pub async fn is_valid(&self, wasm: &[u8]) -> Result { + let result = self.validate(wasm).await?; + Ok(result.valid) + } + + /// Get validation errors. + pub async fn get_errors(&self, wasm: &[u8]) -> Result, CompilerError> { + let result = self.validate(wasm).await?; + Ok(result.errors.into_iter().map(|e| e.message).collect()) + } + + /// Validate contract exports. + pub async fn validate_exports( + &self, + wasm: &[u8], + required_exports: Vec, + ) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + + #[derive(serde::Deserialize)] + struct Response { + valid: bool, + } + + let result: Response = self + .compiler + .post( + "/validate/exports", + json!({ + "wasm": wasm_base64, + "required_exports": required_exports, + }), + ) + .await?; + + Ok(result.valid) + } + + /// Validate memory limits. + pub async fn validate_memory(&self, wasm: &[u8], max_pages: u32) -> Result { + let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm); + + #[derive(serde::Deserialize)] + struct Response { + valid: bool, + } + + let result: Response = self + .compiler + .post( + "/validate/memory", + json!({ + "wasm": wasm_base64, + "max_pages": max_pages, + }), + ) + .await?; + + Ok(result.valid) + } +} diff --git a/sdk/rust/src/compiler/mod.rs b/sdk/rust/src/compiler/mod.rs new file mode 100644 index 0000000..b3deb37 --- /dev/null +++ b/sdk/rust/src/compiler/mod.rs @@ -0,0 +1,9 @@ +//! Synor Compiler SDK for Rust +//! +//! Smart contract compilation, optimization, and ABI generation. + +pub mod types; +pub mod client; + +pub use types::*; +pub use client::*; diff --git a/sdk/rust/src/compiler/types.rs b/sdk/rust/src/compiler/types.rs new file mode 100644 index 0000000..66f26ea --- /dev/null +++ b/sdk/rust/src/compiler/types.rs @@ -0,0 +1,490 @@ +//! Synor Compiler SDK Types +//! +//! Smart contract compilation, optimization, and ABI generation. + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Optimization level for WASM compilation. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum OptimizationLevel { + /// No optimization. + None, + /// Basic optimizations (stripping only). + Basic, + /// Optimize for size (-Os equivalent). + Size, + /// Aggressive optimization (-O3 -Os equivalent). + Aggressive, +} + +impl Default for OptimizationLevel { + fn default() -> Self { + OptimizationLevel::Size + } +} + +/// Options for stripping WASM sections. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StripOptions { + pub strip_debug: bool, + pub strip_producers: bool, + pub strip_names: bool, + pub strip_custom: bool, + pub preserve_sections: Vec, + pub strip_unused: bool, +} + +impl Default for StripOptions { + fn default() -> Self { + StripOptions { + strip_debug: true, + strip_producers: true, + strip_names: true, + strip_custom: true, + preserve_sections: Vec::new(), + strip_unused: true, + } + } +} + +/// Compiler configuration. +#[derive(Clone, Debug)] +pub struct CompilerConfig { + pub api_key: String, + pub endpoint: String, + pub timeout_ms: u64, + pub retries: u32, + pub debug: bool, + pub optimization_level: OptimizationLevel, + pub strip_options: Option, + pub max_contract_size: usize, + pub use_wasm_opt: bool, + pub validate: bool, + pub extract_metadata: bool, + pub generate_abi: bool, +} + +impl CompilerConfig { + pub fn new(api_key: impl Into) -> Self { + CompilerConfig { + api_key: api_key.into(), + endpoint: "https://compiler.synor.io/v1".to_string(), + timeout_ms: 60000, + retries: 3, + debug: false, + optimization_level: OptimizationLevel::Size, + strip_options: None, + max_contract_size: 256 * 1024, + use_wasm_opt: true, + validate: true, + extract_metadata: true, + generate_abi: true, + } + } + + pub fn with_endpoint(mut self, endpoint: impl Into) -> Self { + self.endpoint = endpoint.into(); + self + } + + pub fn with_optimization_level(mut self, level: OptimizationLevel) -> Self { + self.optimization_level = level; + self + } +} + +/// Compilation request. +#[derive(Clone, Debug, Serialize)] +pub struct CompilationRequest { + pub wasm: String, // Base64 encoded + #[serde(skip_serializing_if = "Option::is_none")] + pub optimization_level: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub strip_options: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_wasm_opt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub validate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub extract_metadata: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub generate_abi: Option, +} + +/// Validation error. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ValidationError { + pub code: String, + pub message: String, + pub location: Option, +} + +/// Validation result. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ValidationResult { + pub valid: bool, + #[serde(default)] + pub errors: Vec, + #[serde(default)] + pub warnings: Vec, + #[serde(default)] + pub export_count: usize, + #[serde(default)] + pub import_count: usize, + #[serde(default)] + pub function_count: usize, + #[serde(default)] + pub memory_pages: (u32, Option), +} + +/// Contract metadata. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ContractMetadata { + pub name: Option, + pub version: Option, + #[serde(default)] + pub authors: Vec, + pub description: Option, + pub license: Option, + pub repository: Option, + pub build_timestamp: Option, + pub rust_version: Option, + pub sdk_version: Option, + #[serde(default)] + pub custom: HashMap, +} + +/// Type information. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum TypeInfo { + #[serde(rename = "u8")] + U8, + #[serde(rename = "u16")] + U16, + #[serde(rename = "u32")] + U32, + #[serde(rename = "u64")] + U64, + #[serde(rename = "u128")] + U128, + #[serde(rename = "i8")] + I8, + #[serde(rename = "i16")] + I16, + #[serde(rename = "i32")] + I32, + #[serde(rename = "i64")] + I64, + #[serde(rename = "i128")] + I128, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "string")] + String, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "fixedBytes")] + FixedBytes { size: usize }, + #[serde(rename = "address")] + Address, + #[serde(rename = "hash256")] + Hash256, + #[serde(rename = "array")] + Array { element: Box }, + #[serde(rename = "fixedArray")] + FixedArray { element: Box, size: usize }, + #[serde(rename = "option")] + Option { inner: Box }, + #[serde(rename = "tuple")] + Tuple { elements: Vec }, + #[serde(rename = "struct")] + Struct { + name: String, + fields: Vec<(String, TypeInfo)>, + }, + #[serde(rename = "unknown")] + Unknown { name: String }, +} + +impl TypeInfo { + /// Get the type name as a string. + pub fn type_name(&self) -> String { + match self { + TypeInfo::U8 => "u8".to_string(), + TypeInfo::U16 => "u16".to_string(), + TypeInfo::U32 => "u32".to_string(), + TypeInfo::U64 => "u64".to_string(), + TypeInfo::U128 => "u128".to_string(), + TypeInfo::I8 => "i8".to_string(), + TypeInfo::I16 => "i16".to_string(), + TypeInfo::I32 => "i32".to_string(), + TypeInfo::I64 => "i64".to_string(), + TypeInfo::I128 => "i128".to_string(), + TypeInfo::Bool => "bool".to_string(), + TypeInfo::String => "String".to_string(), + TypeInfo::Bytes => "Vec".to_string(), + TypeInfo::FixedBytes { size } => format!("[u8; {}]", size), + TypeInfo::Address => "Address".to_string(), + TypeInfo::Hash256 => "Hash256".to_string(), + TypeInfo::Array { element } => format!("Vec<{}>", element.type_name()), + TypeInfo::FixedArray { element, size } => { + format!("[{}; {}]", element.type_name(), size) + } + TypeInfo::Option { inner } => format!("Option<{}>", inner.type_name()), + TypeInfo::Tuple { elements } => { + let names: Vec<_> = elements.iter().map(|e| e.type_name()).collect(); + format!("({})", names.join(", ")) + } + TypeInfo::Struct { name, .. } => name.clone(), + TypeInfo::Unknown { name } => name.clone(), + } + } +} + +/// Parameter ABI. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ParamAbi { + pub name: String, + #[serde(rename = "type")] + pub ty: TypeInfo, + #[serde(default)] + pub indexed: bool, +} + +/// Function ABI. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FunctionAbi { + pub name: String, + pub selector: String, + #[serde(default)] + pub inputs: Vec, + #[serde(default)] + pub outputs: Vec, + #[serde(default)] + pub view: bool, + #[serde(default)] + pub payable: bool, + pub doc: Option, +} + +/// Event ABI. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EventAbi { + pub name: String, + pub topic: String, + #[serde(default)] + pub params: Vec, + pub doc: Option, +} + +/// Error ABI. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ErrorAbi { + pub name: String, + pub selector: String, + #[serde(default)] + pub params: Vec, + pub doc: Option, +} + +/// Contract ABI (Application Binary Interface). +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ContractAbi { + pub name: String, + pub version: String, + #[serde(default)] + pub functions: Vec, + #[serde(default)] + pub events: Vec, + #[serde(default)] + pub errors: Vec, +} + +impl ContractAbi { + /// Find a function by name. + pub fn find_function(&self, name: &str) -> Option<&FunctionAbi> { + self.functions.iter().find(|f| f.name == name) + } + + /// Find a function by selector. + pub fn find_by_selector(&self, selector: &str) -> Option<&FunctionAbi> { + self.functions.iter().find(|f| f.selector == selector) + } + + /// Serialize to JSON. + pub fn to_json(&self) -> Result { + serde_json::to_string_pretty(self) + } + + /// Deserialize from JSON. + pub fn from_json(json: &str) -> Result { + serde_json::from_str(json) + } +} + +/// Compilation result. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CompilationResult { + pub contract_id: String, + pub code: String, // Base64 encoded + pub code_hash: String, + pub original_size: usize, + pub optimized_size: usize, + pub size_reduction: f64, + pub metadata: Option, + pub abi: Option, + #[serde(default)] + pub estimated_deploy_gas: u64, + pub validation: Option, + #[serde(default)] + pub warnings: Vec, +} + +impl CompilationResult { + /// Returns size statistics as a formatted string. + pub fn size_stats(&self) -> String { + format!( + "Original: {} bytes, Optimized: {} bytes, Reduction: {:.1}%", + self.original_size, self.optimized_size, self.size_reduction + ) + } + + /// Decode the code from base64. + pub fn decode_code(&self) -> Result, base64::DecodeError> { + use base64::Engine; + base64::engine::general_purpose::STANDARD.decode(&self.code) + } +} + +/// Source mapping entry. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SourceMapping { + pub wasm_offset: usize, + pub line: usize, + pub column: usize, + pub function: Option, +} + +/// Source map for debugging. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SourceMap { + pub file: String, + #[serde(default)] + pub mappings: Vec, +} + +/// Size breakdown. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct SizeBreakdown { + #[serde(default)] + pub code: usize, + #[serde(default)] + pub data: usize, + #[serde(default)] + pub types: usize, + #[serde(default)] + pub functions: usize, + #[serde(default)] + pub memory: usize, + #[serde(default)] + pub table: usize, + #[serde(default)] + pub exports: usize, + #[serde(default)] + pub imports: usize, + #[serde(default)] + pub custom: usize, + #[serde(default)] + pub total: usize, +} + +/// Function analysis. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FunctionAnalysis { + pub name: String, + pub size: usize, + pub instruction_count: usize, + pub local_count: usize, + pub exported: bool, + pub estimated_gas: u64, +} + +/// Import analysis. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ImportAnalysis { + pub module: String, + pub name: String, + pub kind: String, // function, memory, table, global + pub signature: Option, +} + +/// Security issue. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SecurityIssue { + pub severity: String, // low, medium, high, critical + #[serde(rename = "type")] + pub issue_type: String, + pub description: String, + pub location: Option, +} + +/// Security analysis. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SecurityAnalysis { + pub score: u8, // 0-100 + #[serde(default)] + pub issues: Vec, + #[serde(default)] + pub recommendations: Vec, +} + +/// Gas analysis. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GasAnalysis { + pub deployment_gas: u64, + #[serde(default)] + pub function_gas: HashMap, + #[serde(default)] + pub memory_init_gas: u64, + #[serde(default)] + pub data_section_gas: u64, +} + +/// Contract analysis result. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ContractAnalysis { + pub size_breakdown: SizeBreakdown, + #[serde(default)] + pub functions: Vec, + #[serde(default)] + pub imports: Vec, + pub security: Option, + pub gas_analysis: Option, +} + +/// Compiler error. +#[derive(Debug)] +pub struct CompilerError { + pub message: String, + pub code: Option, + pub http_status: Option, +} + +impl std::fmt::Display for CompilerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(code) = &self.code { + write!(f, "{} ({})", self.message, code) + } else { + write!(f, "{}", self.message) + } + } +} + +impl std::error::Error for CompilerError {} + +/// Constants +pub const MAX_CONTRACT_SIZE: usize = 256 * 1024; // 256 KB +pub const MAX_MEMORY_PAGES: u32 = 16; diff --git a/sdk/swift/Sources/SynorCompiler/SynorCompiler.swift b/sdk/swift/Sources/SynorCompiler/SynorCompiler.swift new file mode 100644 index 0000000..b04b31a --- /dev/null +++ b/sdk/swift/Sources/SynorCompiler/SynorCompiler.swift @@ -0,0 +1,590 @@ +/** + * Synor Compiler SDK for Swift + * + * Smart contract compilation, optimization, and ABI generation. + */ + +import Foundation + +// MARK: - Enumerations + +public enum OptimizationLevel: String, Codable { + case none + case basic + case size + case aggressive +} + +// MARK: - Configuration + +public struct StripOptions: Codable { + public var stripDebug: Bool = true + public var stripProducers: Bool = true + public var stripNames: Bool = true + public var stripCustom: Bool = true + public var preserveSections: [String] = [] + public var stripUnused: Bool = true + + public init() {} + + private enum CodingKeys: String, CodingKey { + case stripDebug = "strip_debug" + case stripProducers = "strip_producers" + case stripNames = "strip_names" + case stripCustom = "strip_custom" + case preserveSections = "preserve_sections" + case stripUnused = "strip_unused" + } +} + +public struct CompilerConfig { + public let apiKey: String + public var endpoint: String = "https://compiler.synor.io/v1" + public var timeout: TimeInterval = 60 + public var retries: Int = 3 + public var debug: Bool = false + public var optimizationLevel: OptimizationLevel = .size + public var stripOptions: StripOptions? + public var maxContractSize: Int = 256 * 1024 + public var useWasmOpt: Bool = true + public var validate: Bool = true + public var extractMetadata: Bool = true + public var generateAbi: Bool = true + + public init(apiKey: String) { + self.apiKey = apiKey + } +} + +// MARK: - Types + +public struct ValidationError: Codable { + public let code: String + public let message: String + public let location: String? +} + +public struct ValidationResult: Codable { + public let valid: Bool + public let errors: [ValidationError] + public let warnings: [String] + public let exportCount: Int + public let importCount: Int + public let functionCount: Int + public let memoryPages: [Int?] + + private enum CodingKeys: String, CodingKey { + case valid, errors, warnings + case exportCount = "export_count" + case importCount = "import_count" + case functionCount = "function_count" + case memoryPages = "memory_pages" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + valid = try container.decode(Bool.self, forKey: .valid) + errors = try container.decodeIfPresent([ValidationError].self, forKey: .errors) ?? [] + warnings = try container.decodeIfPresent([String].self, forKey: .warnings) ?? [] + exportCount = try container.decodeIfPresent(Int.self, forKey: .exportCount) ?? 0 + importCount = try container.decodeIfPresent(Int.self, forKey: .importCount) ?? 0 + functionCount = try container.decodeIfPresent(Int.self, forKey: .functionCount) ?? 0 + memoryPages = try container.decodeIfPresent([Int?].self, forKey: .memoryPages) ?? [0, nil] + } +} + +public struct ContractMetadata: Codable { + public let name: String? + public let version: String? + public let authors: [String] + public let description: String? + public let license: String? + public let repository: String? + public let buildTimestamp: Int64? + public let rustVersion: String? + public let sdkVersion: String? + public let custom: [String: String] + + private enum CodingKeys: String, CodingKey { + case name, version, authors, description, license, repository, custom + case buildTimestamp = "build_timestamp" + case rustVersion = "rust_version" + case sdkVersion = "sdk_version" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) + version = try container.decodeIfPresent(String.self, forKey: .version) + authors = try container.decodeIfPresent([String].self, forKey: .authors) ?? [] + description = try container.decodeIfPresent(String.self, forKey: .description) + license = try container.decodeIfPresent(String.self, forKey: .license) + repository = try container.decodeIfPresent(String.self, forKey: .repository) + buildTimestamp = try container.decodeIfPresent(Int64.self, forKey: .buildTimestamp) + rustVersion = try container.decodeIfPresent(String.self, forKey: .rustVersion) + sdkVersion = try container.decodeIfPresent(String.self, forKey: .sdkVersion) + custom = try container.decodeIfPresent([String: String].self, forKey: .custom) ?? [:] + } +} + +public struct TypeInfo: Codable { + public let kind: String + public let size: Int? + public let element: TypeInfo? + public let inner: TypeInfo? + public let elements: [TypeInfo]? + public let name: String? + + public var typeName: String { + switch kind { + case "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", + "bool", "string", "bytes", "address", "hash256": + return kind + case "fixedBytes": + return "bytes\(size ?? 0)" + case "array": + return "\(element?.typeName ?? "")[]" + case "fixedArray": + return "\(element?.typeName ?? "")[\(size ?? 0)]" + case "option": + return "\(inner?.typeName ?? "")?" + case "tuple": + return "(\(elements?.map { $0.typeName }.joined(separator: ", ") ?? ""))" + case "struct": + return name ?? "struct" + default: + return name ?? "unknown" + } + } +} + +public struct ParamAbi: Codable { + public let name: String + public let type: TypeInfo + public let indexed: Bool + + private enum CodingKeys: String, CodingKey { + case name, type, indexed + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + type = try container.decode(TypeInfo.self, forKey: .type) + indexed = try container.decodeIfPresent(Bool.self, forKey: .indexed) ?? false + } +} + +public struct FunctionAbi: Codable { + public let name: String + public let selector: String + public let inputs: [ParamAbi] + public let outputs: [ParamAbi] + public let view: Bool + public let payable: Bool + public let doc: String? + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + selector = try container.decode(String.self, forKey: .selector) + inputs = try container.decodeIfPresent([ParamAbi].self, forKey: .inputs) ?? [] + outputs = try container.decodeIfPresent([ParamAbi].self, forKey: .outputs) ?? [] + view = try container.decodeIfPresent(Bool.self, forKey: .view) ?? false + payable = try container.decodeIfPresent(Bool.self, forKey: .payable) ?? false + doc = try container.decodeIfPresent(String.self, forKey: .doc) + } +} + +public struct EventAbi: Codable { + public let name: String + public let topic: String + public let params: [ParamAbi] + public let doc: String? + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + topic = try container.decode(String.self, forKey: .topic) + params = try container.decodeIfPresent([ParamAbi].self, forKey: .params) ?? [] + doc = try container.decodeIfPresent(String.self, forKey: .doc) + } +} + +public struct ErrorAbi: Codable { + public let name: String + public let selector: String + public let params: [ParamAbi] + public let doc: String? + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + selector = try container.decode(String.self, forKey: .selector) + params = try container.decodeIfPresent([ParamAbi].self, forKey: .params) ?? [] + doc = try container.decodeIfPresent(String.self, forKey: .doc) + } +} + +public struct ContractAbi: Codable { + public let name: String + public let version: String + public let functions: [FunctionAbi] + public let events: [EventAbi] + public let errors: [ErrorAbi] + + public func findFunction(name: String) -> FunctionAbi? { + functions.first { $0.name == name } + } + + public func findBySelector(_ selector: String) -> FunctionAbi? { + functions.first { $0.selector == selector } + } +} + +public struct CompilationResult: Codable { + public let contractId: String + public let code: String + public let codeHash: String + public let originalSize: Int + public let optimizedSize: Int + public let sizeReduction: Double + public let metadata: ContractMetadata? + public let abi: ContractAbi? + public let estimatedDeployGas: Int64 + public let validation: ValidationResult? + public let warnings: [String] + + private enum CodingKeys: String, CodingKey { + case contractId = "contract_id" + case code + case codeHash = "code_hash" + case originalSize = "original_size" + case optimizedSize = "optimized_size" + case sizeReduction = "size_reduction" + case metadata, abi + case estimatedDeployGas = "estimated_deploy_gas" + case validation, warnings + } + + public var sizeStats: String { + String(format: "Original: %d bytes, Optimized: %d bytes, Reduction: %.1f%%", + originalSize, optimizedSize, sizeReduction) + } + + public func decodeCode() -> Data? { + Data(base64Encoded: code) + } +} + +public struct SizeBreakdown: Codable { + public let code: Int + public let data: Int + public let types: Int + public let functions: Int + public let memory: Int + public let table: Int + public let exports: Int + public let imports: Int + public let custom: Int + public let total: Int +} + +public struct FunctionAnalysis: Codable { + public let name: String + public let size: Int + public let instructionCount: Int + public let localCount: Int + public let exported: Bool + public let estimatedGas: Int64 + + private enum CodingKeys: String, CodingKey { + case name, size, exported + case instructionCount = "instruction_count" + case localCount = "local_count" + case estimatedGas = "estimated_gas" + } +} + +public struct ImportAnalysis: Codable { + public let module: String + public let name: String + public let kind: String + public let signature: String? +} + +public struct SecurityIssue: Codable { + public let severity: String + public let type: String + public let description: String + public let location: String? +} + +public struct SecurityAnalysis: Codable { + public let score: Int + public let issues: [SecurityIssue] + public let recommendations: [String] +} + +public struct GasAnalysis: Codable { + public let deploymentGas: Int64 + public let functionGas: [String: Int64] + public let memoryInitGas: Int64 + public let dataSectionGas: Int64 + + private enum CodingKeys: String, CodingKey { + case deploymentGas = "deployment_gas" + case functionGas = "function_gas" + case memoryInitGas = "memory_init_gas" + case dataSectionGas = "data_section_gas" + } +} + +public struct ContractAnalysis: Codable { + public let sizeBreakdown: SizeBreakdown + public let functions: [FunctionAnalysis] + public let imports: [ImportAnalysis] + public let security: SecurityAnalysis? + public let gasAnalysis: GasAnalysis? + + private enum CodingKeys: String, CodingKey { + case sizeBreakdown = "size_breakdown" + case functions, imports, security + case gasAnalysis = "gas_analysis" + } +} + +public struct CompilerError: Error { + public let message: String + public let code: String? + public let httpStatus: Int? + + public init(_ message: String, code: String? = nil, httpStatus: Int? = nil) { + self.message = message + self.code = code + self.httpStatus = httpStatus + } +} + +// MARK: - Client + +public actor SynorCompiler { + private let config: CompilerConfig + private let session: URLSession + private let decoder: JSONDecoder + private let encoder: JSONEncoder + private var closed = false + + public let contracts: ContractsClient + public let abi: AbiClient + public let analysis: AnalysisClient + public let validation: ValidationClient + + public init(config: CompilerConfig) { + self.config = config + let configuration = URLSessionConfiguration.default + configuration.timeoutIntervalForRequest = config.timeout + self.session = URLSession(configuration: configuration) + self.decoder = JSONDecoder() + self.encoder = JSONEncoder() + self.encoder.keyEncodingStrategy = .convertToSnakeCase + + self.contracts = ContractsClient() + self.abi = AbiClient() + self.analysis = AnalysisClient() + self.validation = ValidationClient() + + Task { await self.contracts.setCompiler(self) } + Task { await self.abi.setCompiler(self) } + Task { await self.analysis.setCompiler(self) } + Task { await self.validation.setCompiler(self) } + } + + public var defaultOptimizationLevel: OptimizationLevel { config.optimizationLevel } + + public func healthCheck() async -> Bool { + do { + let result: [String: String] = try await get("/health") + return result["status"] == "healthy" + } catch { + return false + } + } + + public func getInfo() async throws -> [String: Any] { + try await get("/info") + } + + public func close() { + closed = true + } + + public var isClosed: Bool { closed } + + public func compile( + wasm: Data, + optimizationLevel: OptimizationLevel? = nil, + stripOptions: StripOptions? = nil, + useWasmOpt: Bool? = nil, + validate: Bool? = nil, + extractMetadata: Bool? = nil, + generateAbi: Bool? = nil + ) async throws -> CompilationResult { + let wasmBase64 = wasm.base64EncodedString() + var body: [String: Any] = [ + "wasm": wasmBase64, + "optimization_level": (optimizationLevel ?? config.optimizationLevel).rawValue, + "use_wasm_opt": useWasmOpt ?? config.useWasmOpt, + "validate": validate ?? config.validate, + "extract_metadata": extractMetadata ?? config.extractMetadata, + "generate_abi": generateAbi ?? config.generateAbi + ] + if let options = stripOptions { + body["strip_options"] = try encoder.encode(options) + } + return try await post("/compile", body: body) + } + + func get(_ path: String) async throws -> T { + guard !closed else { + throw CompilerError("Client has been closed", code: "CLIENT_CLOSED") + } + + var request = URLRequest(url: URL(string: config.endpoint + path)!) + request.httpMethod = "GET" + request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version") + + let (data, response) = try await session.data(for: request) + return try handleResponse(data: data, response: response) + } + + func post(_ path: String, body: [String: Any]) async throws -> T { + guard !closed else { + throw CompilerError("Client has been closed", code: "CLIENT_CLOSED") + } + + var request = URLRequest(url: URL(string: config.endpoint + path)!) + request.httpMethod = "POST" + request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version") + request.httpBody = try JSONSerialization.data(withJSONObject: body) + + let (data, response) = try await session.data(for: request) + return try handleResponse(data: data, response: response) + } + + private func handleResponse(data: Data, response: URLResponse) throws -> T { + guard let httpResponse = response as? HTTPURLResponse else { + throw CompilerError("Invalid response") + } + + if httpResponse.statusCode >= 400 { + if let error = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { + throw CompilerError( + error["message"] as? String ?? "HTTP \(httpResponse.statusCode)", + code: error["code"] as? String, + httpStatus: httpResponse.statusCode + ) + } + throw CompilerError("HTTP \(httpResponse.statusCode)", httpStatus: httpResponse.statusCode) + } + + return try decoder.decode(T.self, from: data) + } +} + +// MARK: - Sub-Clients + +public actor ContractsClient { + private var compiler: SynorCompiler? + + func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler } + + public func compile(_ wasm: Data) async throws -> CompilationResult { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + return try await compiler.compile(wasm: wasm) + } + + public func compileDev(_ wasm: Data) async throws -> CompilationResult { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + return try await compiler.compile(wasm: wasm, optimizationLevel: .none, useWasmOpt: false, validate: true) + } + + public func compileProduction(_ wasm: Data) async throws -> CompilationResult { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + return try await compiler.compile(wasm: wasm, optimizationLevel: .aggressive, useWasmOpt: true, validate: true, extractMetadata: true, generateAbi: true) + } + + public func get(_ contractId: String) async throws -> CompilationResult { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + return try await compiler.get("/contracts/\(contractId)") + } +} + +public actor AbiClient { + private var compiler: SynorCompiler? + + func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler } + + public func extract(_ wasm: Data) async throws -> ContractAbi { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + let wasmBase64 = wasm.base64EncodedString() + return try await compiler.post("/abi/extract", body: ["wasm": wasmBase64]) + } + + public func get(_ contractId: String) async throws -> ContractAbi { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + return try await compiler.get("/contracts/\(contractId)/abi") + } +} + +public actor AnalysisClient { + private var compiler: SynorCompiler? + + func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler } + + public func analyze(_ wasm: Data) async throws -> ContractAnalysis { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + let wasmBase64 = wasm.base64EncodedString() + return try await compiler.post("/analysis", body: ["wasm": wasmBase64]) + } + + public func get(_ contractId: String) async throws -> ContractAnalysis { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + return try await compiler.get("/contracts/\(contractId)/analysis") + } + + public func extractMetadata(_ wasm: Data) async throws -> ContractMetadata { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + let wasmBase64 = wasm.base64EncodedString() + return try await compiler.post("/analysis/metadata", body: ["wasm": wasmBase64]) + } +} + +public actor ValidationClient { + private var compiler: SynorCompiler? + + func setCompiler(_ compiler: SynorCompiler) { self.compiler = compiler } + + public func validate(_ wasm: Data) async throws -> ValidationResult { + guard let compiler = compiler else { throw CompilerError("Compiler not set") } + let wasmBase64 = wasm.base64EncodedString() + return try await compiler.post("/validate", body: ["wasm": wasmBase64]) + } + + public func isValid(_ wasm: Data) async throws -> Bool { + try await validate(wasm).valid + } + + public func getErrors(_ wasm: Data) async throws -> [String] { + try await validate(wasm).errors.map { $0.message } + } +} + +// MARK: - Constants + +public let MAX_CONTRACT_SIZE = 256 * 1024 +public let MAX_MEMORY_PAGES = 16