neo-devpack-solidity 0.22.0

Production-focused Solidity-to-NeoVM compilation system
Documentation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/**
 * @title Neo N3 Framework Base
 * @dev Base framework providing Neo N3 blockchain integration for Solidity contracts.
 *
 * This base intentionally avoids exposing fully-dynamic contract call helpers
 * (dynamic target + dynamic method name), because those force full wildcard
 * permissions in the Neo N3 manifest (`{"contract":"*","methods":"*"}`).
 *
 * If you need a public dynamic call surface, use `Framework.sol` instead.
 */

import "./Syscalls.sol";
import "./NativeCalls.sol";
import "../libraries/Neo.sol";
import "../libraries/Storage.sol";
import "../libraries/Runtime.sol";

contract FrameworkBase {
    using Neo for *;
    using Storage for *;
    using Runtime for *;

    // Framework state
    address private _owner;
    bool private _initialized;
    uint256 private _version;

    // Neo N3 integration
    bytes4 private constant NEO_MAGIC = 0x3346454E; // "NEF3"
    uint256 private constant MIN_GAS_LIMIT = 20000000; // 0.2 GAS

    // Events
    event FrameworkInitialized(address indexed owner, uint256 version);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event ContractUpgraded(uint256 indexed oldVersion, uint256 indexed newVersion);
    event EmergencyStop(address indexed caller, string reason);

    // Modifiers
    modifier onlyOwner() {
        require(msg.sender == _owner, "Framework: caller is not the owner");
        _;
    }

    modifier whenInitialized() {
        require(_initialized, "Framework: not initialized");
        _;
    }

    modifier withWitness() {
        require(Runtime.checkWitness(msg.sender), "Framework: invalid witness");
        _;
    }

    modifier withGasLimit(uint256 minGas) {
        require(Runtime.gasLeft() >= minGas, "Framework: insufficient gas");
        _;
    }

    /**
     * @dev Initialize the framework
     */
    constructor() {
        _owner = msg.sender;
        _version = 1;
        _initialized = true;

        emit FrameworkInitialized(_owner, _version);
    }

    /**
     * @dev Get framework version
     */
    function version() public view returns (uint256) {
        return _version;
    }

    /**
     * @dev Get contract owner
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Check if framework is initialized
     */
    function initialized() public view returns (bool) {
        return _initialized;
    }

    /**
     * @dev Transfer ownership of the contract
     */
    function transferOwnership(address newOwner) public onlyOwner withWitness {
        require(newOwner != address(0), "Framework: new owner is the zero address");
        require(newOwner != _owner, "Framework: new owner is the same as current owner");

        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }

    /**
     * @dev Renounce ownership of the contract
     */
    function renounceOwnership() public onlyOwner withWitness {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Upgrade contract version
     */
    function upgradeContract(bytes calldata nef, bytes calldata manifest, uint256 newVersion)
        public
        onlyOwner
        withWitness
        withGasLimit(MIN_GAS_LIMIT)
    {
        require(newVersion > _version, "Framework: version must be higher");
        require(nef.length > 0, "Framework: NEF cannot be empty");
        require(manifest.length > 0, "Framework: manifest cannot be empty");

        uint256 oldVersion = _version;
        _version = newVersion;

        // Call ContractManagement.update
        NativeCalls.updateContract(nef, manifest);

        emit ContractUpgraded(oldVersion, newVersion);
    }

    /**
     * @dev Emergency stop function
     */
    function emergencyStop(string calldata reason) public onlyOwner withWitness {
        // Emit emergency event
        emit EmergencyStop(msg.sender, reason);

        // Optional: Call self-destruct via ContractManagement
        // NativeCalls.destroyContract();
    }

    /**
     * @dev Get current block information.
     * @notice `hash` and `merkleRoot` return zero because Neo N3 does not
     *         expose per-block hash or merkle root via lightweight syscalls.
     *         Use `Syscalls.contractCall(LEDGER_CONTRACT, "getBlock", ...)`
     *         for full block data when needed.
     */
    function getCurrentBlock()
        public
        view
        returns (uint256 index, bytes32 hash, uint256 timestamp, bytes32 merkleRoot)
    {
        index = Syscalls.getCurrentIndex();
        hash = bytes32(0);           // Not available via syscall; see @notice
        timestamp = Syscalls.getTime();
        merkleRoot = bytes32(0);     // Not available via syscall; see @notice
    }

    /**
     * @dev Get transaction information
     */
    function getTransaction(bytes32 txHash)
        public
        view
        returns (bytes32 hash, uint256 nonce, address sender, uint256 gasLimit, uint256 gasPrice)
    {
        // Neo N3 exposes full transaction information via the Ledger native contract,
        // but its shape does not map 1:1 to EVM transaction fields.
        //
        // Provide a minimal, deterministic subset that is safe for diagnostics-style use.
        Syscalls.Transaction memory tx = Syscalls.getTransaction(txHash);
        hash = tx.hash;
        nonce = tx.nonce;
        sender = tx.sender;
        gasLimit = tx.systemFee;
        gasPrice = Neo.getGasPrice();
    }

    /**
     * @dev Get contract balance (GAS)
     */
    function getBalance() public view returns (uint256) {
        return Neo.getGasBalance(address(this));
    }

    /**
     * @dev Get NEO balance
     */
    function getNeoBalance(address account) public view returns (uint256) {
        return Neo.getNeoBalance(account);
    }

    /**
     * @dev Transfer GAS to another address
     */
    function transferGas(address to, uint256 amount) public onlyOwner withWitness {
        require(to != address(0), "Framework: cannot transfer to zero address");
        require(amount > 0, "Framework: amount must be greater than zero");

        bool success = Neo.transferGas(address(this), to, amount);
        require(success, "Framework: GAS transfer failed");
    }

    /**
     * @dev Advanced storage operations
     */
    function setStorageValue(bytes calldata key, bytes calldata value) public onlyOwner {
        Storage.put(key, value);
    }

    function getStorageValue(bytes calldata key) public view returns (bytes memory) {
        return Storage.get(key);
    }

    function deleteStorageValue(bytes calldata key) public onlyOwner {
        Storage.remove(key);
    }

    /**
     * @dev Iterate through storage
     */
    function findStorageValues(bytes calldata prefix) public view returns (bytes[] memory values) {
        Storage.Iterator memory iterator = Storage.find(prefix);
        bytes[] memory temp = new bytes[](100); // Max 100 results
        uint256 count = 0;

        while (iterator.next() && count < 100) {
            temp[count] = iterator.value();
            count++;
        }

        values = new bytes[](count);
        for (uint256 i = 0; i < count; i++) {
            values[i] = temp[i];
        }
    }

    /**
     * @dev Deploy new contract
     */
    function deployContract(bytes calldata nef, bytes calldata manifest)
        public
        onlyOwner
        withWitness
        withGasLimit(MIN_GAS_LIMIT)
        returns (address)
    {
        return NativeCalls.deployContract(nef, manifest);
    }

    /**
     * @dev Get contract information via ContractManagement.getContract
     */
    function getContractInfo(address contractHash)
        public
        view
        returns (string memory name, bytes memory script, bytes memory manifest)
    {
        NativeCalls.ContractState memory state = NativeCalls.getContract(contractHash);
        name = "Contract"; // Neo N3 ContractState does not expose a name field
        script = state.nef;
        manifest = state.manifest;
    }

    /**
     * @dev Verify signature
     */
    function verifySignature(bytes32 hash, bytes calldata publicKey, bytes calldata signature)
        public
        pure
        returns (bool)
    {
        return Neo.verifySignature(hash, publicKey, signature);
    }

    /**
     * @dev Get random number (using Neo's randomness)
     */
    function getRandom() public view returns (uint256) {
        return Neo.getRandom();
    }

    /**
     * @dev Check if account is committee member
     */
    function isCommittee(address account) public view returns (bool) {
        return NativeCalls.isCommittee(account);
    }

    /**
     * @dev Get current committee members
     */
    function getCommittee() public view returns (bytes[] memory) {
        return Neo.getCommittee();
    }

    /**
     * @dev Batch operations for gas efficiency
     */
    function batchStorageOperations(bytes[] calldata keys, bytes[] calldata values, bool[] calldata isDelete)
        public
        onlyOwner
    {
        require(keys.length == values.length, "Framework: array length mismatch");
        require(keys.length == isDelete.length, "Framework: array length mismatch");
        require(keys.length > 0, "Framework: empty arrays");
        require(keys.length <= 100, "Framework: too many operations");

        for (uint256 i = 0; i < keys.length; i++) {
            if (isDelete[i]) {
                Storage.remove(keys[i]);
            } else {
                Storage.put(keys[i], values[i]);
            }
        }
    }

    /**
     * @dev Get gas consumption for operation
     */
    function estimateGas(bytes calldata operation) public view returns (uint256) {
        // Base gas estimation based on operation size.
        uint256 baseGas = 1000000; // 0.01 GAS
        uint256 dataGas = operation.length * 1000; // 0.00001 GAS per byte
        return baseGas + dataGas;
    }

    /**
     * @dev Get current gas price
     */
    function getGasPrice() public view returns (uint256) {
        return Neo.getGasPrice();
    }

    /**
     * @dev Get storage price per byte
     */
    function getStoragePrice() public view returns (uint256) {
        return Neo.getStoragePrice();
    }

    /**
     * @dev Framework diagnostics
     */
    function getDiagnostics()
        public
        view
        returns (
            uint256 currentBlock,
            uint256 gasBalance,
            uint256 neoBalance,
            bool isCommitteeMember
        )
    {
        currentBlock = block.number;
        gasBalance = Neo.getGasBalance(address(this));
        neoBalance = Neo.getNeoBalance(address(this));
        isCommitteeMember = isCommittee(address(this));
    }

    /**
     * @dev Framework metadata
     */
    function getFrameworkInfo()
        public
        pure
        returns (string memory name, string memory version, string memory author, string memory repository)
    {
        return (
            "Neo N3 Solidity Framework",
            "1.0.0",
            "Jimmy <jimmy@r3e.network>",
            "https://github.com/r3e-network/neo-devpack-solidity"
        );
    }
}