{"sources":{"test/trial/RemoteTrialVenue.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\ninterface IERC20Min {\n    function transfer(address to, uint256 amount) external returns (bool);\n    function transferFrom(address from, address to, uint256 amount) external returns (bool);\n    function approve(address spender, uint256 amount) external returns (bool);\n}\n\ninterface IRemoteStackerMin {\n    function credit(address trader, uint256 imdAmount) external returns (uint256 queued);\n    function VAULT() external view returns (address);\n    function RECEIVER() external view returns (address);\n}\n\n/// @title RemoteTrialVenue\n/// @notice The positive case for the imd/acc ProjectRegistry trial on Robinhood Chain: the Sepolia\n/// trials' TrialVenue, crediting through the RemoteStacker instead of the Stacker. A small\n/// constant-product pool between IMD and its own token, TRIAL (this contract), whose liquidity can\n/// never be withdrawn by anyone. Every trade pays a 1% fee on its IMD side: 0.5% is the trader's\n/// cashback, credited through the RemoteStacker (where it waits for a flush to Ethereum and reaches\n/// the trader as staked IMD), and 0.5% is burned (sent to 0x...dEaD). Nothing goes to the owner,\n/// who can only seed the pool once.\n///\n/// The trader is the caller when it is a plain wallet, else the active caller its router reports\n/// through `msgSender()`; a caller that resolves to no valid trader gets no cashback, and that\n/// cashback is burned instead (never paid to a router). If `credit` fails, the trader is paid the\n/// cashback in plain IMD. The gas for `credit` is checked before the call.\ncontract RemoteTrialVenue {\n    string public constant name = \"imd/acc trial token\";\n    string public constant symbol = \"TRIAL\";\n    uint8 public constant decimals = 18;\n\n    uint256 public constant BPS = 10_000;\n    uint256 public constant FEE_BPS = 100; // 1% of each trade's IMD side\n    uint256 public constant CASHBACK_BPS = 50; // to the trader, as staked IMD\n    uint256 public constant BURN_BPS = 50; // burned\n    address public constant BURN = 0x000000000000000000000000000000000000dEaD;\n    uint256 public constant TRIAL_SUPPLY = 1_000_000e18;\n\n    IERC20Min public immutable IMD;\n    IRemoteStackerMin public immutable stacker;\n    /// @notice The Ethereum vault and receiver the RemoteStacker names, refused as traders.\n    address public immutable VAULT;\n    address public immutable RECEIVER;\n    /// @notice The only power the owner has: seeding the pool once.\n    address public immutable owner;\n    /// @notice Gas forwarded to `credit`, and the gas kept for the rest of the trade.\n    uint256 public immutable creditGas;\n    uint256 public immutable gasReserve;\n\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    uint256 public reserveImd;\n    uint256 public reserveTrial;\n    bool public seeded;\n    uint256 private _lock = 1;\n\n    event Transfer(address indexed from, address indexed to, uint256 amount);\n    event Approval(address indexed owner, address indexed spender, uint256 amount);\n    event Seeded(uint256 imd, uint256 trial);\n    event Trade(\n        address indexed caller,\n        address indexed trader,\n        bool buy,\n        uint256 imd,\n        uint256 trial,\n        uint256 cashback,\n        uint256 burned\n    );\n\n    error NotOwner();\n    error AlreadySeeded();\n    error NotSeeded();\n    error Slippage();\n    error ZeroAmount();\n    error Reentrant();\n\n    modifier nonReentrant() {\n        if (_lock != 1) revert Reentrant();\n        _lock = 2;\n        _;\n        _lock = 1;\n    }\n\n    constructor(\n        IERC20Min imd,\n        IRemoteStackerMin stacker_,\n        uint256 creditGas_,\n        uint256 gasReserve_\n    ) {\n        IMD = imd;\n        stacker = stacker_;\n        VAULT = stacker_.VAULT();\n        RECEIVER = stacker_.RECEIVER();\n        owner = msg.sender;\n        creditGas = creditGas_;\n        gasReserve = gasReserve_;\n        imd.approve(address(stacker_), type(uint256).max);\n        totalSupply = TRIAL_SUPPLY;\n        balanceOf[address(this)] = TRIAL_SUPPLY;\n        emit Transfer(address(0), address(this), TRIAL_SUPPLY);\n    }\n\n    // ------------------------------------------------------------------ pool\n\n    /// @notice Locks `imdAmount` IMD and the whole TRIAL supply into the pool, once. There is no way\n    /// to take liquidity out.\n    function seed(uint256 imdAmount) external {\n        if (msg.sender != owner) revert NotOwner();\n        if (seeded) revert AlreadySeeded();\n        if (imdAmount == 0) revert ZeroAmount();\n        seeded = true;\n        require(IMD.transferFrom(msg.sender, address(this), imdAmount));\n        reserveImd = imdAmount;\n        reserveTrial = TRIAL_SUPPLY;\n        emit Seeded(imdAmount, TRIAL_SUPPLY);\n    }\n\n    /// @notice Pay `imdIn` IMD for TRIAL. 1% of `imdIn` is the fee: half cashback, half burned.\n    function buy(uint256 imdIn, uint256 minTrialOut)\n        external\n        nonReentrant\n        returns (uint256 trialOut)\n    {\n        if (!seeded) revert NotSeeded();\n        if (imdIn == 0) revert ZeroAmount();\n        require(IMD.transferFrom(msg.sender, address(this), imdIn));\n        uint256 fee = imdIn * FEE_BPS / BPS;\n        uint256 net = imdIn - fee;\n        trialOut = reserveTrial * net / (reserveImd + net);\n        if (trialOut == 0 || trialOut < minTrialOut) revert Slippage();\n        reserveImd += net;\n        reserveTrial -= trialOut;\n        _move(address(this), msg.sender, trialOut);\n        _settleFee(imdIn, fee, true, trialOut);\n    }\n\n    /// @notice Sell `trialIn` TRIAL for IMD. 1% of the IMD paid out is the fee: half cashback, half\n    /// burned.\n    function sell(uint256 trialIn, uint256 minImdOut)\n        external\n        nonReentrant\n        returns (uint256 imdOut)\n    {\n        if (!seeded) revert NotSeeded();\n        if (trialIn == 0) revert ZeroAmount();\n        _move(msg.sender, address(this), trialIn);\n        uint256 gross = reserveImd * trialIn / (reserveTrial + trialIn);\n        uint256 fee = gross * FEE_BPS / BPS;\n        imdOut = gross - fee;\n        if (imdOut == 0 || imdOut < minImdOut) revert Slippage();\n        reserveImd -= gross;\n        reserveTrial += trialIn;\n        require(IMD.transfer(msg.sender, imdOut));\n        _settleFee(gross, fee, false, trialIn);\n    }\n\n    /// @dev Splits a trade's fee: the cashback to the resolved trader, the rest burned.\n    function _settleFee(uint256 imd, uint256 fee, bool isBuy, uint256 trial) internal {\n        uint256 cashback = fee * CASHBACK_BPS / FEE_BPS;\n        uint256 burned = fee - cashback;\n        address trader = resolveTrader(msg.sender);\n        if (trader == address(0)) {\n            burned += cashback; // no valid trader: the cashback is burned, never paid to a router\n            cashback = 0;\n        }\n        if (burned > 0) require(IMD.transfer(BURN, burned));\n        if (cashback > 0) {\n            require(gasleft() >= creditGas + gasReserve, \"imd/acc: insufficient gas\");\n            try stacker.credit{gas: creditGas}(trader, cashback) {}\n            catch {\n                require(IMD.transfer(trader, cashback)); // plain IMD: the trade never reverts\n            }\n        }\n        emit Trade(msg.sender, trader, isBuy, imd, trial, cashback, burned);\n    }\n\n    /// @notice The trader of a trade made by `who`: `who` itself when it has no code, else\n    /// the active caller its router reports through `msgSender()` (bounded gas, at most 32 bytes\n    /// read). Zero when no valid trader can be resolved.\n    function resolveTrader(address who) public view returns (address trader) {\n        if (who.code.length == 0) {\n            trader = who;\n        } else {\n            bool ok;\n            uint256 size;\n            uint256 value;\n            assembly (\"memory-safe\") {\n                mstore(0, 0xd737d0c700000000000000000000000000000000000000000000000000000000) // msgSender()\n                ok := staticcall(30000, who, 0, 4, 0, 32)\n                size := returndatasize()\n                value := mload(0)\n            }\n            if (!ok || size != 32 || value > type(uint160).max) return address(0);\n            trader = address(uint160(value));\n        }\n        if (\n            trader == address(0) || trader == address(stacker) || trader == address(IMD)\n                || trader == VAULT || trader == RECEIVER || trader == address(this)\n                || (trader == who && who.code.length != 0)\n        ) return address(0);\n    }\n\n    // ------------------------------------------------------------------ TRIAL token\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        _move(msg.sender, to, amount);\n        return true;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        emit Approval(msg.sender, spender, amount);\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        uint256 a = allowance[from][msg.sender];\n        if (a != type(uint256).max) allowance[from][msg.sender] = a - amount;\n        _move(from, to, amount);\n        return true;\n    }\n\n    function _move(address from, address to, uint256 amount) internal {\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        emit Transfer(from, to, amount);\n    }\n}\n"}},"matchId":"56045453","creationMatch":"match","runtimeMatch":"match","verifiedAt":"2026-10-13T10:59:43Z","match":"match","chainId":"4663","address":"0x81D17894B724dcb462bd5585Be0792b1B32D19CC"}