Adds formal verification DSL, multi-sig contract, and Hardhat plugin: synor-verifier crate: - Verification DSL for contract invariants and properties - SMT solver integration (Z3 backend optional) - Symbolic execution engine for path exploration - Automatic vulnerability detection (reentrancy, overflow, etc.) - 29 tests passing contracts/multi-sig: - M-of-N multi-signature wallet contract - Transaction proposals with timelock - Owner management (add/remove) - Emergency pause functionality - Native token and contract call support apps/hardhat-plugin (@synor/hardhat-plugin): - Network configuration for mainnet/testnet/devnet - Contract deployment with gas estimation - Contract verification on explorer - WASM compilation support - TypeScript type generation - Testing utilities (fork, impersonate, time manipulation) - Synor-specific RPC methods (quantum status, shard info, DAG)
349 lines
9.1 KiB
TypeScript
349 lines
9.1 KiB
TypeScript
/**
|
|
* Synor Provider
|
|
*
|
|
* Custom JSON-RPC provider for Synor blockchain.
|
|
*/
|
|
|
|
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
import { TransactionRequest, TransactionResponse, TransactionReceipt, Log } from "./type-extensions";
|
|
|
|
/**
|
|
* Synor-specific JSON-RPC provider
|
|
*/
|
|
export class SynorProvider {
|
|
private hre: HardhatRuntimeEnvironment;
|
|
private rpcUrl: string;
|
|
|
|
constructor(hre: HardhatRuntimeEnvironment) {
|
|
this.hre = hre;
|
|
this.rpcUrl = this.getNetworkUrl();
|
|
}
|
|
|
|
/**
|
|
* Gets the RPC URL for the current network
|
|
*/
|
|
private getNetworkUrl(): string {
|
|
const network = this.hre.network.config;
|
|
if ("url" in network) {
|
|
return network.url;
|
|
}
|
|
return "http://localhost:8545";
|
|
}
|
|
|
|
/**
|
|
* Makes an RPC call
|
|
*/
|
|
async rpc<T>(method: string, params: any[] = []): Promise<T> {
|
|
const response = await fetch(this.rpcUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: Date.now(),
|
|
method,
|
|
params,
|
|
}),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.error) {
|
|
throw new Error(`RPC Error: ${data.error.message}`);
|
|
}
|
|
|
|
return data.result;
|
|
}
|
|
|
|
/**
|
|
* Gets account balance
|
|
*/
|
|
async getBalance(address: string, blockTag: string = "latest"): Promise<bigint> {
|
|
const result = await this.rpc<string>("eth_getBalance", [address, blockTag]);
|
|
return BigInt(result);
|
|
}
|
|
|
|
/**
|
|
* Gets current block number
|
|
*/
|
|
async getBlockNumber(): Promise<number> {
|
|
const result = await this.rpc<string>("eth_blockNumber");
|
|
return parseInt(result, 16);
|
|
}
|
|
|
|
/**
|
|
* Gets a block by number or hash
|
|
*/
|
|
async getBlock(blockHashOrNumber: string | number, includeTransactions: boolean = false): Promise<any> {
|
|
const method = typeof blockHashOrNumber === "number"
|
|
? "eth_getBlockByNumber"
|
|
: "eth_getBlockByHash";
|
|
|
|
const param = typeof blockHashOrNumber === "number"
|
|
? `0x${blockHashOrNumber.toString(16)}`
|
|
: blockHashOrNumber;
|
|
|
|
return this.rpc(method, [param, includeTransactions]);
|
|
}
|
|
|
|
/**
|
|
* Gets transaction by hash
|
|
*/
|
|
async getTransaction(hash: string): Promise<TransactionResponse | null> {
|
|
const tx = await this.rpc<any>("eth_getTransactionByHash", [hash]);
|
|
|
|
if (!tx) return null;
|
|
|
|
return {
|
|
hash: tx.hash,
|
|
from: tx.from,
|
|
to: tx.to,
|
|
value: BigInt(tx.value || "0"),
|
|
gasLimit: BigInt(tx.gas || "0"),
|
|
gasPrice: BigInt(tx.gasPrice || "0"),
|
|
nonce: parseInt(tx.nonce, 16),
|
|
data: tx.input || "0x",
|
|
blockNumber: tx.blockNumber ? parseInt(tx.blockNumber, 16) : undefined,
|
|
blockHash: tx.blockHash,
|
|
timestamp: undefined, // Would need to fetch block for timestamp
|
|
confirmations: 0, // Would need current block to calculate
|
|
wait: async (confirmations = 1) => this.waitForTransaction(hash, confirmations),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets transaction receipt
|
|
*/
|
|
async getTransactionReceipt(hash: string): Promise<TransactionReceipt | null> {
|
|
const receipt = await this.rpc<any>("eth_getTransactionReceipt", [hash]);
|
|
|
|
if (!receipt) return null;
|
|
|
|
return {
|
|
hash: receipt.transactionHash,
|
|
blockNumber: parseInt(receipt.blockNumber, 16),
|
|
blockHash: receipt.blockHash,
|
|
transactionIndex: parseInt(receipt.transactionIndex, 16),
|
|
from: receipt.from,
|
|
to: receipt.to,
|
|
contractAddress: receipt.contractAddress,
|
|
gasUsed: BigInt(receipt.gasUsed),
|
|
status: parseInt(receipt.status, 16),
|
|
logs: receipt.logs.map((log: any) => this.parseLog(log)),
|
|
events: [], // Would need ABI to parse events
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Waits for transaction confirmation
|
|
*/
|
|
async waitForTransaction(hash: string, confirmations: number = 1): Promise<TransactionReceipt> {
|
|
const timeout = this.hre.config.synor.timeout;
|
|
const startTime = Date.now();
|
|
|
|
while (Date.now() - startTime < timeout) {
|
|
const receipt = await this.getTransactionReceipt(hash);
|
|
|
|
if (receipt) {
|
|
const currentBlock = await this.getBlockNumber();
|
|
const txConfirmations = currentBlock - receipt.blockNumber + 1;
|
|
|
|
if (txConfirmations >= confirmations) {
|
|
return receipt;
|
|
}
|
|
}
|
|
|
|
// Wait before next check
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
|
|
throw new Error(`Transaction ${hash} not confirmed within ${timeout}ms`);
|
|
}
|
|
|
|
/**
|
|
* Sends a transaction
|
|
*/
|
|
async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
|
|
const txData: any = {
|
|
from: tx.from,
|
|
to: tx.to,
|
|
value: tx.value ? `0x${BigInt(tx.value).toString(16)}` : "0x0",
|
|
data: tx.data || "0x",
|
|
};
|
|
|
|
if (tx.gasLimit) {
|
|
txData.gas = `0x${tx.gasLimit.toString(16)}`;
|
|
}
|
|
|
|
if (tx.gasPrice) {
|
|
txData.gasPrice = `0x${tx.gasPrice.toString(16)}`;
|
|
}
|
|
|
|
if (tx.nonce !== undefined) {
|
|
txData.nonce = `0x${tx.nonce.toString(16)}`;
|
|
}
|
|
|
|
const hash = await this.rpc<string>("eth_sendTransaction", [txData]);
|
|
|
|
return {
|
|
hash,
|
|
from: tx.from || "",
|
|
to: tx.to,
|
|
value: BigInt(tx.value || 0),
|
|
gasLimit: BigInt(tx.gasLimit || 0),
|
|
gasPrice: tx.gasPrice || BigInt(0),
|
|
nonce: tx.nonce || 0,
|
|
data: tx.data || "0x",
|
|
confirmations: 0,
|
|
wait: async (confirmations = 1) => this.waitForTransaction(hash, confirmations),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Sends a raw transaction
|
|
*/
|
|
async sendRawTransaction(signedTx: string): Promise<string> {
|
|
return this.rpc<string>("eth_sendRawTransaction", [signedTx]);
|
|
}
|
|
|
|
/**
|
|
* Calls a contract (read-only)
|
|
*/
|
|
async call(tx: TransactionRequest, blockTag: string = "latest"): Promise<string> {
|
|
const txData = {
|
|
from: tx.from,
|
|
to: tx.to,
|
|
data: tx.data,
|
|
value: tx.value ? `0x${BigInt(tx.value).toString(16)}` : undefined,
|
|
};
|
|
|
|
return this.rpc<string>("eth_call", [txData, blockTag]);
|
|
}
|
|
|
|
/**
|
|
* Estimates gas for a transaction
|
|
*/
|
|
async estimateGas(tx: TransactionRequest): Promise<bigint> {
|
|
const txData = {
|
|
from: tx.from,
|
|
to: tx.to,
|
|
data: tx.data,
|
|
value: tx.value ? `0x${BigInt(tx.value).toString(16)}` : undefined,
|
|
};
|
|
|
|
const result = await this.rpc<string>("eth_estimateGas", [txData]);
|
|
return BigInt(result);
|
|
}
|
|
|
|
/**
|
|
* Gets current gas price
|
|
*/
|
|
async getGasPrice(): Promise<bigint> {
|
|
const result = await this.rpc<string>("eth_gasPrice");
|
|
return BigInt(result);
|
|
}
|
|
|
|
/**
|
|
* Gets account nonce
|
|
*/
|
|
async getTransactionCount(address: string, blockTag: string = "pending"): Promise<number> {
|
|
const result = await this.rpc<string>("eth_getTransactionCount", [address, blockTag]);
|
|
return parseInt(result, 16);
|
|
}
|
|
|
|
/**
|
|
* Gets contract code
|
|
*/
|
|
async getCode(address: string, blockTag: string = "latest"): Promise<string> {
|
|
return this.rpc<string>("eth_getCode", [address, blockTag]);
|
|
}
|
|
|
|
/**
|
|
* Gets storage at position
|
|
*/
|
|
async getStorageAt(address: string, position: string, blockTag: string = "latest"): Promise<string> {
|
|
return this.rpc<string>("eth_getStorageAt", [address, position, blockTag]);
|
|
}
|
|
|
|
/**
|
|
* Gets logs matching filter
|
|
*/
|
|
async getLogs(filter: {
|
|
fromBlock?: string | number;
|
|
toBlock?: string | number;
|
|
address?: string | string[];
|
|
topics?: (string | string[] | null)[];
|
|
}): Promise<Log[]> {
|
|
const filterData: any = {};
|
|
|
|
if (filter.fromBlock !== undefined) {
|
|
filterData.fromBlock = typeof filter.fromBlock === "number"
|
|
? `0x${filter.fromBlock.toString(16)}`
|
|
: filter.fromBlock;
|
|
}
|
|
|
|
if (filter.toBlock !== undefined) {
|
|
filterData.toBlock = typeof filter.toBlock === "number"
|
|
? `0x${filter.toBlock.toString(16)}`
|
|
: filter.toBlock;
|
|
}
|
|
|
|
if (filter.address) {
|
|
filterData.address = filter.address;
|
|
}
|
|
|
|
if (filter.topics) {
|
|
filterData.topics = filter.topics;
|
|
}
|
|
|
|
const logs = await this.rpc<any[]>("eth_getLogs", [filterData]);
|
|
return logs.map(log => this.parseLog(log));
|
|
}
|
|
|
|
/**
|
|
* Parses a raw log
|
|
*/
|
|
private parseLog(log: any): Log {
|
|
return {
|
|
blockNumber: parseInt(log.blockNumber, 16),
|
|
blockHash: log.blockHash,
|
|
transactionIndex: parseInt(log.transactionIndex, 16),
|
|
transactionHash: log.transactionHash,
|
|
logIndex: parseInt(log.logIndex, 16),
|
|
address: log.address,
|
|
topics: log.topics,
|
|
data: log.data,
|
|
};
|
|
}
|
|
|
|
// Synor-specific methods
|
|
|
|
/**
|
|
* Gets quantum signature status
|
|
*/
|
|
async getQuantumStatus(): Promise<{ enabled: boolean; algorithm: string }> {
|
|
return this.rpc("synor_getQuantumStatus");
|
|
}
|
|
|
|
/**
|
|
* Gets shard information
|
|
*/
|
|
async getShardInfo(): Promise<{ shardId: number; totalShards: number }> {
|
|
return this.rpc("synor_getShardInfo");
|
|
}
|
|
|
|
/**
|
|
* Gets DAG block information
|
|
*/
|
|
async getDagBlock(hash: string): Promise<any> {
|
|
return this.rpc("synor_getDagBlock", [hash]);
|
|
}
|
|
|
|
/**
|
|
* Gets pending cross-shard messages
|
|
*/
|
|
async getPendingCrossShardMessages(address: string): Promise<any[]> {
|
|
return this.rpc("synor_getPendingCrossShardMessages", [address]);
|
|
}
|
|
}
|