feat(sdk): Add Compiler SDK for all 12 languages
Implements WASM smart contract compilation, optimization, and ABI generation across JavaScript/TypeScript, Python, Go, Rust, Flutter/Dart, Java, Kotlin, Swift, C, C++, C#/.NET, and Ruby. Features: - Optimization levels: None, Basic, Size, Aggressive - WASM section stripping with customizable options - Contract validation against VM requirements - ABI extraction and generation - Contract metadata handling - Gas estimation for deployment - Security analysis and recommendations - Size breakdown analysis Sub-clients: Contracts, ABI, Analysis, Validation
This commit is contained in:
parent
eab599767c
commit
2c534a18bb
21 changed files with 9362 additions and 0 deletions
520
sdk/c/include/synor/compiler.h
Normal file
520
sdk/c/include/synor/compiler.h
Normal file
|
|
@ -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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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 */
|
||||
423
sdk/cpp/include/synor/compiler.hpp
Normal file
423
sdk/cpp/include/synor/compiler.hpp
Normal file
|
|
@ -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 <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <stdexcept>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
|
||||
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<std::string>& code = std::nullopt,
|
||||
const std::optional<int>& status = std::nullopt)
|
||||
: std::runtime_error(message), code_(code), status_(status) {}
|
||||
|
||||
const std::optional<std::string>& code() const { return code_; }
|
||||
const std::optional<int>& status() const { return status_; }
|
||||
|
||||
private:
|
||||
std::optional<std::string> code_;
|
||||
std::optional<int> 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<std::string> 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<StripOptions> 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<std::string> location;
|
||||
};
|
||||
|
||||
/** Validation result */
|
||||
struct ValidationResult {
|
||||
bool valid;
|
||||
std::vector<ValidationError> errors;
|
||||
std::vector<std::string> warnings;
|
||||
size_t export_count = 0;
|
||||
size_t import_count = 0;
|
||||
size_t function_count = 0;
|
||||
std::pair<uint32_t, std::optional<uint32_t>> memory_pages = {0, std::nullopt};
|
||||
};
|
||||
|
||||
/** Contract metadata */
|
||||
struct ContractMetadata {
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> version;
|
||||
std::vector<std::string> authors;
|
||||
std::optional<std::string> description;
|
||||
std::optional<std::string> license;
|
||||
std::optional<std::string> repository;
|
||||
std::optional<uint64_t> build_timestamp;
|
||||
std::optional<std::string> rust_version;
|
||||
std::optional<std::string> sdk_version;
|
||||
std::map<std::string, std::string> custom;
|
||||
};
|
||||
|
||||
/** Type information */
|
||||
struct TypeInfo {
|
||||
std::string kind;
|
||||
std::optional<size_t> size;
|
||||
std::unique_ptr<TypeInfo> element;
|
||||
std::unique_ptr<TypeInfo> inner;
|
||||
std::vector<TypeInfo> elements;
|
||||
std::optional<std::string> name;
|
||||
std::vector<std::pair<std::string, TypeInfo>> 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<ParamAbi> inputs;
|
||||
std::vector<ParamAbi> outputs;
|
||||
bool view = false;
|
||||
bool payable = false;
|
||||
std::optional<std::string> doc;
|
||||
};
|
||||
|
||||
/** Event ABI */
|
||||
struct EventAbi {
|
||||
std::string name;
|
||||
std::string topic;
|
||||
std::vector<ParamAbi> params;
|
||||
std::optional<std::string> doc;
|
||||
};
|
||||
|
||||
/** Error ABI */
|
||||
struct ErrorAbi {
|
||||
std::string name;
|
||||
std::string selector;
|
||||
std::vector<ParamAbi> params;
|
||||
std::optional<std::string> doc;
|
||||
};
|
||||
|
||||
/** Contract ABI */
|
||||
struct ContractAbi {
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::vector<FunctionAbi> functions;
|
||||
std::vector<EventAbi> events;
|
||||
std::vector<ErrorAbi> 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<uint8_t> code;
|
||||
std::string code_hash;
|
||||
size_t original_size;
|
||||
size_t optimized_size;
|
||||
double size_reduction;
|
||||
std::optional<ContractMetadata> metadata;
|
||||
std::optional<ContractAbi> abi;
|
||||
uint64_t estimated_deploy_gas = 0;
|
||||
std::optional<ValidationResult> validation;
|
||||
std::vector<std::string> 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<std::string> signature;
|
||||
};
|
||||
|
||||
/** Security issue */
|
||||
struct SecurityIssue {
|
||||
std::string severity;
|
||||
std::string type;
|
||||
std::string description;
|
||||
std::optional<std::string> location;
|
||||
};
|
||||
|
||||
/** Security analysis */
|
||||
struct SecurityAnalysis {
|
||||
int score = 0;
|
||||
std::vector<SecurityIssue> issues;
|
||||
std::vector<std::string> recommendations;
|
||||
};
|
||||
|
||||
/** Gas analysis */
|
||||
struct GasAnalysis {
|
||||
uint64_t deployment_gas = 0;
|
||||
std::map<std::string, uint64_t> function_gas;
|
||||
uint64_t memory_init_gas = 0;
|
||||
uint64_t data_section_gas = 0;
|
||||
};
|
||||
|
||||
/** Contract analysis */
|
||||
struct ContractAnalysis {
|
||||
SizeBreakdown size_breakdown;
|
||||
std::vector<FunctionAnalysis> functions;
|
||||
std::vector<ImportAnalysis> imports;
|
||||
std::optional<SecurityAnalysis> security;
|
||||
std::optional<GasAnalysis> gas_analysis;
|
||||
};
|
||||
|
||||
/** Compilation request */
|
||||
struct CompilationRequest {
|
||||
std::optional<OptimizationLevel> optimization_level;
|
||||
std::optional<StripOptions> strip_options;
|
||||
std::optional<bool> use_wasm_opt;
|
||||
std::optional<bool> validate;
|
||||
std::optional<bool> extract_metadata;
|
||||
std::optional<bool> generate_abi;
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* Client Classes
|
||||
* ============================================================================ */
|
||||
|
||||
class SynorCompiler;
|
||||
|
||||
/** Contracts sub-client */
|
||||
class ContractsClient {
|
||||
public:
|
||||
explicit ContractsClient(SynorCompiler& compiler) : compiler_(compiler) {}
|
||||
|
||||
std::future<CompilationResult> compile(const std::vector<uint8_t>& wasm,
|
||||
const std::optional<CompilationRequest>& request = std::nullopt);
|
||||
std::future<CompilationResult> compile_dev(const std::vector<uint8_t>& wasm);
|
||||
std::future<CompilationResult> compile_production(const std::vector<uint8_t>& wasm);
|
||||
std::future<CompilationResult> get(const std::string& contract_id);
|
||||
std::future<std::vector<CompilationResult>> list(std::optional<int> limit = std::nullopt,
|
||||
std::optional<int> offset = std::nullopt);
|
||||
std::future<std::vector<uint8_t>> get_code(const std::string& contract_id);
|
||||
|
||||
private:
|
||||
SynorCompiler& compiler_;
|
||||
};
|
||||
|
||||
/** ABI sub-client */
|
||||
class AbiClient {
|
||||
public:
|
||||
explicit AbiClient(SynorCompiler& compiler) : compiler_(compiler) {}
|
||||
|
||||
std::future<ContractAbi> extract(const std::vector<uint8_t>& wasm);
|
||||
std::future<ContractAbi> get(const std::string& contract_id);
|
||||
std::future<std::string> encode_call(const FunctionAbi& func, const std::vector<std::string>& args);
|
||||
std::future<std::vector<std::string>> 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<ContractAnalysis> analyze(const std::vector<uint8_t>& wasm);
|
||||
std::future<ContractAnalysis> get(const std::string& contract_id);
|
||||
std::future<ContractMetadata> extract_metadata(const std::vector<uint8_t>& wasm);
|
||||
std::future<uint64_t> estimate_deploy_gas(const std::vector<uint8_t>& wasm);
|
||||
std::future<SecurityAnalysis> security_scan(const std::vector<uint8_t>& wasm);
|
||||
|
||||
private:
|
||||
SynorCompiler& compiler_;
|
||||
};
|
||||
|
||||
/** Validation sub-client */
|
||||
class ValidationClient {
|
||||
public:
|
||||
explicit ValidationClient(SynorCompiler& compiler) : compiler_(compiler) {}
|
||||
|
||||
std::future<ValidationResult> validate(const std::vector<uint8_t>& wasm);
|
||||
std::future<bool> is_valid(const std::vector<uint8_t>& wasm);
|
||||
std::future<std::vector<std::string>> get_errors(const std::vector<uint8_t>& wasm);
|
||||
std::future<bool> validate_exports(const std::vector<uint8_t>& wasm,
|
||||
const std::vector<std::string>& required_exports);
|
||||
std::future<bool> validate_memory(const std::vector<uint8_t>& 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<bool> health_check();
|
||||
|
||||
/** Get service info */
|
||||
std::future<std::map<std::string, std::string>> get_info();
|
||||
|
||||
/** Close the client */
|
||||
void close();
|
||||
|
||||
/** Check if client is closed */
|
||||
bool is_closed() const { return closed_; }
|
||||
|
||||
/** Compile WASM bytecode */
|
||||
std::future<CompilationResult> compile(const std::vector<uint8_t>& wasm,
|
||||
const std::optional<CompilationRequest>& 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<std::map<std::string, std::string>> get(const std::string& path);
|
||||
std::future<std::map<std::string, std::string>> post(const std::string& path,
|
||||
const std::map<std::string, std::string>& 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
|
||||
325
sdk/csharp/src/Synor.Compiler/SynorCompiler.cs
Normal file
325
sdk/csharp/src/Synor.Compiler/SynorCompiler.cs
Normal file
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Synor Compiler SDK for C#/.NET
|
||||
///
|
||||
/// Smart contract compilation, optimization, and ABI generation.
|
||||
/// </summary>
|
||||
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<bool> HealthCheckAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await GetAsync<Dictionary<string, object>>("/health", ct);
|
||||
return result.TryGetValue("status", out var status) && status?.ToString() == "healthy";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> GetInfoAsync(CancellationToken ct = default) =>
|
||||
GetAsync<Dictionary<string, object>>("/info", ct);
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
|
||||
public bool IsClosed => _closed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task<CompilationResult> CompileAsync(
|
||||
byte[] wasm,
|
||||
CompilationRequest? request = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
["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<CompilationResult>("/compile", body, ct);
|
||||
}
|
||||
|
||||
internal async Task<T> GetAsync<T>(string path, CancellationToken ct = default)
|
||||
{
|
||||
CheckClosed();
|
||||
var response = await _httpClient.GetAsync(path, ct);
|
||||
return await HandleResponseAsync<T>(response);
|
||||
}
|
||||
|
||||
internal async Task<T> PostAsync<T>(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<T>(response);
|
||||
}
|
||||
|
||||
private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response)
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = JsonSerializer.Deserialize<Dictionary<string, object>>(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<T>(json, _jsonOptions)!;
|
||||
}
|
||||
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (_closed) throw new CompilerException("Client has been closed", "CLIENT_CLOSED");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Contracts sub-client</summary>
|
||||
public class ContractsClient
|
||||
{
|
||||
private readonly SynorCompiler _compiler;
|
||||
|
||||
internal ContractsClient(SynorCompiler compiler) => _compiler = compiler;
|
||||
|
||||
public Task<CompilationResult> CompileAsync(byte[] wasm, CompilationRequest? request = null, CancellationToken ct = default) =>
|
||||
_compiler.CompileAsync(wasm, request, ct);
|
||||
|
||||
public Task<CompilationResult> CompileDevAsync(byte[] wasm, CancellationToken ct = default) =>
|
||||
_compiler.CompileAsync(wasm, new CompilationRequest
|
||||
{
|
||||
OptimizationLevel = OptimizationLevel.None,
|
||||
UseWasmOpt = false,
|
||||
Validate = true
|
||||
}, ct);
|
||||
|
||||
public Task<CompilationResult> 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<CompilationResult> GetAsync(string contractId, CancellationToken ct = default) =>
|
||||
_compiler.GetAsync<CompilationResult>($"/contracts/{contractId}", ct);
|
||||
|
||||
public async Task<List<CompilationResult>> ListAsync(int? limit = null, int? offset = null, CancellationToken ct = default)
|
||||
{
|
||||
var path = "/contracts";
|
||||
var qs = new List<string>();
|
||||
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<ContractsListResponse>(path, ct);
|
||||
return result.Contracts ?? new List<CompilationResult>();
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetCodeAsync(string contractId, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _compiler.GetAsync<CodeResponse>($"/contracts/{contractId}/code", ct);
|
||||
return Convert.FromBase64String(result.Code);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>ABI sub-client</summary>
|
||||
public class AbiClient
|
||||
{
|
||||
private readonly SynorCompiler _compiler;
|
||||
|
||||
internal AbiClient(SynorCompiler compiler) => _compiler = compiler;
|
||||
|
||||
public async Task<ContractAbi> ExtractAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
return await _compiler.PostAsync<ContractAbi>("/abi/extract", new { wasm = wasmBase64 }, ct);
|
||||
}
|
||||
|
||||
public Task<ContractAbi> GetAsync(string contractId, CancellationToken ct = default) =>
|
||||
_compiler.GetAsync<ContractAbi>($"/contracts/{contractId}/abi", ct);
|
||||
|
||||
public async Task<string> EncodeCallAsync(FunctionAbi functionAbi, object[] args, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _compiler.PostAsync<DataResponse>("/abi/encode", new
|
||||
{
|
||||
function = functionAbi,
|
||||
args
|
||||
}, ct);
|
||||
return result.Data;
|
||||
}
|
||||
|
||||
public async Task<object[]> DecodeResultAsync(FunctionAbi functionAbi, string data, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _compiler.PostAsync<ValuesResponse>("/abi/decode", new
|
||||
{
|
||||
function = functionAbi,
|
||||
data
|
||||
}, ct);
|
||||
return result.Values ?? Array.Empty<object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Analysis sub-client</summary>
|
||||
public class AnalysisClient
|
||||
{
|
||||
private readonly SynorCompiler _compiler;
|
||||
|
||||
internal AnalysisClient(SynorCompiler compiler) => _compiler = compiler;
|
||||
|
||||
public async Task<ContractAnalysis> AnalyzeAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
return await _compiler.PostAsync<ContractAnalysis>("/analysis", new { wasm = wasmBase64 }, ct);
|
||||
}
|
||||
|
||||
public Task<ContractAnalysis> GetAsync(string contractId, CancellationToken ct = default) =>
|
||||
_compiler.GetAsync<ContractAnalysis>($"/contracts/{contractId}/analysis", ct);
|
||||
|
||||
public async Task<ContractMetadata> ExtractMetadataAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
return await _compiler.PostAsync<ContractMetadata>("/analysis/metadata", new { wasm = wasmBase64 }, ct);
|
||||
}
|
||||
|
||||
public async Task<long> EstimateDeployGasAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
var result = await _compiler.PostAsync<GasResponse>("/analysis/estimate-gas", new { wasm = wasmBase64 }, ct);
|
||||
return result.Gas;
|
||||
}
|
||||
|
||||
public async Task<SecurityAnalysis> SecurityScanAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
var result = await _compiler.PostAsync<SecurityResponse>("/analysis/security", new { wasm = wasmBase64 }, ct);
|
||||
return result.Security!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Validation sub-client</summary>
|
||||
public class ValidationClient
|
||||
{
|
||||
private readonly SynorCompiler _compiler;
|
||||
|
||||
internal ValidationClient(SynorCompiler compiler) => _compiler = compiler;
|
||||
|
||||
public async Task<ValidationResult> ValidateAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
return await _compiler.PostAsync<ValidationResult>("/validate", new { wasm = wasmBase64 }, ct);
|
||||
}
|
||||
|
||||
public async Task<bool> IsValidAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var result = await ValidateAsync(wasm, ct);
|
||||
return result.Valid;
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetErrorsAsync(byte[] wasm, CancellationToken ct = default)
|
||||
{
|
||||
var result = await ValidateAsync(wasm, ct);
|
||||
return result.Errors?.ConvertAll(e => e.Message) ?? new List<string>();
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateExportsAsync(byte[] wasm, List<string> requiredExports, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
var result = await _compiler.PostAsync<ValidResponse>("/validate/exports", new
|
||||
{
|
||||
wasm = wasmBase64,
|
||||
required_exports = requiredExports
|
||||
}, ct);
|
||||
return result.Valid;
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateMemoryAsync(byte[] wasm, int maxPages, CancellationToken ct = default)
|
||||
{
|
||||
var wasmBase64 = Convert.ToBase64String(wasm);
|
||||
var result = await _compiler.PostAsync<ValidResponse>("/validate/memory", new
|
||||
{
|
||||
wasm = wasmBase64,
|
||||
max_pages = maxPages
|
||||
}, ct);
|
||||
return result.Valid;
|
||||
}
|
||||
}
|
||||
|
||||
// Response helper types
|
||||
internal record ContractsListResponse(List<CompilationResult>? 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);
|
||||
345
sdk/csharp/src/Synor.Compiler/Types.cs
Normal file
345
sdk/csharp/src/Synor.Compiler/Types.cs
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Synor.Compiler;
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Optimization level for WASM compilation.</summary>
|
||||
public enum OptimizationLevel
|
||||
{
|
||||
/// <summary>No optimization.</summary>
|
||||
None,
|
||||
/// <summary>Basic optimizations (stripping only).</summary>
|
||||
Basic,
|
||||
/// <summary>Optimize for size (-Os equivalent).</summary>
|
||||
Size,
|
||||
/// <summary>Aggressive optimization (-O3 -Os equivalent).</summary>
|
||||
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
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Strip options for WASM sections.</summary>
|
||||
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<string> PreserveSections { get; init; } = new();
|
||||
public bool StripUnused { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>Compiler configuration.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Compilation request.</summary>
|
||||
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
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Validation error.</summary>
|
||||
public record ValidationError
|
||||
{
|
||||
public string Code { get; init; } = "";
|
||||
public string Message { get; init; } = "";
|
||||
public string? Location { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Validation result.</summary>
|
||||
public record ValidationResult
|
||||
{
|
||||
public bool Valid { get; init; }
|
||||
public List<ValidationError>? Errors { get; init; }
|
||||
public List<string>? 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
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Contract metadata.</summary>
|
||||
public record ContractMetadata
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public List<string>? 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<string, string>? Custom { get; init; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ABI Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Type information.</summary>
|
||||
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<TypeInfo>? Elements { get; init; }
|
||||
public string? Name { get; init; }
|
||||
public List<TypeField>? 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"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Type field for structs.</summary>
|
||||
public record TypeField(string Name, TypeInfo Type);
|
||||
|
||||
/// <summary>Parameter ABI.</summary>
|
||||
public record ParamAbi
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public TypeInfo Type { get; init; } = new();
|
||||
public bool Indexed { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Function ABI.</summary>
|
||||
public record FunctionAbi
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public string Selector { get; init; } = "";
|
||||
public List<ParamAbi>? Inputs { get; init; }
|
||||
public List<ParamAbi>? Outputs { get; init; }
|
||||
public bool View { get; init; }
|
||||
public bool Payable { get; init; }
|
||||
public string? Doc { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Event ABI.</summary>
|
||||
public record EventAbi
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public string Topic { get; init; } = "";
|
||||
public List<ParamAbi>? Params { get; init; }
|
||||
public string? Doc { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Error ABI.</summary>
|
||||
public record ErrorAbi
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public string Selector { get; init; } = "";
|
||||
public List<ParamAbi>? Params { get; init; }
|
||||
public string? Doc { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Contract ABI (Application Binary Interface).</summary>
|
||||
public record ContractAbi
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public string Version { get; init; } = "1.0.0";
|
||||
public List<FunctionAbi>? Functions { get; init; }
|
||||
public List<EventAbi>? Events { get; init; }
|
||||
public List<ErrorAbi>? 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
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Compilation result.</summary>
|
||||
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<string>? Warnings { get; init; }
|
||||
|
||||
public string SizeStats =>
|
||||
$"Original: {OriginalSize} bytes, Optimized: {OptimizedSize} bytes, Reduction: {SizeReduction:F1}%";
|
||||
|
||||
public byte[] DecodeCode() => Convert.FromBase64String(Code);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Analysis Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Size breakdown.</summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>Function analysis.</summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>Import analysis.</summary>
|
||||
public record ImportAnalysis
|
||||
{
|
||||
public string Module { get; init; } = "";
|
||||
public string Name { get; init; } = "";
|
||||
public string Kind { get; init; } = "function";
|
||||
public string? Signature { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Security issue.</summary>
|
||||
public record SecurityIssue
|
||||
{
|
||||
public string Severity { get; init; } = "low";
|
||||
public string Type { get; init; } = "";
|
||||
public string Description { get; init; } = "";
|
||||
public string? Location { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Security analysis.</summary>
|
||||
public record SecurityAnalysis
|
||||
{
|
||||
public int Score { get; init; }
|
||||
public List<SecurityIssue>? Issues { get; init; }
|
||||
public List<string>? Recommendations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Gas analysis.</summary>
|
||||
public record GasAnalysis
|
||||
{
|
||||
public long DeploymentGas { get; init; }
|
||||
public Dictionary<string, long>? FunctionGas { get; init; }
|
||||
public long MemoryInitGas { get; init; }
|
||||
public long DataSectionGas { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Contract analysis result.</summary>
|
||||
public record ContractAnalysis
|
||||
{
|
||||
public SizeBreakdown? SizeBreakdown { get; init; }
|
||||
public List<FunctionAnalysis>? Functions { get; init; }
|
||||
public List<ImportAnalysis>? Imports { get; init; }
|
||||
public SecurityAnalysis? Security { get; init; }
|
||||
public GasAnalysis? GasAnalysis { get; init; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Types
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Compiler exception.</summary>
|
||||
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
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>Compiler constants.</summary>
|
||||
public static class CompilerConstants
|
||||
{
|
||||
public const int MaxContractSize = 256 * 1024;
|
||||
public const int MaxMemoryPages = 16;
|
||||
}
|
||||
390
sdk/flutter/lib/src/compiler/client.dart
Normal file
390
sdk/flutter/lib/src/compiler/client.dart
Normal file
|
|
@ -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<bool> healthCheck() async {
|
||||
try {
|
||||
final result = await _get('/health');
|
||||
return result['status'] == 'healthy';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get service info.
|
||||
Future<Map<String, dynamic>> 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<CompilationResult> compile(
|
||||
List<int> 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<Map<String, dynamic>> _get(String path) async {
|
||||
_checkClosed();
|
||||
|
||||
final response = await _httpClient.get(
|
||||
Uri.parse('${_config.endpoint}$path'),
|
||||
headers: _headers,
|
||||
);
|
||||
|
||||
return _handleResponse(response);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _post(
|
||||
String path, Map<String, dynamic> body) async {
|
||||
_checkClosed();
|
||||
|
||||
final response = await _httpClient.post(
|
||||
Uri.parse('${_config.endpoint}$path'),
|
||||
headers: _headers,
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
|
||||
return _handleResponse(response);
|
||||
}
|
||||
|
||||
Map<String, String> get _headers => {
|
||||
'Authorization': 'Bearer ${_config.apiKey}',
|
||||
'Content-Type': 'application/json',
|
||||
'X-SDK-Version': 'flutter/0.1.0',
|
||||
};
|
||||
|
||||
Map<String, dynamic> _handleResponse(http.Response response) {
|
||||
final body = response.body;
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
Map<String, dynamic> 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<CompilationResult> compile(
|
||||
List<int> 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<CompilationResult> compileDev(List<int> wasm) {
|
||||
return _compiler.compile(
|
||||
wasm,
|
||||
optimizationLevel: OptimizationLevel.none,
|
||||
useWasmOpt: false,
|
||||
validate: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// Compile with production settings.
|
||||
Future<CompilationResult> compileProduction(List<int> wasm) {
|
||||
return _compiler.compile(
|
||||
wasm,
|
||||
optimizationLevel: OptimizationLevel.aggressive,
|
||||
useWasmOpt: true,
|
||||
validate: true,
|
||||
extractMetadata: true,
|
||||
generateAbi: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get a compiled contract by ID.
|
||||
Future<CompilationResult> get(String contractId) async {
|
||||
final result = await _compiler._get('/contracts/$contractId');
|
||||
return CompilationResult.fromJson(result);
|
||||
}
|
||||
|
||||
/// List compiled contracts.
|
||||
Future<List<CompilationResult>> list({int? limit, int? offset}) async {
|
||||
var path = '/contracts';
|
||||
final params = <String>[];
|
||||
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<dynamic>?)
|
||||
?.map((c) => CompilationResult.fromJson(c))
|
||||
.toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
/// Get optimized bytecode for a contract.
|
||||
Future<List<int>> 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<ContractAbi> extract(List<int> 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<ContractAbi> get(String contractId) async {
|
||||
final result = await _compiler._get('/contracts/$contractId/abi');
|
||||
return ContractAbi.fromJson(result);
|
||||
}
|
||||
|
||||
/// Encode function call.
|
||||
Future<String> encodeCall(FunctionAbi functionAbi, List<dynamic> 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<List<dynamic>> 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<ContractAnalysis> analyze(List<int> 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<ContractAnalysis> get(String contractId) async {
|
||||
final result = await _compiler._get('/contracts/$contractId/analysis');
|
||||
return ContractAnalysis.fromJson(result);
|
||||
}
|
||||
|
||||
/// Extract metadata from WASM bytecode.
|
||||
Future<ContractMetadata> extractMetadata(List<int> 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<SourceMap?> extractSourceMap(List<int> 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<int> estimateDeployGas(List<int> wasm) async {
|
||||
final wasmBase64 = base64Encode(wasm);
|
||||
final result =
|
||||
await _compiler._post('/analysis/estimate-gas', {'wasm': wasmBase64});
|
||||
return result['gas'] ?? 0;
|
||||
}
|
||||
|
||||
/// Get security analysis.
|
||||
Future<SecurityAnalysis> securityScan(List<int> 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<ValidationResult> validate(List<int> wasm) async {
|
||||
final wasmBase64 = base64Encode(wasm);
|
||||
final result =
|
||||
await _compiler._post('/validate', {'wasm': wasmBase64});
|
||||
return ValidationResult.fromJson(result);
|
||||
}
|
||||
|
||||
/// Check if WASM is valid.
|
||||
Future<bool> isValid(List<int> wasm) async {
|
||||
final result = await validate(wasm);
|
||||
return result.valid;
|
||||
}
|
||||
|
||||
/// Get validation errors.
|
||||
Future<List<String>> getErrors(List<int> wasm) async {
|
||||
final result = await validate(wasm);
|
||||
return result.errors.map((e) => e.message).toList();
|
||||
}
|
||||
|
||||
/// Validate contract exports.
|
||||
Future<bool> validateExports(
|
||||
List<int> wasm, List<String> 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<bool> validateMemory(List<int> wasm, int maxPages) async {
|
||||
final wasmBase64 = base64Encode(wasm);
|
||||
final result = await _compiler._post('/validate/memory', {
|
||||
'wasm': wasmBase64,
|
||||
'max_pages': maxPages,
|
||||
});
|
||||
return result['valid'] ?? false;
|
||||
}
|
||||
}
|
||||
788
sdk/flutter/lib/src/compiler/types.dart
Normal file
788
sdk/flutter/lib/src/compiler/types.dart
Normal file
|
|
@ -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<String> 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<String, dynamic> 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<String>.from(json['preserve_sections'] ?? []),
|
||||
stripUnused: json['strip_unused'] ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> 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<String, dynamic> json) {
|
||||
return ValidationError(
|
||||
code: json['code'] ?? '',
|
||||
message: json['message'] ?? '',
|
||||
location: json['location'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Validation result.
|
||||
class ValidationResult {
|
||||
final bool valid;
|
||||
final List<ValidationError> errors;
|
||||
final List<String> warnings;
|
||||
final int exportCount;
|
||||
final int importCount;
|
||||
final int functionCount;
|
||||
final List<int?> 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<String, dynamic> json) {
|
||||
return ValidationResult(
|
||||
valid: json['valid'] ?? false,
|
||||
errors: (json['errors'] as List<dynamic>?)
|
||||
?.map((e) => ValidationError.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
warnings: List<String>.from(json['warnings'] ?? []),
|
||||
exportCount: json['export_count'] ?? 0,
|
||||
importCount: json['import_count'] ?? 0,
|
||||
functionCount: json['function_count'] ?? 0,
|
||||
memoryPages: List<int?>.from(json['memory_pages'] ?? [0, null]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Contract metadata.
|
||||
class ContractMetadata {
|
||||
final String? name;
|
||||
final String? version;
|
||||
final List<String> authors;
|
||||
final String? description;
|
||||
final String? license;
|
||||
final String? repository;
|
||||
final int? buildTimestamp;
|
||||
final String? rustVersion;
|
||||
final String? sdkVersion;
|
||||
final Map<String, String> 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<String, dynamic> json) {
|
||||
return ContractMetadata(
|
||||
name: json['name'],
|
||||
version: json['version'],
|
||||
authors: List<String>.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<String, String>.from(json['custom'] ?? {}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Type information.
|
||||
class TypeInfo {
|
||||
final String kind;
|
||||
final int? size;
|
||||
final TypeInfo? element;
|
||||
final TypeInfo? inner;
|
||||
final List<TypeInfo>? elements;
|
||||
final String? name;
|
||||
final List<TypeField>? fields;
|
||||
|
||||
const TypeInfo({
|
||||
required this.kind,
|
||||
this.size,
|
||||
this.element,
|
||||
this.inner,
|
||||
this.elements,
|
||||
this.name,
|
||||
this.fields,
|
||||
});
|
||||
|
||||
factory TypeInfo.fromJson(Map<String, dynamic> 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<dynamic>?)
|
||||
?.map((e) => TypeInfo.fromJson(e))
|
||||
.toList(),
|
||||
name: json['name'],
|
||||
fields: (json['fields'] as List<dynamic>?)
|
||||
?.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<String, dynamic> 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<String, dynamic> 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<ParamAbi> inputs;
|
||||
final List<ParamAbi> 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<String, dynamic> json) {
|
||||
return FunctionAbi(
|
||||
name: json['name'] ?? '',
|
||||
selector: json['selector'] ?? '',
|
||||
inputs: (json['inputs'] as List<dynamic>?)
|
||||
?.map((i) => ParamAbi.fromJson(i))
|
||||
.toList() ??
|
||||
[],
|
||||
outputs: (json['outputs'] as List<dynamic>?)
|
||||
?.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<ParamAbi> params;
|
||||
final String? doc;
|
||||
|
||||
const EventAbi({
|
||||
required this.name,
|
||||
required this.topic,
|
||||
this.params = const [],
|
||||
this.doc,
|
||||
});
|
||||
|
||||
factory EventAbi.fromJson(Map<String, dynamic> json) {
|
||||
return EventAbi(
|
||||
name: json['name'] ?? '',
|
||||
topic: json['topic'] ?? '',
|
||||
params: (json['params'] as List<dynamic>?)
|
||||
?.map((p) => ParamAbi.fromJson(p))
|
||||
.toList() ??
|
||||
[],
|
||||
doc: json['doc'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Error ABI.
|
||||
class ErrorAbi {
|
||||
final String name;
|
||||
final String selector;
|
||||
final List<ParamAbi> params;
|
||||
final String? doc;
|
||||
|
||||
const ErrorAbi({
|
||||
required this.name,
|
||||
required this.selector,
|
||||
this.params = const [],
|
||||
this.doc,
|
||||
});
|
||||
|
||||
factory ErrorAbi.fromJson(Map<String, dynamic> json) {
|
||||
return ErrorAbi(
|
||||
name: json['name'] ?? '',
|
||||
selector: json['selector'] ?? '',
|
||||
params: (json['params'] as List<dynamic>?)
|
||||
?.map((p) => ParamAbi.fromJson(p))
|
||||
.toList() ??
|
||||
[],
|
||||
doc: json['doc'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Contract ABI (Application Binary Interface).
|
||||
class ContractAbi {
|
||||
final String name;
|
||||
final String version;
|
||||
final List<FunctionAbi> functions;
|
||||
final List<EventAbi> events;
|
||||
final List<ErrorAbi> errors;
|
||||
|
||||
const ContractAbi({
|
||||
required this.name,
|
||||
required this.version,
|
||||
this.functions = const [],
|
||||
this.events = const [],
|
||||
this.errors = const [],
|
||||
});
|
||||
|
||||
factory ContractAbi.fromJson(Map<String, dynamic> json) {
|
||||
return ContractAbi(
|
||||
name: json['name'] ?? '',
|
||||
version: json['version'] ?? '1.0.0',
|
||||
functions: (json['functions'] as List<dynamic>?)
|
||||
?.map((f) => FunctionAbi.fromJson(f))
|
||||
.toList() ??
|
||||
[],
|
||||
events: (json['events'] as List<dynamic>?)
|
||||
?.map((e) => EventAbi.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
errors: (json['errors'] as List<dynamic>?)
|
||||
?.map((e) => ErrorAbi.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
FunctionAbi? findFunction(String name) {
|
||||
return functions.cast<FunctionAbi?>().firstWhere(
|
||||
(f) => f?.name == name,
|
||||
orElse: () => null,
|
||||
);
|
||||
}
|
||||
|
||||
FunctionAbi? findBySelector(String selector) {
|
||||
return functions.cast<FunctionAbi?>().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<String> 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<String, dynamic> 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<String>.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<String, dynamic> 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<SourceMapping> mappings;
|
||||
|
||||
const SourceMap({
|
||||
required this.file,
|
||||
this.mappings = const [],
|
||||
});
|
||||
|
||||
factory SourceMap.fromJson(Map<String, dynamic> json) {
|
||||
return SourceMap(
|
||||
file: json['file'] ?? '',
|
||||
mappings: (json['mappings'] as List<dynamic>?)
|
||||
?.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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<SecurityIssue> issues;
|
||||
final List<String> recommendations;
|
||||
|
||||
const SecurityAnalysis({
|
||||
required this.score,
|
||||
this.issues = const [],
|
||||
this.recommendations = const [],
|
||||
});
|
||||
|
||||
factory SecurityAnalysis.fromJson(Map<String, dynamic> json) {
|
||||
return SecurityAnalysis(
|
||||
score: json['score'] ?? 0,
|
||||
issues: (json['issues'] as List<dynamic>?)
|
||||
?.map((i) => SecurityIssue.fromJson(i))
|
||||
.toList() ??
|
||||
[],
|
||||
recommendations: List<String>.from(json['recommendations'] ?? []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gas analysis.
|
||||
class GasAnalysis {
|
||||
final int deploymentGas;
|
||||
final Map<String, int> 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<String, dynamic> json) {
|
||||
return GasAnalysis(
|
||||
deploymentGas: json['deployment_gas'] ?? 0,
|
||||
functionGas: Map<String, int>.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<FunctionAnalysis> functions;
|
||||
final List<ImportAnalysis> 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<String, dynamic> json) {
|
||||
return ContractAnalysis(
|
||||
sizeBreakdown: SizeBreakdown.fromJson(json['size_breakdown'] ?? {}),
|
||||
functions: (json['functions'] as List<dynamic>?)
|
||||
?.map((f) => FunctionAnalysis.fromJson(f))
|
||||
.toList() ??
|
||||
[],
|
||||
imports: (json['imports'] as List<dynamic>?)
|
||||
?.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;
|
||||
483
sdk/go/compiler/client.go
Normal file
483
sdk/go/compiler/client.go
Normal file
|
|
@ -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
|
||||
}
|
||||
382
sdk/go/compiler/types.go
Normal file
382
sdk/go/compiler/types.go
Normal file
|
|
@ -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
|
||||
)
|
||||
329
sdk/java/src/main/java/io/synor/compiler/SynorCompiler.java
Normal file
329
sdk/java/src/main/java/io/synor/compiler/SynorCompiler.java
Normal file
|
|
@ -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:
|
||||
* <pre>{@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");
|
||||
* }</pre>
|
||||
*/
|
||||
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<Boolean> healthCheck() {
|
||||
return get("/health", new TypeToken<Map<String, Object>>() {})
|
||||
.thenApply(result -> "healthy".equals(result.get("status")))
|
||||
.exceptionally(e -> false);
|
||||
}
|
||||
|
||||
public CompletableFuture<Map<String, Object>> getInfo() {
|
||||
return get("/info", new TypeToken<Map<String, Object>>() {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed.set(true);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed.get();
|
||||
}
|
||||
|
||||
public CompletableFuture<CompilationResult> compile(byte[] wasm) {
|
||||
return compile(wasm, null);
|
||||
}
|
||||
|
||||
public CompletableFuture<CompilationResult> compile(byte[] wasm, CompilationRequest request) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
|
||||
Map<String, Object> 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<CompilationResult>() {});
|
||||
}
|
||||
|
||||
<T> CompletableFuture<T> get(String path, TypeToken<T> 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));
|
||||
}
|
||||
|
||||
<T> CompletableFuture<T> post(String path, Object body, TypeToken<T> 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> T handleResponse(HttpResponse<String> response, TypeToken<T> typeToken) {
|
||||
if (response.statusCode() >= 400) {
|
||||
Map<String, Object> error;
|
||||
try {
|
||||
error = gson.fromJson(response.body(), new TypeToken<Map<String, Object>>() {}.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<CompilationResult> compile(byte[] wasm) {
|
||||
return compiler.compile(wasm, null);
|
||||
}
|
||||
|
||||
public CompletableFuture<CompilationResult> compile(byte[] wasm, CompilationRequest request) {
|
||||
return compiler.compile(wasm, request);
|
||||
}
|
||||
|
||||
public CompletableFuture<CompilationResult> compileDev(byte[] wasm) {
|
||||
CompilationRequest request = new CompilationRequest();
|
||||
request.setOptimizationLevel(OptimizationLevel.NONE);
|
||||
request.setUseWasmOpt(false);
|
||||
request.setValidate(true);
|
||||
return compiler.compile(wasm, request);
|
||||
}
|
||||
|
||||
public CompletableFuture<CompilationResult> 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<CompilationResult> get(String contractId) {
|
||||
return compiler.get("/contracts/" + contractId, new TypeToken<CompilationResult>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<List<CompilationResult>> list(Integer limit, Integer offset) {
|
||||
StringBuilder path = new StringBuilder("/contracts");
|
||||
List<String> 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<Map<String, List<CompilationResult>>>() {})
|
||||
.thenApply(result -> result.getOrDefault("contracts", List.of()));
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> getCode(String contractId) {
|
||||
return compiler.get("/contracts/" + contractId + "/code", new TypeToken<Map<String, String>>() {})
|
||||
.thenApply(result -> Base64.getDecoder().decode(result.getOrDefault("code", "")));
|
||||
}
|
||||
}
|
||||
|
||||
class AbiClient {
|
||||
private final SynorCompiler compiler;
|
||||
|
||||
AbiClient(SynorCompiler compiler) {
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
||||
public CompletableFuture<ContractAbi> extract(byte[] wasm) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
return compiler.post("/abi/extract", Map.of("wasm", wasmBase64), new TypeToken<ContractAbi>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<ContractAbi> get(String contractId) {
|
||||
return compiler.get("/contracts/" + contractId + "/abi", new TypeToken<ContractAbi>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<String> encodeCall(FunctionAbi functionAbi, List<Object> args) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("function", functionAbi);
|
||||
body.put("args", args);
|
||||
return compiler.post("/abi/encode", body, new TypeToken<Map<String, String>>() {})
|
||||
.thenApply(result -> result.getOrDefault("data", ""));
|
||||
}
|
||||
|
||||
public CompletableFuture<List<Object>> decodeResult(FunctionAbi functionAbi, String data) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("function", functionAbi);
|
||||
body.put("data", data);
|
||||
return compiler.post("/abi/decode", body, new TypeToken<Map<String, List<Object>>>() {})
|
||||
.thenApply(result -> result.getOrDefault("values", List.of()));
|
||||
}
|
||||
}
|
||||
|
||||
class AnalysisClient {
|
||||
private final SynorCompiler compiler;
|
||||
|
||||
AnalysisClient(SynorCompiler compiler) {
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
||||
public CompletableFuture<ContractAnalysis> analyze(byte[] wasm) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
return compiler.post("/analysis", Map.of("wasm", wasmBase64), new TypeToken<ContractAnalysis>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<ContractAnalysis> get(String contractId) {
|
||||
return compiler.get("/contracts/" + contractId + "/analysis", new TypeToken<ContractAnalysis>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<ContractMetadata> extractMetadata(byte[] wasm) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
return compiler.post("/analysis/metadata", Map.of("wasm", wasmBase64), new TypeToken<ContractMetadata>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Long> estimateDeployGas(byte[] wasm) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
return compiler.post("/analysis/estimate-gas", Map.of("wasm", wasmBase64), new TypeToken<Map<String, Long>>() {})
|
||||
.thenApply(result -> result.getOrDefault("gas", 0L));
|
||||
}
|
||||
|
||||
public CompletableFuture<SecurityAnalysis> securityScan(byte[] wasm) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
return compiler.post("/analysis/security", Map.of("wasm", wasmBase64), new TypeToken<Map<String, SecurityAnalysis>>() {})
|
||||
.thenApply(result -> result.get("security"));
|
||||
}
|
||||
}
|
||||
|
||||
class ValidationClient {
|
||||
private final SynorCompiler compiler;
|
||||
|
||||
ValidationClient(SynorCompiler compiler) {
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
||||
public CompletableFuture<ValidationResult> validate(byte[] wasm) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
return compiler.post("/validate", Map.of("wasm", wasmBase64), new TypeToken<ValidationResult>() {});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> isValid(byte[] wasm) {
|
||||
return validate(wasm).thenApply(ValidationResult::isValid);
|
||||
}
|
||||
|
||||
public CompletableFuture<List<String>> getErrors(byte[] wasm) {
|
||||
return validate(wasm).thenApply(result ->
|
||||
result.getErrors().stream().map(ValidationError::getMessage).toList()
|
||||
);
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> validateExports(byte[] wasm, List<String> requiredExports) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("wasm", wasmBase64);
|
||||
body.put("required_exports", requiredExports);
|
||||
return compiler.post("/validate/exports", body, new TypeToken<Map<String, Boolean>>() {})
|
||||
.thenApply(result -> result.getOrDefault("valid", false));
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> validateMemory(byte[] wasm, int maxPages) {
|
||||
String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("wasm", wasmBase64);
|
||||
body.put("max_pages", maxPages);
|
||||
return compiler.post("/validate/memory", body, new TypeToken<Map<String, Boolean>>() {})
|
||||
.thenApply(result -> result.getOrDefault("valid", false));
|
||||
}
|
||||
}
|
||||
494
sdk/java/src/main/java/io/synor/compiler/Types.java
Normal file
494
sdk/java/src/main/java/io/synor/compiler/Types.java
Normal file
|
|
@ -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<String> 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<String> getPreserveSections() { return preserveSections; }
|
||||
public void setPreserveSections(List<String> preserveSections) { this.preserveSections = preserveSections; }
|
||||
|
||||
public boolean isStripUnused() { return stripUnused; }
|
||||
public void setStripUnused(boolean stripUnused) { this.stripUnused = stripUnused; }
|
||||
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> 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<ValidationError> errors = new ArrayList<>();
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
private int exportCount;
|
||||
private int importCount;
|
||||
private int functionCount;
|
||||
private int[] memoryPages = {0, 0};
|
||||
|
||||
public boolean isValid() { return valid; }
|
||||
public List<ValidationError> getErrors() { return errors; }
|
||||
public List<String> 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<String> authors = new ArrayList<>();
|
||||
private String description;
|
||||
private String license;
|
||||
private String repository;
|
||||
private Long buildTimestamp;
|
||||
private String rustVersion;
|
||||
private String sdkVersion;
|
||||
private Map<String, String> custom = new HashMap<>();
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getVersion() { return version; }
|
||||
public List<String> 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<String, String> getCustom() { return custom; }
|
||||
}
|
||||
|
||||
// ABI Types
|
||||
|
||||
class TypeInfo {
|
||||
private String kind;
|
||||
private Integer size;
|
||||
private TypeInfo element;
|
||||
private TypeInfo inner;
|
||||
private List<TypeInfo> elements;
|
||||
private String name;
|
||||
private List<TypeField> fields;
|
||||
|
||||
public String getKind() { return kind; }
|
||||
public Integer getSize() { return size; }
|
||||
public TypeInfo getElement() { return element; }
|
||||
public TypeInfo getInner() { return inner; }
|
||||
public List<TypeInfo> getElements() { return elements; }
|
||||
public String getName() { return name; }
|
||||
public List<TypeField> 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<ParamAbi> inputs = new ArrayList<>();
|
||||
private List<ParamAbi> outputs = new ArrayList<>();
|
||||
private boolean view;
|
||||
private boolean payable;
|
||||
private String doc;
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getSelector() { return selector; }
|
||||
public List<ParamAbi> getInputs() { return inputs; }
|
||||
public List<ParamAbi> 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<ParamAbi> params = new ArrayList<>();
|
||||
private String doc;
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getTopic() { return topic; }
|
||||
public List<ParamAbi> getParams() { return params; }
|
||||
public String getDoc() { return doc; }
|
||||
}
|
||||
|
||||
class ErrorAbi {
|
||||
private String name;
|
||||
private String selector;
|
||||
private List<ParamAbi> params = new ArrayList<>();
|
||||
private String doc;
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getSelector() { return selector; }
|
||||
public List<ParamAbi> getParams() { return params; }
|
||||
public String getDoc() { return doc; }
|
||||
}
|
||||
|
||||
class ContractAbi {
|
||||
private String name;
|
||||
private String version;
|
||||
private List<FunctionAbi> functions = new ArrayList<>();
|
||||
private List<EventAbi> events = new ArrayList<>();
|
||||
private List<ErrorAbi> errors = new ArrayList<>();
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getVersion() { return version; }
|
||||
public List<FunctionAbi> getFunctions() { return functions; }
|
||||
public List<EventAbi> getEvents() { return events; }
|
||||
public List<ErrorAbi> 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<String> 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<String> 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<SecurityIssue> issues = new ArrayList<>();
|
||||
private List<String> recommendations = new ArrayList<>();
|
||||
|
||||
public int getScore() { return score; }
|
||||
public List<SecurityIssue> getIssues() { return issues; }
|
||||
public List<String> getRecommendations() { return recommendations; }
|
||||
}
|
||||
|
||||
class GasAnalysis {
|
||||
private long deploymentGas;
|
||||
private Map<String, Long> functionGas = new HashMap<>();
|
||||
private long memoryInitGas;
|
||||
private long dataSectionGas;
|
||||
|
||||
public long getDeploymentGas() { return deploymentGas; }
|
||||
public Map<String, Long> getFunctionGas() { return functionGas; }
|
||||
public long getMemoryInitGas() { return memoryInitGas; }
|
||||
public long getDataSectionGas() { return dataSectionGas; }
|
||||
}
|
||||
|
||||
class ContractAnalysis {
|
||||
private SizeBreakdown sizeBreakdown;
|
||||
private List<FunctionAnalysis> functions = new ArrayList<>();
|
||||
private List<ImportAnalysis> imports = new ArrayList<>();
|
||||
private SecurityAnalysis security;
|
||||
private GasAnalysis gasAnalysis;
|
||||
|
||||
public SizeBreakdown getSizeBreakdown() { return sizeBreakdown; }
|
||||
public List<FunctionAnalysis> getFunctions() { return functions; }
|
||||
public List<ImportAnalysis> 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() {}
|
||||
}
|
||||
539
sdk/js/src/compiler/index.ts
Normal file
539
sdk/js/src/compiler/index.ts
Normal file
|
|
@ -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<CompilerConfig, 'stripOptions'> & { 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<boolean> {
|
||||
try {
|
||||
const result = await this.get<{ status: string }>('/health');
|
||||
return result.status === 'healthy';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get service info */
|
||||
async getInfo(): Promise<Record<string, unknown>> {
|
||||
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<CompilationRequest>
|
||||
): Promise<CompilationResult> {
|
||||
const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
|
||||
|
||||
return this.post<CompilationResult>('/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<T>(path: string): Promise<T> {
|
||||
this.checkClosed();
|
||||
const response = await this.request('GET', path);
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
/** Internal POST request */
|
||||
async post<T>(path: string, body?: unknown): Promise<T> {
|
||||
this.checkClosed();
|
||||
const response = await this.request('POST', path, body);
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
private async request(method: string, path: string, body?: unknown): Promise<Response> {
|
||||
const url = `${this.config.endpoint}${path}`;
|
||||
const headers: Record<string, string> = {
|
||||
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<T>(response: Response): Promise<T> {
|
||||
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<string, unknown> = {};
|
||||
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<CompilationRequest>
|
||||
): Promise<CompilationResult> {
|
||||
return this.compiler.compile(wasm, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile with development settings (fast, minimal optimization)
|
||||
*/
|
||||
async compileDev(wasm: Uint8Array | string): Promise<CompilationResult> {
|
||||
return this.compiler.compile(wasm, {
|
||||
optimizationLevel: OptimizationLevel.None,
|
||||
useWasmOpt: false,
|
||||
validate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile with production settings (aggressive optimization)
|
||||
*/
|
||||
async compileProduction(wasm: Uint8Array | string): Promise<CompilationResult> {
|
||||
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<CompilationResult> {
|
||||
return this.compiler.get<CompilationResult>(`/contracts/${contractId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* List compiled contracts
|
||||
*/
|
||||
async list(limit?: number, offset?: number): Promise<CompilationResult[]> {
|
||||
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<Uint8Array> {
|
||||
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<ContractAbi> {
|
||||
const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
|
||||
return this.compiler.post<ContractAbi>('/abi/extract', { wasm: wasmBase64 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ABI for a compiled contract
|
||||
*/
|
||||
async get(contractId: string): Promise<ContractAbi> {
|
||||
return this.compiler.get<ContractAbi>(`/contracts/${contractId}/abi`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode function call
|
||||
*/
|
||||
async encodeCall(functionAbi: FunctionAbi, args: unknown[]): Promise<string> {
|
||||
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<unknown[]> {
|
||||
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<string> {
|
||||
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<ContractAnalysis> {
|
||||
const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
|
||||
return this.compiler.post<ContractAnalysis>('/analysis', { wasm: wasmBase64 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get analysis for a compiled contract
|
||||
*/
|
||||
async get(contractId: string): Promise<ContractAnalysis> {
|
||||
return this.compiler.get<ContractAnalysis>(`/contracts/${contractId}/analysis`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract metadata from WASM bytecode
|
||||
*/
|
||||
async extractMetadata(wasm: Uint8Array | string): Promise<ContractMetadata> {
|
||||
const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
|
||||
return this.compiler.post<ContractMetadata>('/analysis/metadata', { wasm: wasmBase64 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract source map from WASM bytecode
|
||||
*/
|
||||
async extractSourceMap(wasm: Uint8Array | string): Promise<SourceMap | null> {
|
||||
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<number> {
|
||||
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<ContractAnalysis['security']> {
|
||||
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<ValidationResult> {
|
||||
const wasmBase64 = typeof wasm === 'string' ? wasm : this.toBase64(wasm);
|
||||
return this.compiler.post<ValidationResult>('/validate', { wasm: wasmBase64 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WASM is valid
|
||||
*/
|
||||
async isValid(wasm: Uint8Array | string): Promise<boolean> {
|
||||
const result = await this.validate(wasm);
|
||||
return result.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation errors
|
||||
*/
|
||||
async getErrors(wasm: Uint8Array | string): Promise<string[]> {
|
||||
const result = await this.validate(wasm);
|
||||
return result.errors.map((e) => e.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate contract exports
|
||||
*/
|
||||
async validateExports(wasm: Uint8Array | string, requiredExports: string[]): Promise<boolean> {
|
||||
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<boolean> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
514
sdk/js/src/compiler/types.ts
Normal file
514
sdk/js/src/compiler/types.ts
Normal file
|
|
@ -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<StripOptions>;
|
||||
/** 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<StripOptions>;
|
||||
/** 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<string, string>;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* 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<string, number>;
|
||||
/** 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<string, unknown>
|
||||
) {
|
||||
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<CompilerConfig> = {
|
||||
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;
|
||||
}
|
||||
}
|
||||
500
sdk/kotlin/src/main/kotlin/io/synor/compiler/SynorCompiler.kt
Normal file
500
sdk/kotlin/src/main/kotlin/io/synor/compiler/SynorCompiler.kt
Normal file
|
|
@ -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<String> = 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<ValidationError> = emptyList(),
|
||||
val warnings: List<String> = emptyList(),
|
||||
val exportCount: Int = 0,
|
||||
val importCount: Int = 0,
|
||||
val functionCount: Int = 0,
|
||||
val memoryPages: List<Int?> = listOf(0, null)
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContractMetadata(
|
||||
val name: String? = null,
|
||||
val version: String? = null,
|
||||
val authors: List<String> = 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<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TypeInfo(
|
||||
val kind: String,
|
||||
val size: Int? = null,
|
||||
val element: TypeInfo? = null,
|
||||
val inner: TypeInfo? = null,
|
||||
val elements: List<TypeInfo>? = null,
|
||||
val name: String? = null,
|
||||
val fields: List<TypeField>? = 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<ParamAbi> = emptyList(),
|
||||
val outputs: List<ParamAbi> = 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<ParamAbi> = emptyList(),
|
||||
val doc: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ErrorAbi(
|
||||
val name: String,
|
||||
val selector: String,
|
||||
val params: List<ParamAbi> = emptyList(),
|
||||
val doc: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContractAbi(
|
||||
val name: String,
|
||||
val version: String,
|
||||
val functions: List<FunctionAbi> = emptyList(),
|
||||
val events: List<EventAbi> = emptyList(),
|
||||
val errors: List<ErrorAbi> = 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<String> = 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<SecurityIssue> = emptyList(),
|
||||
val recommendations: List<String> = emptyList()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GasAnalysis(
|
||||
val deploymentGas: Long,
|
||||
val functionGas: Map<String, Long> = emptyMap(),
|
||||
val memoryInitGas: Long = 0,
|
||||
val dataSectionGas: Long = 0
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContractAnalysis(
|
||||
val sizeBreakdown: SizeBreakdown,
|
||||
val functions: List<FunctionAnalysis> = emptyList(),
|
||||
val imports: List<ImportAnalysis> = 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<Map<String, String>>("/health")
|
||||
result["status"] == "healthy"
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
suspend fun getInfo(): Map<String, Any> = 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 <reified T> 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 <reified T> 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 <reified T> handleResponse(response: HttpResponse<String>): T {
|
||||
if (response.statusCode() >= 400) {
|
||||
val error = try {
|
||||
json.decodeFromString<Map<String, String>>(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<String, Any?>): 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<CompilationResult> {
|
||||
val params = mutableListOf<String>()
|
||||
limit?.let { params.add("limit=$it") }
|
||||
offset?.let { params.add("offset=$it") }
|
||||
val query = if (params.isNotEmpty()) "?${params.joinToString("&")}" else ""
|
||||
val result: Map<String, List<CompilationResult>> = compiler.get("/contracts$query")
|
||||
return result["contracts"] ?: emptyList()
|
||||
}
|
||||
|
||||
suspend fun getCode(contractId: String): ByteArray {
|
||||
val result: Map<String, String> = 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<Any>): String {
|
||||
val body = buildJsonObject {
|
||||
put("function", Json.encodeToJsonElement(functionAbi))
|
||||
put("args", Json.encodeToJsonElement(args))
|
||||
}
|
||||
val result: Map<String, String> = 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<String, Long> = 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<String, SecurityAnalysis> = 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<String> =
|
||||
validate(wasm).errors.map { it.message }
|
||||
|
||||
suspend fun validateExports(wasm: ByteArray, requiredExports: List<String>): Boolean {
|
||||
val wasmBase64 = Base64.getEncoder().encodeToString(wasm)
|
||||
val body = buildJsonObject {
|
||||
put("wasm", wasmBase64)
|
||||
put("required_exports", Json.encodeToJsonElement(requiredExports))
|
||||
}
|
||||
val result: Map<String, Boolean> = 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<String, Boolean> = compiler.post("/validate/memory", body)
|
||||
return result["valid"] ?: false
|
||||
}
|
||||
}
|
||||
|
||||
// Constants
|
||||
const val MAX_CONTRACT_SIZE = 256 * 1024
|
||||
const val MAX_MEMORY_PAGES = 16
|
||||
70
sdk/python/src/synor_compiler/__init__.py
Normal file
70
sdk/python/src/synor_compiler/__init__.py
Normal file
|
|
@ -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",
|
||||
]
|
||||
378
sdk/python/src/synor_compiler/client.py
Normal file
378
sdk/python/src/synor_compiler/client.py
Normal file
|
|
@ -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)
|
||||
588
sdk/python/src/synor_compiler/types.py
Normal file
588
sdk/python/src/synor_compiler/types.py
Normal file
|
|
@ -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,
|
||||
}
|
||||
643
sdk/ruby/lib/synor/compiler.rb
Normal file
643
sdk/ruby/lib/synor/compiler.rb
Normal file
|
|
@ -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
|
||||
562
sdk/rust/src/compiler/client.rs
Normal file
562
sdk/rust/src/compiler/client.rs
Normal file
|
|
@ -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<AtomicBool>,
|
||||
}
|
||||
|
||||
impl SynorCompiler {
|
||||
/// Create a new compiler client.
|
||||
pub fn new(config: CompilerConfig) -> Result<Self, CompilerError> {
|
||||
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::<serde_json::Value>("/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<serde_json::Value, CompilerError> {
|
||||
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<CompilationRequest>,
|
||||
) -> Result<CompilationResult, CompilerError> {
|
||||
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<T: DeserializeOwned>(&self, path: &str) -> Result<T, CompilerError> {
|
||||
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<T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: serde_json::Value,
|
||||
) -> Result<T, CompilerError> {
|
||||
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<T: DeserializeOwned>(
|
||||
&self,
|
||||
response: reqwest::Response,
|
||||
) -> Result<T, CompilerError> {
|
||||
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<CompilationRequest>,
|
||||
) -> Result<CompilationResult, CompilerError> {
|
||||
self.compiler.compile(wasm, opts).await
|
||||
}
|
||||
|
||||
/// Compile with development settings.
|
||||
pub async fn compile_dev(&self, wasm: &[u8]) -> Result<CompilationResult, CompilerError> {
|
||||
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<CompilationResult, CompilerError> {
|
||||
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<CompilationResult, CompilerError> {
|
||||
self.compiler
|
||||
.get(&format!("/contracts/{}", contract_id))
|
||||
.await
|
||||
}
|
||||
|
||||
/// List compiled contracts.
|
||||
pub async fn list(
|
||||
&self,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
) -> Result<Vec<CompilationResult>, 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<CompilationResult>,
|
||||
}
|
||||
|
||||
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<Vec<u8>, 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<ContractAbi, CompilerError> {
|
||||
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<ContractAbi, CompilerError> {
|
||||
self.compiler
|
||||
.get(&format!("/contracts/{}/abi", contract_id))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Encode function call.
|
||||
pub async fn encode_call(
|
||||
&self,
|
||||
function_abi: &FunctionAbi,
|
||||
args: Vec<serde_json::Value>,
|
||||
) -> Result<String, CompilerError> {
|
||||
#[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<Vec<serde_json::Value>, CompilerError> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Response {
|
||||
values: Vec<serde_json::Value>,
|
||||
}
|
||||
|
||||
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<ContractAnalysis, CompilerError> {
|
||||
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<ContractAnalysis, CompilerError> {
|
||||
self.compiler
|
||||
.get(&format!("/contracts/{}/analysis", contract_id))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Extract metadata from WASM bytecode.
|
||||
pub async fn extract_metadata(&self, wasm: &[u8]) -> Result<ContractMetadata, CompilerError> {
|
||||
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<Option<SourceMap>, CompilerError> {
|
||||
let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Response {
|
||||
source_map: Option<SourceMap>,
|
||||
}
|
||||
|
||||
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<u64, CompilerError> {
|
||||
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<SecurityAnalysis, CompilerError> {
|
||||
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<ValidationResult, CompilerError> {
|
||||
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<bool, CompilerError> {
|
||||
let result = self.validate(wasm).await?;
|
||||
Ok(result.valid)
|
||||
}
|
||||
|
||||
/// Get validation errors.
|
||||
pub async fn get_errors(&self, wasm: &[u8]) -> Result<Vec<String>, 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<String>,
|
||||
) -> Result<bool, CompilerError> {
|
||||
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<bool, CompilerError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
9
sdk/rust/src/compiler/mod.rs
Normal file
9
sdk/rust/src/compiler/mod.rs
Normal file
|
|
@ -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::*;
|
||||
490
sdk/rust/src/compiler/types.rs
Normal file
490
sdk/rust/src/compiler/types.rs
Normal file
|
|
@ -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<String>,
|
||||
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<StripOptions>,
|
||||
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<String>) -> 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<String>) -> 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<OptimizationLevel>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub strip_options: Option<StripOptions>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub use_wasm_opt: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub validate: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extract_metadata: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub generate_abi: Option<bool>,
|
||||
}
|
||||
|
||||
/// Validation error.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ValidationError {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
pub location: Option<String>,
|
||||
}
|
||||
|
||||
/// Validation result.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ValidationResult {
|
||||
pub valid: bool,
|
||||
#[serde(default)]
|
||||
pub errors: Vec<ValidationError>,
|
||||
#[serde(default)]
|
||||
pub warnings: Vec<String>,
|
||||
#[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<u32>),
|
||||
}
|
||||
|
||||
/// Contract metadata.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ContractMetadata {
|
||||
pub name: Option<String>,
|
||||
pub version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub authors: Vec<String>,
|
||||
pub description: Option<String>,
|
||||
pub license: Option<String>,
|
||||
pub repository: Option<String>,
|
||||
pub build_timestamp: Option<u64>,
|
||||
pub rust_version: Option<String>,
|
||||
pub sdk_version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub custom: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// 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<TypeInfo> },
|
||||
#[serde(rename = "fixedArray")]
|
||||
FixedArray { element: Box<TypeInfo>, size: usize },
|
||||
#[serde(rename = "option")]
|
||||
Option { inner: Box<TypeInfo> },
|
||||
#[serde(rename = "tuple")]
|
||||
Tuple { elements: Vec<TypeInfo> },
|
||||
#[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<u8>".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<ParamAbi>,
|
||||
#[serde(default)]
|
||||
pub outputs: Vec<ParamAbi>,
|
||||
#[serde(default)]
|
||||
pub view: bool,
|
||||
#[serde(default)]
|
||||
pub payable: bool,
|
||||
pub doc: Option<String>,
|
||||
}
|
||||
|
||||
/// Event ABI.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EventAbi {
|
||||
pub name: String,
|
||||
pub topic: String,
|
||||
#[serde(default)]
|
||||
pub params: Vec<ParamAbi>,
|
||||
pub doc: Option<String>,
|
||||
}
|
||||
|
||||
/// Error ABI.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ErrorAbi {
|
||||
pub name: String,
|
||||
pub selector: String,
|
||||
#[serde(default)]
|
||||
pub params: Vec<ParamAbi>,
|
||||
pub doc: Option<String>,
|
||||
}
|
||||
|
||||
/// Contract ABI (Application Binary Interface).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ContractAbi {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
#[serde(default)]
|
||||
pub functions: Vec<FunctionAbi>,
|
||||
#[serde(default)]
|
||||
pub events: Vec<EventAbi>,
|
||||
#[serde(default)]
|
||||
pub errors: Vec<ErrorAbi>,
|
||||
}
|
||||
|
||||
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<String, serde_json::Error> {
|
||||
serde_json::to_string_pretty(self)
|
||||
}
|
||||
|
||||
/// Deserialize from JSON.
|
||||
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
|
||||
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<ContractMetadata>,
|
||||
pub abi: Option<ContractAbi>,
|
||||
#[serde(default)]
|
||||
pub estimated_deploy_gas: u64,
|
||||
pub validation: Option<ValidationResult>,
|
||||
#[serde(default)]
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
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<Vec<u8>, 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<String>,
|
||||
}
|
||||
|
||||
/// Source map for debugging.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SourceMap {
|
||||
pub file: String,
|
||||
#[serde(default)]
|
||||
pub mappings: Vec<SourceMapping>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// Security analysis.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SecurityAnalysis {
|
||||
pub score: u8, // 0-100
|
||||
#[serde(default)]
|
||||
pub issues: Vec<SecurityIssue>,
|
||||
#[serde(default)]
|
||||
pub recommendations: Vec<String>,
|
||||
}
|
||||
|
||||
/// Gas analysis.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct GasAnalysis {
|
||||
pub deployment_gas: u64,
|
||||
#[serde(default)]
|
||||
pub function_gas: HashMap<String, u64>,
|
||||
#[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<FunctionAnalysis>,
|
||||
#[serde(default)]
|
||||
pub imports: Vec<ImportAnalysis>,
|
||||
pub security: Option<SecurityAnalysis>,
|
||||
pub gas_analysis: Option<GasAnalysis>,
|
||||
}
|
||||
|
||||
/// Compiler error.
|
||||
#[derive(Debug)]
|
||||
pub struct CompilerError {
|
||||
pub message: String,
|
||||
pub code: Option<String>,
|
||||
pub http_status: Option<u16>,
|
||||
}
|
||||
|
||||
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;
|
||||
590
sdk/swift/Sources/SynorCompiler/SynorCompiler.swift
Normal file
590
sdk/swift/Sources/SynorCompiler/SynorCompiler.swift
Normal file
|
|
@ -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<T: Decodable>(_ 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<T: Decodable>(_ 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<T: Decodable>(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
|
||||
Loading…
Add table
Reference in a new issue