Skip to main content
PulseScanner.io

Address

0xe3d5de07962ca32a7c19385d38014e5bb57dfd87
Current Holdings
$0.00
TXs sent
0
First Active
2026-02-26
block 25,891,725
Last Active
today
block 27,506,579
Funded By
not identified

Net worth historyi

No net-worth snapshots recorded yet
partial matchTWAPOraclesolc 0.8.28+commit.7893614aruntime partial · creation partial
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// --- Interfaces ---
interface IUniswapV2Pair {
    function price0CumulativeLast() external view returns (uint);

    function price1CumulativeLast() external view returns (uint);

    function getReserves()
        external
        view
        returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);

    function token0() external view returns (address);

    function token1() external view returns (address);
}

interface IERC20Decimals {
    function decimals() external view returns (uint8);
}

interface ITWAPToken {
    function totalSupply() external view returns (uint);

    function balanceOf(address account) external view returns (uint);

    function mint(address to, uint amount) external;
}

// --- Libraries ---
library FixedPoint {
    struct uq112x112 {
        uint224 _x;
    }
    struct uq144x112 {
        uint _x;
    }
    uint8 private constant RESOLUTION = 112;

    function encode(uint112 x) internal pure returns (uq112x112 memory) {
        return uq112x112(uint224(x) << RESOLUTION);
    }

    function mul(
        uq112x112 memory self,
        uint y
    ) internal pure returns (uq144x112 memory) {
        return uq144x112(self._x * y);
    }

    function fraction(
        uint112 numerator,
        uint112 denominator
    ) internal pure returns (uq112x112 memory) {
        if (denominator == 0) revert("FixedPoint: DIV_BY_ZERO");
        return uq112x112((uint224(numerator) << RESOLUTION) / denominator);
    }

    function decode144(uq144x112 memory self) internal pure returns (uint144) {
        return uint144(self._x >> RESOLUTION);
    }

    function rpow(uint x, uint n) internal pure returns (uint z) {
        z = n % 2 != 0 ? x : 1e18;
        for (n /= 2; n != 0; n /= 2) {
            x = (x * x) / 1e18;
            if (n % 2 != 0) z = (z * x) / 1e18;
        }
    }
}

library UniswapV2OracleLibrary {
    using FixedPoint for *;

    function currentBlockTimestamp() internal view returns (uint) {
        return block.timestamp;
    }

    function currentCumulativePrices(
        address pair
    )
        internal
        view
        returns (
            uint price0Cumulative,
            uint price1Cumulative,
            uint blockTimestamp
        )
    {
        blockTimestamp = currentBlockTimestamp();
        price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast();
        price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast();
        (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        ) = IUniswapV2Pair(pair).getReserves();
        if (blockTimestampLast != blockTimestamp) {
            uint32 timeElapsed = uint32(blockTimestamp - blockTimestampLast);
            unchecked {
                price0Cumulative +=
                    uint(FixedPoint.fraction(reserve1, reserve0)._x) *
                    timeElapsed;
                price1Cumulative +=
                    uint(FixedPoint.fraction(reserve0, reserve1)._x) *
                    timeElapsed;
            }
        }
    }

    struct PriceData {
        uint price0Cumulative;
        uint price1Cumulative;
    }

    function batchCumulativePrices(
        address[] memory pairs
    ) internal view returns (PriceData[] memory prices, uint blockTimestamp) {
        blockTimestamp = currentBlockTimestamp();
        prices = new PriceData[](pairs.length);
        for (uint i; i < pairs.length; ) {
            IUniswapV2Pair pair = IUniswapV2Pair(pairs[i]);
            uint price0Cumulative = pair.price0CumulativeLast();
            uint price1Cumulative = pair.price1CumulativeLast();
            (
                uint112 reserve0,
                uint112 reserve1,
                uint32 blockTimestampLast
            ) = pair.getReserves();
            if (blockTimestampLast != blockTimestamp) {
                uint32 timeElapsed = uint32(
                    blockTimestamp - blockTimestampLast
                );
                unchecked {
                    price0Cumulative +=
                        uint(FixedPoint.fraction(reserve1, reserve0)._x) *
                        timeElapsed;
                    price1Cumulative +=
                        uint(FixedPoint.fraction(reserve0, reserve1)._x) *
                        timeElapsed;
                }
            }
            prices[i] = PriceData(price0Cumulative, price1Cumulative);
            unchecked {
                ++i;
            }
        }
    }
}

// --- Oracle Contract ---
contract TWAPOracle {
    using FixedPoint for *;

    struct Observation {
        uint timestamp;
        uint price0Cumulative;
        uint price1Cumulative;
    }
    struct ReferenceStable {
        address stable;
        address pair;
        bool isWplsToken0;
        uint8 stableDecimals;
        bool active;
    }
    struct PairInfo {
        address pair;
        bool isToken0;
    }

    // Errors
    error UnknownPair();
    error PairAlreadyAdded();
    error InvalidPair();
    error InvalidTWAPWindow();
    error Unauthorized();
    error PeriodNotElapsed();
    error RewardsNotEnabled();
    error InsufficientBalanceForReward();
    error AlreadyLaunched();
    error NoValidReferences();
    error StalePrice();
    error InsufficientObservations();
    error InvalidIndex();

    address public immutable wpls;
    ITWAPToken public immutable twapToken; // The separated token

    mapping(address => uint) public wards;

    function rely(address usr) external auth {
        wards[usr] = 1;
        emit Rely(usr);
    }

    function deny(address usr) external auth {
        wards[usr] = 0;
        emit Deny(usr);
    }

    modifier auth() {
        if (wards[msg.sender] != 1) revert Unauthorized();
        _;
    }

    bool public rewardsEnabled;

    // Constants (Using the responsive 15m settings)
    uint public constant PERIOD_SIZE = 900;
    uint public constant MIN_WINDOW = 900;
    uint public constant MAX_WINDOW = 7200;
    uint public constant REWARD_WINDOW = 1350;
    uint public constant DECAY_RATE = 500000000000000000; // 0.5
    uint public constant DECAY_PERIOD = 225;

    // Reward Config
    uint public constant BASE_REWARD_RATE = 13888888888888888;
    uint public constant INITIAL_SUPPLY = 1_000_000 * 10 ** 18;
    uint public constant MIN_HOLDING_DIVISOR = 555;

    // State
    mapping(address => Observation[5]) public observations;
    mapping(address => uint8) public observationHeads;
    address[] public livePairs;
    mapping(address => PairInfo) public tokenToPairInfo;
    address[] public pricedTokens;
    ReferenceStable[] public referenceStables;
    uint public lastSuperUpdateTimestamp;

    // Events
    event PriceUpdated(
        address indexed token,
        address indexed pair,
        uint price0Cumulative,
        uint price1Cumulative,
        uint timestamp
    );
    event Reward(address indexed updater, uint amount);
    event RewardsLaunched();
    event Rely(address indexed usr);
    event Deny(address indexed usr);
    event ReferenceAdded(
        address indexed stable,
        address indexed pair,
        bool isWplsToken0,
        uint8 stableDecimals
    );
    event ReferenceUpdated(uint indexed index, bool active);
    event ReferenceRemoved(uint indexed index, address indexed pair);
    event DirectPairAdded(
        address indexed token,
        address indexed pair,
        bool isToken0
    );
    event DirectPairRemoved(address indexed token);
    event SuperUpdate(uint timestamp, uint updatedPairs);

    constructor(address _wpls, address _twapToken) {
        wpls = _wpls;
        twapToken = ITWAPToken(_twapToken);
        wards[msg.sender] = 1;
        emit Rely(msg.sender);
    }

    // --- Views ---
    function getMinHoldingForReward() public view returns (uint) {
        return twapToken.totalSupply() / MIN_HOLDING_DIVISOR;
    }

    function launch() external auth {
        if (rewardsEnabled) revert AlreadyLaunched();
        rewardsEnabled = true;
        emit RewardsLaunched();
    }

    // --- Admin Logic ---
    function _isPairLive(address pair) internal view returns (bool) {
        for (uint i = 0; i < livePairs.length; i++) {
            if (livePairs[i] == pair) return true;
        }
        return false;
    }

    function addReferenceStable(
        address stable,
        address pair,
        bool isWplsToken0
    ) external auth {
        address token0 = IUniswapV2Pair(pair).token0();
        address token1 = IUniswapV2Pair(pair).token1();
        if (
            (isWplsToken0 && token0 != wpls) ||
            (!isWplsToken0 && token1 != wpls)
        ) revert InvalidPair();
        if (stable != token0 && stable != token1) revert InvalidPair();
        uint8 stableDecimals = IERC20Decimals(stable).decimals();
        if (stableDecimals > 18) revert("Stable decimals >18");

        referenceStables.push(
            ReferenceStable(stable, pair, isWplsToken0, stableDecimals, true)
        );
        if (!_isPairLive(pair)) {
            if (observationHeads[pair] == 0) {
                (
                    uint price0Cumulative,
                    uint price1Cumulative,

                ) = UniswapV2OracleLibrary.currentCumulativePrices(pair);
                observations[pair][0] = Observation(
                    block.timestamp,
                    price0Cumulative,
                    price1Cumulative
                );
                observationHeads[pair] = 1;
            }
            livePairs.push(pair);
        }
        emit ReferenceAdded(stable, pair, isWplsToken0, stableDecimals);
    }

    function updateReference(uint index, bool active) external auth {
        if (index >= referenceStables.length) revert("Invalid index");
        referenceStables[index].active = active;
        emit ReferenceUpdated(index, active);
    }

    function addDirectPair(
        address token,
        address pair,
        bool isToken0
    ) external auth {
        if (tokenToPairInfo[token].pair != address(0))
            revert PairAlreadyAdded();
        address token0 = IUniswapV2Pair(pair).token0();
        address token1 = IUniswapV2Pair(pair).token1();
        if ((isToken0 && token0 != token) || (!isToken0 && token1 != token))
            revert InvalidPair();
        tokenToPairInfo[token] = PairInfo(pair, isToken0);
        pricedTokens.push(token);
        if (!_isPairLive(pair)) {
            if (observationHeads[pair] == 0) {
                (
                    uint price0Cumulative,
                    uint price1Cumulative,

                ) = UniswapV2OracleLibrary.currentCumulativePrices(pair);
                observations[pair][0] = Observation(
                    block.timestamp,
                    price0Cumulative,
                    price1Cumulative
                );
                observationHeads[pair] = 1;
            }
            livePairs.push(pair);
        }
        emit DirectPairAdded(token, pair, isToken0);
    }

    function removeDirectPair(address token) external auth {
        PairInfo memory info = tokenToPairInfo[token];
        if (info.pair == address(0)) revert UnknownPair();
        delete tokenToPairInfo[token];
        for (uint i = 0; i < pricedTokens.length; i++) {
            if (pricedTokens[i] == token) {
                pricedTokens[i] = pricedTokens[pricedTokens.length - 1];
                pricedTokens.pop();
                break;
            }
        }
        address pair = info.pair;
        bool isStillReference = false;
        for (uint i = 0; i < referenceStables.length; i++) {
            if (referenceStables[i].pair == pair) {
                isStillReference = true;
                break;
            }
        }
        if (!isStillReference) {
            _removeLivePair(pair);
            delete observations[pair];
            delete observationHeads[pair];
        }
        emit DirectPairRemoved(token);
    }

    function removeReferenceStable(uint index) external auth {
        if (index >= referenceStables.length) revert InvalidIndex();
        address pair = referenceStables[index].pair;
        referenceStables[index] = referenceStables[referenceStables.length - 1];
        referenceStables.pop();
        bool isStillUsed = false;
        for (uint i = 0; i < pricedTokens.length; i++) {
            if (tokenToPairInfo[pricedTokens[i]].pair == pair) {
                isStillUsed = true;
                break;
            }
        }
        if (!isStillUsed) {
            for (uint i = 0; i < referenceStables.length; i++) {
                if (referenceStables[i].pair == pair) {
                    isStillUsed = true;
                    break;
                }
            }
        }
        if (!isStillUsed) {
            _removeLivePair(pair);
            delete observations[pair];
            delete observationHeads[pair];
        }
        emit ReferenceRemoved(index, pair);
    }

    function _removeLivePair(address pair) internal {
        for (uint i = 0; i < livePairs.length; i++) {
            if (livePairs[i] == pair) {
                livePairs[i] = livePairs[livePairs.length - 1];
                livePairs.pop();
                break;
            }
        }
    }

    // --- Core Logic (FIXED with correct CEI + reward timing) ---
    function superUpdate() public {
        uint currentTimestamp = block.timestamp;
        if (currentTimestamp - lastSuperUpdateTimestamp < MIN_WINDOW)
            revert PeriodNotElapsed();

        // === CHECKS DONE ===

        uint totalPairs = livePairs.length;
        address[] memory pairsToUpdate = new address[](totalPairs);
        uint updateCount = 0;
        for (uint i = 0; i < totalPairs; ) {
            address pair = livePairs[i];
            uint8 head = observationHeads[pair];
            uint8 lastIdx = (head == 0) ? 4 : head - 1;
            if (
                currentTimestamp - observations[pair][lastIdx].timestamp >=
                PERIOD_SIZE
            ) {
                pairsToUpdate[updateCount] = pair;
                unchecked {
                    ++updateCount;
                }
            }
            unchecked {
                ++i;
            }
        }

        // === EFFECTS START (price updates + timestamp) ===
        if (updateCount > 0) {
            address[] memory activeBatch = new address[](updateCount);
            for (uint i = 0; i < updateCount; i++) {
                activeBatch[i] = pairsToUpdate[i];
            }
            (
                UniswapV2OracleLibrary.PriceData[] memory prices,

            ) = UniswapV2OracleLibrary.batchCumulativePrices(activeBatch);
            for (uint i = 0; i < updateCount; i++) {
                _applyUpdate(activeBatch[i], prices[i], currentTimestamp);
            }
        }

        // Calculate reward BEFORE updating timestamp (this is the key fix)
        uint reward = 0;
        if (
            rewardsEnabled &&
            twapToken.balanceOf(msg.sender) >= getMinHoldingForReward()
        ) {
            reward = calculateReward(currentTimestamp); // still uses OLD lastSuperUpdateTimestamp
        }

        // Now update timestamp (state change)
        lastSuperUpdateTimestamp = currentTimestamp;
        emit SuperUpdate(currentTimestamp, updateCount);

        // === INTERACTIONS (external call last) ===
        if (reward > 0) {
            _mintReward(msg.sender, reward);
        }
    }

    function _applyUpdate(
        address pair,
        UniswapV2OracleLibrary.PriceData memory price,
        uint blockTimestamp
    ) private {
        uint8 head = observationHeads[pair];
        observations[pair][head] = Observation(
            blockTimestamp,
            price.price0Cumulative,
            price.price1Cumulative
        );
        observationHeads[pair] = (head + 1) % 5;
        emit PriceUpdated(
            address(0),
            pair,
            price.price0Cumulative,
            price.price1Cumulative,
            blockTimestamp
        );
    }

    function getMedianPrice(
        address pair,
        bool needPrice0,
        uint currentCumulative,
        uint currentTimestamp
    ) internal view returns (uint224 medianPrice) {
        Observation[5] memory obsArray;
        for (uint i = 0; i < 5; i++) {
            obsArray[i] = observations[pair][i];
        }
        for (uint i = 1; i < 5; ) {
            Observation memory key = obsArray[i];
            uint j = i;
            while (j > 0 && obsArray[j - 1].timestamp < key.timestamp) {
                obsArray[j] = obsArray[j - 1];
                j--;
            }
            obsArray[j] = key;
            unchecked {
                ++i;
            }
        }
        uint224[] memory segmentPrices = new uint224[](5);
        uint count = 0;
        uint prevCum = currentCumulative;
        uint prevTs = currentTimestamp;
        for (uint i = 0; i < 5; i++) {
            if (obsArray[i].timestamp == 0) continue;
            uint timeElapsed = prevTs - obsArray[i].timestamp;
            if (timeElapsed < MIN_WINDOW || timeElapsed > MAX_WINDOW) continue;
            uint obsCum = needPrice0
                ? obsArray[i].price0Cumulative
                : obsArray[i].price1Cumulative;
            uint segmentTWAP = (prevCum - obsCum) / timeElapsed;
            segmentPrices[count] = uint224(segmentTWAP);
            count++;
            prevCum = obsCum;
            prevTs = obsArray[i].timestamp;
            if (count >= 3) break; // Rolling Window
        }
        if (count < 3) revert InsufficientObservations();
        for (uint i = 1; i < count; ) {
            uint224 key = segmentPrices[i];
            uint j = i;
            while (j > 0 && segmentPrices[j - 1] > key) {
                segmentPrices[j] = segmentPrices[j - 1];
                j--;
            }
            segmentPrices[j] = key;
            unchecked {
                ++i;
            }
        }
        if (count % 2 == 1) {
            medianPrice = segmentPrices[count / 2];
        } else {
            medianPrice =
                (segmentPrices[count / 2 - 1] + segmentPrices[count / 2]) /
                2;
        }
    }

    function _getNormalizedPriceFromRef(
        ReferenceStable memory ref
    ) internal view returns (uint priceNormalized) {
        (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(ref.pair)
            .getReserves();
        uint wplsReserve = ref.isWplsToken0 ? uint(reserve0) : uint(reserve1);
        if (wplsReserve == 0) return 0;
        (
            uint price0Cumulative,
            uint price1Cumulative,
            uint blockTimestamp
        ) = UniswapV2OracleLibrary.currentCumulativePrices(ref.pair);
        bool needPrice0 = ref.isWplsToken0;
        uint224 medianPrice = getMedianPrice(
            ref.pair,
            needPrice0,
            needPrice0 ? price0Cumulative : price1Cumulative,
            blockTimestamp
        );
        uint scalar = 1e18 * (10 ** (18 - ref.stableDecimals));
        FixedPoint.uq144x112 memory fullPrice = FixedPoint
            .uq112x112(medianPrice)
            .mul(scalar);
        priceNormalized = fullPrice._x >> 112;
    }

    function getWplsPriceInUsd() public view returns (uint priceWad) {
        uint validCount = 0;
        uint[] memory prices = new uint[](referenceStables.length);
        for (uint i = 0; i < referenceStables.length; i++) {
            ReferenceStable memory ref = referenceStables[i];
            if (!ref.active) continue;
            uint price = _getNormalizedPriceFromRef(ref);
            if (price == 0) continue;
            prices[validCount] = price;
            validCount++;
        }
        if (validCount == 0) revert NoValidReferences();
        for (uint i = 0; i < validCount; i++) {
            for (uint j = i + 1; j < validCount; j++) {
                if (prices[i] > prices[j]) {
                    uint temp = prices[i];
                    prices[i] = prices[j];
                    prices[j] = temp;
                }
            }
        }
        if (validCount % 2 == 1) {
            priceWad = prices[validCount / 2];
        } else {
            priceWad =
                (prices[validCount / 2 - 1] + prices[validCount / 2]) /
                2;
        }
    }

    function getPriceInUsd(
        address token,
        uint amountIn
    ) public view returns (uint amountOutUsd) {
        if (token == wpls) {
            return (amountIn * getWplsPriceInUsd()) / 1e18;
        }
        PairInfo memory info = tokenToPairInfo[token];
        if (info.pair != address(0)) {
            (
                uint price0Cumulative,
                uint price1Cumulative,
                uint blockTimestamp
            ) = UniswapV2OracleLibrary.currentCumulativePrices(info.pair);
            uint224 medianPrice = getMedianPrice(
                info.pair,
                info.isToken0,
                info.isToken0 ? price0Cumulative : price1Cumulative,
                blockTimestamp
            );
            FixedPoint.uq144x112 memory temp = FixedPoint
                .uq112x112(medianPrice)
                .mul(amountIn);
            uint amountInWpls = temp._x >> 112;
            amountOutUsd = (amountInWpls * getWplsPriceInUsd()) / 1e18;
        } else {
            revert UnknownPair();
        }
    }

    // --- Rewards ---
    function _mintReward(address updater, uint reward) private {
        if (!rewardsEnabled) revert RewardsNotEnabled();
        if (reward == 0) return;
        twapToken.mint(updater, reward); // Calls the external token contract
        emit Reward(updater, reward);
    }

    function calculateReward(uint currentTime) internal view returns (uint) {
        if (!rewardsEnabled) return 0;
        uint timeSinceLast = currentTime - lastSuperUpdateTimestamp;
        if (timeSinceLast < MIN_WINDOW) return 0;
        uint effectiveTime = timeSinceLast - MIN_WINDOW;
        uint peakEffectiveTime = REWARD_WINDOW - MIN_WINDOW;
        uint timeBasedReward;
        if (effectiveTime <= peakEffectiveTime) {
            timeBasedReward = BASE_REWARD_RATE * effectiveTime;
        } else {
            uint cappedExtra = effectiveTime - peakEffectiveTime;
            uint decayIntervals = cappedExtra / DECAY_PERIOD;
            uint decayFactor = FixedPoint.rpow(DECAY_RATE, decayIntervals);
            uint peakReward = BASE_REWARD_RATE * peakEffectiveTime;
            timeBasedReward = (peakReward * decayFactor) / 1e18;
        }
        uint supplyFactor = 1e18;
        uint currentSupply = twapToken.totalSupply(); // Read from external token
        if (currentSupply > INITIAL_SUPPLY) {
            uint a = INITIAL_SUPPLY;
            uint b = currentSupply;
            supplyFactor = (2 * a * 1e18) / (a + b);
        }
        return (timeBasedReward * supplyFactor) / 1e18;
    }

    function getTokens() external view returns (address[] memory) {
        return pricedTokens;
    }

    function getReferenceCount() external view returns (uint) {
        return referenceStables.length;
    }

    function getPricedTokenCount() external view returns (uint) {
        return pricedTokens.length;
    }

    function getCurrentReward() external view returns (uint) {
        return calculateReward(block.timestamp);
    }
}