{"sources":{"test/trial/TrialVenue.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 IStackerMin {\n    function credit(address trader, uint256 imdAmount) external returns (uint256 shares);\n    function SIMD() external view returns (address);\n}\n\n/// @title TrialVenue\n/// @notice The positive case for the imd/acc ProjectRegistry trial on Sepolia: a small trading venue\n/// that follows every listing rule. It is a constant-product pool between IMD and its own token,\n/// TRIAL (this contract), and its liquidity can never be withdrawn by anyone. Every trade pays a 1%\n/// fee on its IMD side: 0.5% is the trader's cashback, credited as staked IMD through the imd/acc\n/// Stacker, and 0.5% is burned (sent to 0x...dEaD). Nothing goes to the owner, who can only seed the\n/// 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 TrialVenue {\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    IStackerMin public immutable stacker;\n    address public immutable SIMD;\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(IERC20Min imd, IStackerMin stacker_, uint256 creditGas_, uint256 gasReserve_) {\n        IMD = imd;\n        stacker = stacker_;\n        SIMD = stacker_.SIMD();\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 == SIMD || 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"}},"metadata":{"compiler":{"version":"0.8.26+commit.8a97fa7a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"contract IERC20Min","name":"imd","type":"address"},{"internalType":"contract IStackerMin","name":"stacker_","type":"address"},{"internalType":"uint256","name":"creditGas_","type":"uint256"},{"internalType":"uint256","name":"gasReserve_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadySeeded","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"NotSeeded","type":"error"},{"inputs":[],"name":"Reentrant","type":"error"},{"inputs":[],"name":"Slippage","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"imd","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"trial","type":"uint256"}],"name":"Seeded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"bool","name":"buy","type":"bool"},{"indexed":false,"internalType":"uint256","name":"imd","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"trial","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cashback","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"burned","type":"uint256"}],"name":"Trade","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BURN_BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CASHBACK_BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"IMD","outputs":[{"internalType":"contract IERC20Min","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIMD","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRIAL_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"imdIn","type":"uint256"},{"internalType":"uint256","name":"minTrialOut","type":"uint256"}],"name":"buy","outputs":[{"internalType":"uint256","name":"trialOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"creditGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasReserve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reserveImd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reserveTrial","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"resolveTrader","outputs":[{"internalType":"address","name":"trader","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"imdAmount","type":"uint256"}],"name":"seed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"seeded","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"trialIn","type":"uint256"},{"internalType":"uint256","name":"minImdOut","type":"uint256"}],"name":"sell","outputs":[{"internalType":"uint256","name":"imdOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stacker","outputs":[{"internalType":"contract IStackerMin","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"devdoc":{"kind":"dev","methods":{},"title":"TrialVenue","version":1},"userdoc":{"kind":"user","methods":{"buy(uint256,uint256)":{"notice":"Pay `imdIn` IMD for TRIAL. 1% of `imdIn` is the fee: half cashback, half burned."},"creditGas()":{"notice":"Gas forwarded to `credit`, and the gas kept for the rest of the trade."},"owner()":{"notice":"The only power the owner has: seeding the pool once."},"resolveTrader(address)":{"notice":"The trader of a trade made by `who`: `who` itself when it has no code, else the active caller its router reports through `msgSender()` (bounded gas, at most 32 bytes read). Zero when no valid trader can be resolved."},"seed(uint256)":{"notice":"Locks `imdAmount` IMD and the whole TRIAL supply into the pool, once. There is no way to take liquidity out."},"sell(uint256,uint256)":{"notice":"Sell `trialIn` TRIAL for IMD. 1% of the IMD paid out is the fee: half cashback, half burned."}},"notice":"The positive case for the imd/acc ProjectRegistry trial on Sepolia: a small trading venue that follows every listing rule. It is a constant-product pool between IMD and its own token, TRIAL (this contract), and its liquidity can never be withdrawn by anyone. Every trade pays a 1% fee on its IMD side: 0.5% is the trader's cashback, credited as staked IMD through the imd/acc Stacker, and 0.5% is burned (sent to 0x...dEaD). Nothing goes to the owner, who can only seed the pool once. The trader is the caller when it is a plain wallet, else the active caller its router reports through `msgSender()`; a caller that resolves to no valid trader gets no cashback, and that cashback is burned instead (never paid to a router). If `credit` fails, the trader is paid the cashback in plain IMD. The gas for `credit` is checked before the call.","version":1}},"settings":{"compilationTarget":{"test/trial/TrialVenue.sol":"TrialVenue"},"evmVersion":"cancun","libraries":{},"metadata":{"bytecodeHash":"none"},"optimizer":{"enabled":true,"runs":10000},"remappings":[":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",":forge-std/=lib/forge-std/src/",":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",":solady/=lib/solady/src/"]},"sources":{"test/trial/TrialVenue.sol":{"keccak256":"0x56d09fe3ee134ef67649a9a9e57d70f2281edf2ab866773259e7e8953f4b3630","license":"MIT","urls":["bzz-raw://5329d71b39dd71f7b14ed34442fe65f86f2eb6053312ff981445641c2e277c94","dweb:/ipfs/QmQdyg2GgXwCvXrz6TPmyzTYJ6FMVARmW4Ww6787rBed3W"]}},"version":1},"matchId":"56004145","creationMatch":"match","runtimeMatch":"match","verifiedAt":"2026-10-12T17:07:16Z","match":"match","chainId":"11155111","address":"0xF2520B379021AE989Bb0eBBD350a0c8571256FEF"}