neo-devpack-solidity 0.22.0

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

/// @title Escrow - Secure escrow service for Neo N3
/// @author Neo DevPack for Solidity Team
/// @notice Holds funds in escrow until conditions are met
/// @dev Supports time-locked releases and multi-party approval
contract Escrow {
    // Escrow states
    enum State { Created, Funded, Released, Refunded, Disputed }

    // Escrow details
    struct EscrowDetails {
        address depositor;
        address beneficiary;
        address arbiter;
        uint256 amount;
        uint256 createdAt;
        uint256 releaseTime;
        State state;
        string description;
    }

    // State variables
    mapping(uint256 => EscrowDetails) public escrows;
    mapping(address => uint256[]) public userEscrows;
    uint256 public escrowCount;
    uint256 public totalEscrowed;
    address public owner;
    uint256 public feePercent; // Fee in basis points (100 = 1%)
    uint256 public collectedFees;

    // Events
    event EscrowCreated(
        uint256 indexed escrowId,
        address indexed depositor,
        address indexed beneficiary,
        uint256 amount,
        uint256 releaseTime
    );
    event EscrowFunded(uint256 indexed escrowId, uint256 amount);
    event EscrowReleased(uint256 indexed escrowId, address indexed beneficiary, uint256 amount);
    event EscrowRefunded(uint256 indexed escrowId, address indexed depositor, uint256 amount);
    event EscrowDisputed(uint256 indexed escrowId, address indexed disputer);
    event DisputeResolved(uint256 indexed escrowId, address indexed winner, uint256 amount);
    event ArbiterAssigned(uint256 indexed escrowId, address indexed arbiter);
    event FeeUpdated(uint256 oldFee, uint256 newFee);

    /// @notice Contract constructor
    /// @param _feePercent Initial fee percentage in basis points
    constructor(uint256 _feePercent) {
        require(_feePercent <= 1000, "Escrow: fee too high"); // Max 10%
        owner = msg.sender;
        feePercent = _feePercent;
        escrowCount = 0;
        totalEscrowed = 0;
        collectedFees = 0;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Escrow: caller is not owner");
        _;
    }

    modifier escrowExists(uint256 escrowId) {
        require(escrowId < escrowCount, "Escrow: escrow does not exist");
        _;
    }

    modifier onlyDepositor(uint256 escrowId) {
        require(msg.sender == escrows[escrowId].depositor, "Escrow: caller is not depositor");
        _;
    }

    modifier onlyArbiter(uint256 escrowId) {
        require(msg.sender == escrows[escrowId].arbiter, "Escrow: caller is not arbiter");
        _;
    }

    /// @notice Create a new escrow
    /// @param beneficiary The address that will receive funds
    /// @param arbiter The address that can resolve disputes
    /// @param releaseTime Unix timestamp when funds can be released
    /// @param description Description of the escrow purpose
    /// @return escrowId The ID of the created escrow
    function createEscrow(
        address beneficiary,
        address arbiter,
        uint256 releaseTime,
        string memory description
    ) public returns (uint256 escrowId) {
        require(beneficiary != address(0), "Escrow: beneficiary is zero address");
        require(beneficiary != msg.sender, "Escrow: cannot escrow to self");
        require(releaseTime > block.timestamp, "Escrow: release time must be in future");

        escrowId = escrowCount;
        escrowCount += 1;

        escrows[escrowId] = EscrowDetails({
            depositor: msg.sender,
            beneficiary: beneficiary,
            arbiter: arbiter,
            amount: 0,
            createdAt: block.timestamp,
            releaseTime: releaseTime,
            state: State.Created,
            description: description
        });

        userEscrows[msg.sender].push(escrowId);
        userEscrows[beneficiary].push(escrowId);

        emit EscrowCreated(escrowId, msg.sender, beneficiary, 0, releaseTime);
        return escrowId;
    }

    /// @notice Fund an existing escrow
    /// @param escrowId The escrow to fund
    /// @param amount The amount to deposit
    function fundEscrow(uint256 escrowId, uint256 amount) public escrowExists(escrowId) onlyDepositor(escrowId) {
        EscrowDetails storage escrow = escrows[escrowId];
        require(escrow.state == State.Created, "Escrow: already funded or closed");
        require(amount > 0, "Escrow: amount must be positive");

        escrow.amount = amount;
        escrow.state = State.Funded;
        totalEscrowed += amount;

        emit EscrowFunded(escrowId, amount);
    }

    /// @notice Release funds to beneficiary
    /// @param escrowId The escrow to release
    function release(uint256 escrowId) public escrowExists(escrowId) {
        EscrowDetails storage escrow = escrows[escrowId];
        require(escrow.state == State.Funded, "Escrow: not funded");
        require(
            msg.sender == escrow.depositor ||
            (block.timestamp >= escrow.releaseTime && msg.sender == escrow.beneficiary),
            "Escrow: not authorized to release"
        );

        uint256 fee = (escrow.amount * feePercent) / 10000;
        uint256 releaseAmount = escrow.amount - fee;

        escrow.state = State.Released;
        totalEscrowed -= escrow.amount;
        collectedFees += fee;

        emit EscrowReleased(escrowId, escrow.beneficiary, releaseAmount);
    }

    /// @notice Refund funds to depositor (before release time)
    /// @param escrowId The escrow to refund
    function refund(uint256 escrowId) public escrowExists(escrowId) {
        EscrowDetails storage escrow = escrows[escrowId];
        require(escrow.state == State.Funded, "Escrow: not funded");
        require(
            msg.sender == escrow.beneficiary ||
            msg.sender == escrow.arbiter,
            "Escrow: not authorized to refund"
        );

        uint256 refundAmount = escrow.amount;
        escrow.state = State.Refunded;
        totalEscrowed -= escrow.amount;

        emit EscrowRefunded(escrowId, escrow.depositor, refundAmount);
    }

    /// @notice Raise a dispute on an escrow
    /// @param escrowId The escrow to dispute
    function dispute(uint256 escrowId) public escrowExists(escrowId) {
        EscrowDetails storage escrow = escrows[escrowId];
        require(escrow.state == State.Funded, "Escrow: not funded");
        require(
            msg.sender == escrow.depositor || msg.sender == escrow.beneficiary,
            "Escrow: not a party to escrow"
        );
        require(escrow.arbiter != address(0), "Escrow: no arbiter assigned");

        escrow.state = State.Disputed;
        emit EscrowDisputed(escrowId, msg.sender);
    }

    /// @notice Resolve a dispute (arbiter only)
    /// @param escrowId The disputed escrow
    /// @param releaseTobeneficiary True to release to beneficiary, false to refund depositor
    function resolveDispute(uint256 escrowId, bool releaseTobeneficiary) public escrowExists(escrowId) onlyArbiter(escrowId) {
        EscrowDetails storage escrow = escrows[escrowId];
        require(escrow.state == State.Disputed, "Escrow: not disputed");

        uint256 amount = escrow.amount;
        totalEscrowed -= amount;

        if (releaseTobeneficiary) {
            uint256 fee = (amount * feePercent) / 10000;
            uint256 releaseAmount = amount - fee;
            collectedFees += fee;
            escrow.state = State.Released;
            emit DisputeResolved(escrowId, escrow.beneficiary, releaseAmount);
        } else {
            escrow.state = State.Refunded;
            emit DisputeResolved(escrowId, escrow.depositor, amount);
        }
    }

    /// @notice Get escrow details
    /// @param escrowId The escrow ID
    /// @return depositor The depositor address
    /// @return beneficiary The beneficiary address
    /// @return amount The escrowed amount
    /// @return state The current state
    function getEscrow(uint256 escrowId) public view escrowExists(escrowId) returns (
        address depositor,
        address beneficiary,
        uint256 amount,
        State state
    ) {
        EscrowDetails storage escrow = escrows[escrowId];
        return (escrow.depositor, escrow.beneficiary, escrow.amount, escrow.state);
    }

    /// @notice Check if escrow can be released
    /// @param escrowId The escrow ID
    /// @return True if releasable
    function canRelease(uint256 escrowId) public view escrowExists(escrowId) returns (bool) {
        EscrowDetails storage escrow = escrows[escrowId];
        return escrow.state == State.Funded && block.timestamp >= escrow.releaseTime;
    }

    /// @notice Get user's escrow IDs
    /// @param user The user address
    /// @return Array of escrow IDs
    function getUserEscrows(address user) public view returns (uint256[] memory) {
        return userEscrows[user];
    }

    /// @notice Update fee percentage (owner only)
    /// @param newFeePercent New fee in basis points
    function setFeePercent(uint256 newFeePercent) public onlyOwner {
        require(newFeePercent <= 1000, "Escrow: fee too high");
        uint256 oldFee = feePercent;
        feePercent = newFeePercent;
        emit FeeUpdated(oldFee, newFeePercent);
    }

    /// @notice Withdraw collected fees (owner only)
    /// @return amount The withdrawn amount
    function withdrawFees() public onlyOwner returns (uint256 amount) {
        amount = collectedFees;
        collectedFees = 0;
        return amount;
    }

    /// @notice Get contract statistics
    /// @return _escrowCount Total escrows created
    /// @return _totalEscrowed Current total escrowed
    /// @return _collectedFees Total fees collected
    function getStats() public view returns (
        uint256 _escrowCount,
        uint256 _totalEscrowed,
        uint256 _collectedFees
    ) {
        return (escrowCount, totalEscrowed, collectedFees);
    }
}