TaxCollector

0x086c58400b8708ef993f256e12e752dcf0ac918e

Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
3,916 bytes
ABI entries
25
12 read · 2 write
License
none

Contract information

Address
0x086c58400b8708ef993f256e12e752dcf0ac918e
Chain
Robinhood Chain (4663)
Compiler
v0.8.30+commit.73712a01
Optimization
Enabled
Creation tx
0xfcd9083b8a…dfe66a53b2

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (12)

net()address
oracle()address
pTeam()address
pairContract()address
pendingNet()uint256
router()address
teamBps()uint256
teamWallet()address
treasury()address
treasuryBps()uint256
usdg()address
wired()bool

Events (1)

Converted

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "net_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "usdg_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "router_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pair_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "oracle_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "teamWallet_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyWired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ClipTooLarge",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "MulDivOverflow",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotDeployer",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotWired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SlippageExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "netIn",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "usdgOutRaw",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "teamUsdgRaw",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "treasuryUsdgRaw",
        "type": "uint256"
      }
    ],
    "name": "Converted",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "netAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minUsdgOutRaw",
        "type": "uint256"
      }
    ],
    "name": "convert",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "net",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "oracle",
    "outputs": [
      {
        "internalType": "contract IPairOracle",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pTeam",
    "outputs": [
      {
        "internalType": "contract IPTEAM",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pairContract",
    "outputs": [
      {
        "internalType": "contract IUniswapV2Pair",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingNet",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "router",
    "outputs": [
      {
        "internalType": "contract IUniswapV2Router02",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "teamBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "teamWallet",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasuryBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "usdg",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pTeam_",
        "type": "address"
      }
    ],
    "name": "wire",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "wired",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.24;

import {ITaxCollector} from "./interfaces/ITaxCollector.sol";
import {IPTEAM} from "./interfaces/IPTEAM.sol";
import {IPairOracle} from "./interfaces/IPairOracle.sol";
import {IERC20, IERC20Metadata} from "./interfaces/external/IERC20.sol";
import {IUniswapV2Pair, IUniswapV2Router02} from "./interfaces/external/IUniswapV2.sol";
import {Constants} from "./Constants.sol";
import {FixedPointMath} from "./libraries/FixedPointMath.sol";
import {Wired} from "./abstract/Wired.sol";

/// @title TaxCollector — trading-tax accrual and conversion (specs/tax.md §4)
/// @notice NET tax accrues here during swaps (collect-then-convert; this
///         contract never re-enters the pool inside a taxed transfer).
///         `convert()` is permissionless (Q12): batch-swaps NET → USDG on the
///         canonical pool, clip-limited to TAX_SWAP_MAX_CLIP_BPS of pool NET
///         reserves and TWAP-bounded by TAX_SWAP_MAX_DEV_BPS, then splits
///         proceeds by the deterministic decay (mechanism §4):
///         teamBps = 400 × (1 − v), treasuryBps = 500 − teamBps, where v is
///         pTEAM's vestedFraction — one clock, no admin setter.
contract TaxCollector is ITaxCollector, Wired {
    error ZeroAmount();
    error ClipTooLarge();
    error SlippageExceeded();
    error TransferFailed();

    IERC20 public immutable net;
    IERC20 public immutable usdg;
    IUniswapV2Router02 public immutable router;
    IUniswapV2Pair public immutable pairContract;
    IPairOracle public immutable oracle;
    address public immutable treasury;
    address public immutable teamWallet;
    bool private immutable _netIsToken0;
    uint256 private immutable _usdgWadFactor;

    IPTEAM public pTeam;

    constructor(
        address net_,
        address usdg_,
        address router_,
        address pair_,
        address oracle_,
        address treasury_,
        address teamWallet_
    ) {
        net = IERC20(_nonZero(net_));
        usdg = IERC20(_nonZero(usdg_));
        router = IUniswapV2Router02(_nonZero(router_));
        pairContract = IUniswapV2Pair(_nonZero(pair_));
        oracle = IPairOracle(_nonZero(oracle_));
        treasury = _nonZero(treasury_);
        teamWallet = _nonZero(teamWallet_);
        _netIsToken0 = IUniswapV2Pair(pair_).token0() == net_;
        _usdgWadFactor = 10 ** (18 - IERC20Metadata(usdg_).decimals());
    }

    /// @notice One-time wiring (pTEAM ↔ TaxCollector deploy cycle).
    function wire(address pTeam_) external wiring {
        pTeam = IPTEAM(_nonZero(pTeam_));
    }

    /// @inheritdoc ITaxCollector
    function convert(uint256 netAmount, uint256 minUsdgOutRaw) external {
        _checkWired();
        if (netAmount == 0) revert ZeroAmount();

        // Clip bound: keep swaps small relative to pool depth.
        (uint112 r0, uint112 r1,) = pairContract.getReserves();
        uint112 netReserve = _netIsToken0 ? r0 : r1;
        if (netAmount * Constants.BPS > uint256(netReserve) * Constants.TAX_SWAP_MAX_CLIP_BPS) {
            revert ClipTooLarge();
        }

        // TWAP bound: reverts on stale oracle (D1) — no conversion without a
        // live TWAP.
        uint256 twap = oracle.twapNetUsdg();
        uint256 expectedWad = FixedPointMath.mulDiv(netAmount, twap, Constants.NET_UNIT);
        uint256 minAllowedRaw = expectedWad * (Constants.BPS - Constants.TAX_SWAP_MAX_DEV_BPS)
            / Constants.BPS / _usdgWadFactor;
        uint256 minOut = minUsdgOutRaw > minAllowedRaw ? minUsdgOutRaw : minAllowedRaw;

        uint256 usdgBefore = usdg.balanceOf(address(this));
        if (!net.approve(address(router), netAmount)) revert TransferFailed();
        address[] memory path = new address[](2);
        path[0] = address(net);
        path[1] = address(usdg);
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            netAmount, minOut, path, address(this), block.timestamp
        );
        uint256 usdgOut = usdg.balanceOf(address(this)) - usdgBefore;
        if (usdgOut < minOut) revert SlippageExceeded();

        uint256 teamShare = usdgOut * teamBps() / Constants.TAX_TOTAL_BPS;
        uint256 treasuryShare = usdgOut - teamShare;
        if (teamShare != 0 && !usdg.transfer(teamWallet, teamShare)) revert TransferFailed();
        if (!usdg.transfer(treasury, treasuryShare)) revert TransferFailed();
        emit Converted(netAmount, usdgOut, teamShare, treasuryShare);
    }

    /// @inheritdoc ITaxCollector
    function teamBps() public view returns (uint256) {
        uint256 v = pTeam.vestedFraction(); // WAD, clamps at 1e18 forever
        return Constants.TAX_TEAM_START_BPS * (Constants.WAD - v) / Constants.WAD;
    }

    /// @inheritdoc ITaxCollector
    function treasuryBps() external view returns (uint256) {
        return Constants.TAX_TOTAL_BPS - teamBps();
    }

    /// @inheritdoc ITaxCollector
    function pendingNet() external view returns (uint256) {
        return net.balanceOf(address(this));
    }
}
Chain explorer6537msChain node79ms