neo-devpack-solidity 0.22.0

Production-focused Solidity-to-NeoVM compilation system
Documentation
# Common Patterns

This section highlights common Solidity design patterns and explains how they are adapted for NeoVM using Neo DevPack for Solidity.

## The Withdrawal Pattern

On Ethereum, pushing funds to another address using `address.transfer()` is discouraged due to reentrancy risks and fixed gas limits. The recommended approach is a "pull" pattern where users call a `withdraw()` function.

On Neo N3, value transfer is handled via NEP-17 token transfers, which inherently use a callback pattern (`onNEP17Payment`).

**Neo Adaptation:**
Because NEP-17 transfers invoke the recipient's callback, reentrancy is still a theoretical possibility on Neo if the recipient is a contract. You should still utilize the Checks-Effects-Interactions pattern.

```solidity
import {NativeCalls} from "@neo/NativeCalls.sol";
import {Runtime} from "@neo/Runtime.sol";

contract PullPayment {
    mapping(address => uint256) public balances;

    function withdraw() public {
        address payee = Runtime.getCallingScriptHash();
        uint256 amount = balances[payee];
        
        require(amount > 0, "No funds to withdraw");
        
        // Effects
        balances[payee] = 0;
        
        // Interactions
        bool success = NativeCalls.gasTransfer(address(this), payee, amount, "");
        require(success, "Transfer failed");
    }
}
```

## Receiving Funds (`onNEP17Payment`)

::: tip 💡 NeoVM Difference
Ethereum contracts use `receive() external payable` or `fallback() external payable` to accept native Ether. **Neo contracts must implement `onNEP17Payment` to receive NEP-17 tokens (including GAS).**
:::

```solidity
import "contracts/NativeCalls.sol";
import "contracts/Syscalls.sol";

contract Treasury {
    uint256 public totalDeposits;

    /// @notice Equivalent to `receive() external payable`
    function onNEP17Payment(address from, uint256 amount, Any calldata data) external {
        from;
        data;
        // Only accept GAS
        require(Syscalls.getCallingScriptHash() == NativeCalls.GAS_CONTRACT, "Only GAS accepted");
        
        totalDeposits += amount;
    }
}
```

## Restricting Access

Access restriction is one of the most common patterns. On Neo, this relies on `Runtime.checkWitness()` rather than `msg.sender`.

```solidity
import {Runtime} from "@neo/Runtime.sol";

contract Owned {
    address public owner;

    constructor() {
        // Initialize owner during deployment
        owner = Runtime.getCallingScriptHash();
    }

    modifier onlyOwner() {
        require(Runtime.checkWitness(owner), "Unauthorized: Must be signed by owner");
        _;
    }

    function changeOwner(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}
```

## Contract Upgrades

Proxy patterns (like Transparent Proxies or UUPS) using `delegatecall` are heavily used on Ethereum. NeoVM blocks `delegatecall` but offers a native upgrade syscall.

**Neo Adaptation:**
Use `ContractManagement.update` to replace the contract bytecode while preserving the storage state.

```solidity
import {ContractManagement} from "@neo/ContractManagement.sol";

contract Upgradeable {
    address public admin;

    constructor() {
        admin = Runtime.getCallingScriptHash();
    }

    function upgrade(bytes memory nefFile, bytes memory manifest) public {
        require(Runtime.checkWitness(admin), "Unauthorized");
        
        // Native in-place upgrade. Storage remains intact.
        ContractManagement.update(nefFile, manifest);
    }
}
```