diff --git a/src/FourMemeTokenQuoter.sol b/src/FourMemeTokenQuoter.sol new file mode 100644 index 0000000..e77a6c1 --- /dev/null +++ b/src/FourMemeTokenQuoter.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface ITokenHelper { + function getTokenInfo( + address token + ) + external + view + returns ( + uint256 version, + address tokenManager, + address quote, + uint256 lastPrice, + uint256 tradingFeeRate, + uint256 minTradingFee, + uint256 launchTime, + uint256 offers, + uint256 maxOffers, + uint256 funds, + uint256 maxFunds, + bool liquidityAdded + ); +} +interface ITokenSwap { + function _fees(address) external view returns (uint); +} +interface IFactory { + function getPool(address, address, uint24) external view returns (address); + function poolByPair(address, address) external view returns (address); +} + + +contract FourMemeTokenQuoter { + address public constant TOKEN_MANAGER = 0x5c952063c7fc8610FFDB798152D69F0B9550762b; + address public constant TOKEN_SWAP = 0x350A94c918f7A0C8d108ba90f1b242B0143572B9; + address public constant TOKEN_HELPER = 0xF251F83e40a78868FcfA3FA4599Dad6494E46034; + + address public constant THENA = 0xF4C8E32EaDEC4BFe97E0F595AdD0f4450a863a11; + address public constant WETH = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + address public constant PANCAKE_QUOTER = 0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997; + address public constant PANCAKE_FACTORY = 0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865; + address public constant THENA_FACTORY = 0x306F06C147f064A010530292A1EB6737c3e378e4; + + // Query token version + function getTokenVersion(address token) external view returns (uint256 version) { + (uint256 _version, , , , , , , , , , , ) = ITokenHelper(TOKEN_HELPER).getTokenInfo(token); + return _version; + } + + // Query the pool address corresponding to the paired token + function getPoolAddress(address token) public view returns (address pool) { + (uint256 version, , address quoteToken, , , , , , , , , ) = ITokenHelper(TOKEN_HELPER).getTokenInfo(token); + require(version == 2, "Only version 2 supported"); + require(quoteToken != WETH, "Quote token cannot be BNB"); + + uint24 fee = uint24(ITokenSwap(TOKEN_SWAP)._fees(quoteToken)); + + if (quoteToken == THENA) { + pool = IFactory(THENA_FACTORY).poolByPair(quoteToken, WETH); + } else { + require(fee > 0, "Invalid fee rate"); + pool = IFactory(PANCAKE_FACTORY).getPool(quoteToken, WETH, fee); + } + + require(pool != address(0), "Pool not found"); + return pool; + } + + // Query the fee rate of the paired token + function getFeeRate(address quoteToken) public view returns (uint24) { + return uint24(ITokenSwap(TOKEN_SWAP)._fees(quoteToken)); + } + + // Get complete token information + function getTokenInfo(address token) external view returns ( + uint256 version, + address quoteToken, + address poolAddress, + uint24 feeRate + ) { + (version, , quoteToken, , , , , , , , , ) = ITokenHelper(TOKEN_HELPER).getTokenInfo(token); + + if (version == 2 && quoteToken != WETH) { + feeRate = getFeeRate(quoteToken); + if (feeRate > 0 || quoteToken == THENA) { + poolAddress = getPoolAddress(token); + } + } + + return (version, quoteToken, poolAddress, feeRate); + } +} diff --git a/src/RingQuoter.sol b/src/RingQuoter.sol new file mode 100644 index 0000000..7536b52 --- /dev/null +++ b/src/RingQuoter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); + function decimals() external view returns (uint8); +} + +interface ISwapV2Pair { + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IFewWrappedToken { + function token() external view returns (address); +} + +contract RingQuoter { + function getPoolTokenBalance( + address pool + ) + external + view + returns ( + uint256 token0Balance, + uint256 token1Balance, + uint256 underlyingAsset0Balance, + uint256 underlyingAsset1Balance + ) + { + address token0 = ISwapV2Pair(pool).token0(); + address token1 = ISwapV2Pair(pool).token1(); + address underlyingAsset0 = IFewWrappedToken(token0).token(); + address underlyingAsset1 = IFewWrappedToken(token1).token(); + token0Balance = IERC20(token0).balanceOf(pool); + token1Balance = IERC20(token1).balanceOf(pool); + underlyingAsset0Balance = IERC20(underlyingAsset0).balanceOf(token0); + underlyingAsset1Balance = IERC20(underlyingAsset1).balanceOf(token1); + + return ( + token0Balance, + token1Balance, + underlyingAsset0Balance, + underlyingAsset1Balance + ); + } +} diff --git a/test/FourMemeTokenQuoter.t.sol b/test/FourMemeTokenQuoter.t.sol new file mode 100644 index 0000000..8db2509 --- /dev/null +++ b/test/FourMemeTokenQuoter.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test, console2} from "forge-std/Test.sol"; +import {FourMemeTokenQuoter} from "../src/FourMemeTokenQuoter.sol"; + +contract FourMemeTokenQuoterTest is Test { + FourMemeTokenQuoter public quoter; + + address public constant TOKEN_MANAGER = 0x5c952063c7fc8610FFDB798152D69F0B9550762b; + address public constant TOKEN_SWAP = 0x350A94c918f7A0C8d108ba90f1b242B0143572B9; + address public constant TOKEN_HELPER = 0xF251F83e40a78868FcfA3FA4599Dad6494E46034; + + address public constant THENA = 0xF4C8E32EaDEC4BFe97E0F595AdD0f4450a863a11; + address public constant WETH = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + address public constant PANCAKE_QUOTER = 0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997; + address public constant PANCAKE_FACTORY = 0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865; + address public constant THENA_FACTORY = 0x306F06C147f064A010530292A1EB6737c3e378e4; + + + address constant TestToken = 0xb15EeE57E3AcC00ab15cCA0B65C2b5d0dA154444; + address constant ThenaToken = 0xbf141E43c961A8a35930b15722923fb39bDd4444; + + function setUp() public { + // Fork BSC mainnet for realistic testing + vm.createSelectFork("https://bsc.blockrazor.xyz"); + + // Deploy the quoter contract + quoter = new FourMemeTokenQuoter(); + } + + function test_getTokenInfo_NormalToken() public { + (uint256 version, address quoteToken, address poolAddress, uint24 feeRate) = quoter.getTokenInfo(TestToken); + console2.log("version", version); + console2.log("quoteToken", quoteToken); + console2.log("poolAddress", poolAddress); + console2.log("feeRate", feeRate); + assert(version == 2); + assert(quoteToken == 0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d); + assert(poolAddress == 0x4a3218606AF9B4728a9F187E1c1a8c07fBC172a9); + } + + function test_getTokenInfo_ThenaToken() public { + (uint256 version, address quoteToken, address poolAddress, uint24 feeRate) = quoter.getTokenInfo(ThenaToken); + console2.log("version", version); + console2.log("quoteToken", quoteToken); + console2.log("poolAddress", poolAddress); + console2.log("feeRate", feeRate); + } +} diff --git a/test/RingQuoter.t.sol b/test/RingQuoter.t.sol new file mode 100644 index 0000000..5c8ec33 --- /dev/null +++ b/test/RingQuoter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {RingQuoter} from "../src/RingQuoter.sol"; +import {Test, console2} from "forge-std/Test.sol"; + +contract RingQuoterTest is Test { + RingQuoter public quoter; + address constant RingPool = 0x44deaF11892D189D1D1c0CCF3bdE7a6CCEa24405; + + function setUp() public { + vm.createSelectFork("https://eth.llamarpc.com"); + quoter = new RingQuoter(); + } + + function testGetPoolTokenBalance() public { + (uint256 token0Balance, uint256 token1Balance, uint256 underlyingAsset0Balance, uint256 underlyingAsset1Balance) = quoter.getPoolTokenBalance(RingPool); + console2.log("token0Balance", token0Balance); + console2.log("token1Balance", token1Balance); + console2.log("underlyingAsset0Balance", underlyingAsset0Balance); + console2.log("underlyingAsset1Balance", underlyingAsset1Balance); + } + +}