S Price: $0.550406 (+3.11%)

Contract

0x06Ce8086965234400FDecAb190B115C2C0717047

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Parent Transaction Hash Block From To
View All Internal Transactions
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x1daB6560...df3a51828
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
ChainlinkEMA

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 200 runs

Other Settings:
istanbul EvmVersion, None license
File 1 of 1 : ChainlinkEMA.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

interface IChainlinkAggregator {
    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

    function getRoundData(
        uint80 _roundId
    )
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

    function decimals() external view returns (uint256);
}

interface IPriceOracle {
    /**
        @notice Returns the current oracle price, normalized to 1e18 precision
        @dev Called by all state-changing market / amm operations with the exception
             of `MainController.close_loan`
     */
    function price_w() external returns (uint256);

    /**
        @notice Returns the current oracle price, normalized to 1e18 precision
        @dev Read-only version used within view methods. Should always return
             the same value as `price_w`
     */
    function price() external view returns (uint256);
}


/**
    @title Chainlink EMA Oracle
    @author defidotmoney
    @notice Calculates an exponential moving average from a Chainlink feed
    @dev This contract is designed for use in L2/sidechain environments where
         gas costs are negligible. It is not recommended for use on Ethereum mainnet.
 */
contract ChainlinkEMA {
    IChainlinkAggregator public immutable chainlinkFeed;

    /// @notice The number of observations used in calculating the EMA.
    uint256 public immutable OBSERVATIONS;
    /// @notice The number of seconds between price observations when calculating the EMA.
    uint256 public immutable INTERVAL;
    /// @dev `2 / (OBSERVATIONS - 1)` stored with 1e18 precision
    uint256 public immutable SMOOTHING_FACTOR;

    uint256 private immutable MAX_LOOKBACK;
    uint256 private immutable PRECISION_MUL;

    uint256 public storedPrice;
    uint256 public storedObservationTimestamp;
    ChainlinkResponse public storedResponse;

    struct ChainlinkResponse {
        uint80 roundId;
        uint128 updatedAt;
        uint256 answer; // normalized to 1e18
    }

    constructor(IChainlinkAggregator _chainlink, uint256 _observations, uint256 _interval) {
        chainlinkFeed = _chainlink;
        OBSERVATIONS = _observations;
        INTERVAL = _interval;
        SMOOTHING_FACTOR = 2e18 / (_observations + 1);
        MAX_LOOKBACK = _observations * 2;
        PRECISION_MUL = 10 ** (18 - _chainlink.decimals());

        uint256 currentObservation = _getCurrentObservationTimestamp();
        (storedPrice, storedResponse) = _calculateNewEMA(currentObservation);
        storedObservationTimestamp = currentObservation;
    }

    /**
        @notice Returns the current oracle price, normalized to 1e18 precision.
        @dev Read-only version used in view methods. Returns the same value as `price_w`.
     */
    function price() external view returns (uint256 currentPrice) {
        uint256 currentObservation = _getCurrentObservationTimestamp();
        uint256 storedObservation = storedObservationTimestamp;
        if (currentObservation == storedObservation) return storedPrice;

        if (storedObservation + MAX_LOOKBACK * INTERVAL > currentObservation) {
            (currentPrice, , ) = _calculateLatestEMA(currentObservation, storedObservation);
        } else {
            (currentPrice, ) = _calculateNewEMA(currentObservation);
        }
        return currentPrice;
    }

    /**
        @notice Returns the current oracle price, normalized to 1e18 precision.
        @dev It is preferred to call this method during on-chain interactions if possible.
     */
    function price_w() external returns (uint256 currentPrice) {
        uint256 currentObservation = _getCurrentObservationTimestamp();
        uint256 storedObservation = storedObservationTimestamp;
        if (currentObservation == storedObservation) return storedPrice;

        if (storedObservation + MAX_LOOKBACK * INTERVAL > currentObservation) {
            bool isNewResponse;
            ChainlinkResponse memory response;
            (currentPrice, response, isNewResponse) = _calculateLatestEMA(currentObservation, storedObservation);
            if (isNewResponse) storedResponse = response;
        } else {
            (currentPrice, storedResponse) = _calculateNewEMA(currentObservation);
        }
        storedObservationTimestamp = currentObservation;
        storedPrice = currentPrice;
        return currentPrice;
    }

    /**
        @dev Calculates the latest EMA price by performing observations at all observation
             intervals since the last stored one. Used when the number of new observations
             required is less than `2 * OBSERVATIONS`.
     */
    function _calculateLatestEMA(
        uint256 currentObservation,
        uint256 storedObservation
    ) internal view returns (uint256 currentPrice, ChainlinkResponse memory latestResponse, bool isNewResponse) {
        currentPrice = storedPrice;
        latestResponse = _getLatestRoundData();
        ChainlinkResponse memory response = storedResponse;

        // special case, latest round is the same as stored round
        if (latestResponse.roundId == response.roundId) {
            uint256 answer = response.answer;
            while (storedObservation < currentObservation) {
                storedObservation += INTERVAL;
                currentPrice = _getNextEMA(answer, currentPrice);
            }
            return (currentPrice, latestResponse, false);
        }

        bool isLatestResponse;
        ChainlinkResponse memory nextResponse;
        if (latestResponse.roundId > response.roundId + 1) {
            nextResponse = _getNextRoundData(response.roundId);
        } else {
            nextResponse = latestResponse;
        }

        while (storedObservation < currentObservation) {
            storedObservation += INTERVAL;
            while (!isLatestResponse && nextResponse.updatedAt < storedObservation) {
                response = nextResponse;
                if (nextResponse.roundId == latestResponse.roundId) {
                    isLatestResponse = true;
                } else {
                    nextResponse = _getNextRoundData(nextResponse.roundId);
                }
            }
            currentPrice = _getNextEMA(response.answer, currentPrice);
        }

        return (currentPrice, latestResponse, true);
    }

    /**
        @dev Calculates an EMA price without relying on the last stored observation.
             Used when the number of new observations required is at least `2 * OBSERVATIONS`.
     */
    function _calculateNewEMA(
        uint256 observationTimestamp
    ) internal view returns (uint256 currentPrice, ChainlinkResponse memory latestResponse) {
        latestResponse = _getLatestRoundData();
        ChainlinkResponse memory response = latestResponse;

        uint256[] memory oracleResponses = new uint256[](MAX_LOOKBACK);

        // in the following while loops, we manually decrement and then increment
        // idx so we know where the first non-zero value is within oracleResponses
        uint256 idx = MAX_LOOKBACK;

        // iterate backward to get oracle responses for each observation time
        while (true) {
            while (response.updatedAt >= observationTimestamp) {
                if (response.roundId & type(uint64).max == 1) {
                    // first roundId for this aggregator, cannot look back further
                    break;
                }
                response = _getRoundData(response.roundId - 1);
            }
            if (response.updatedAt >= observationTimestamp) {
                if (idx == MAX_LOOKBACK) {
                    // edge case, if the first round is more recent than our latest
                    // observation time we can only return the first round's response
                    return (response.answer, latestResponse);
                }
                break;
            }
            idx--;
            oracleResponses[idx] = response.answer;
            if (idx == 0) break;
            observationTimestamp -= INTERVAL;
        }

        // now iterate forward to calculate EMA based on the observed oracle responses
        currentPrice = oracleResponses[idx];
        idx++;
        while (idx < MAX_LOOKBACK) {
            currentPrice = _getNextEMA(oracleResponses[idx], currentPrice);
            idx++;
        }

        return (currentPrice, latestResponse);
    }

    /** @dev Given the latest price and the last EMA, returns the new EMA */
    function _getNextEMA(uint256 newPrice, uint256 lastEMA) internal view returns (uint256) {
        return ((newPrice * SMOOTHING_FACTOR) + (lastEMA * (1e18 - SMOOTHING_FACTOR))) / 1e18;
    }

    /** @dev The timestamp of the latest oracle observation */
    function _getCurrentObservationTimestamp() internal view returns (uint256) {
        return (block.timestamp / INTERVAL) * INTERVAL;
    }

    function _getLatestRoundData() internal view returns (ChainlinkResponse memory) {
        (uint80 roundId, int256 answer, , uint256 updatedAt, ) = chainlinkFeed.latestRoundData();
        return _validateAndFormatResponse(roundId, answer, updatedAt);
    }

    function _getRoundData(uint80 roundId) internal view returns (ChainlinkResponse memory) {
        (uint80 roundId, int256 answer, , uint256 updatedAt, ) = chainlinkFeed.getRoundData(roundId);
        return _validateAndFormatResponse(roundId, answer, updatedAt);
    }

    /**
        @dev Given a `roundId`, gets the response data for the next round. This method is preferred
             over calling `_getRoundData(roundId + 1)` because it handles a case where the oracle
             phase has increased: https://docs.chain.link/data-feeds/historical-data#roundid-in-proxy
     */
    function _getNextRoundData(uint80 roundId) internal view returns (ChainlinkResponse memory) {
        try chainlinkFeed.getRoundData(roundId + 1) returns (uint80 round, int answer, uint, uint updatedAt, uint80) {
            // depending on the direction the wind blows, an invalid roundId can revert or return zeros
            if (updatedAt > 0) return _validateAndFormatResponse(round, answer, updatedAt);
        } catch {}
        uint80 nextRoundId = (((roundId >> 64) + 1) << 64) + 1;
        return _getRoundData(nextRoundId);
    }

    function _validateAndFormatResponse(
        uint80 roundId,
        int256 answer,
        uint256 updatedAt
    ) internal view returns (ChainlinkResponse memory) {
        require(answer > 0, "DFM: Chainlink answer too low");
        return
            ChainlinkResponse({
                roundId: roundId,
                updatedAt: uint128(updatedAt),
                answer: uint256(answer) * PRECISION_MUL
            });
    }
}

Settings
{
  "evmVersion": "istanbul",
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "libraries": {
    "ChainlinkEMA.sol": {}
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract IChainlinkAggregator","name":"_chainlink","type":"address"},{"internalType":"uint256","name":"_observations","type":"uint256"},{"internalType":"uint256","name":"_interval","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"INTERVAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OBSERVATIONS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SMOOTHING_FACTOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chainlinkFeed","outputs":[{"internalType":"contract IChainlinkAggregator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price","outputs":[{"internalType":"uint256","name":"currentPrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price_w","outputs":[{"internalType":"uint256","name":"currentPrice","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"storedObservationTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"storedPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"storedResponse","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"uint128","name":"updatedAt","type":"uint128"},{"internalType":"uint256","name":"answer","type":"uint256"}],"stateMutability":"view","type":"function"}]

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106100935760003560e01c80639ed30128116100665780639ed3012814610171578063a035b1fe1461017a578063ae06785414610182578063b6e7aac4146101a9578063ceb7f759146101d057600080fd5b80637dbdf1f51461009857806380e63922146100dc57806389facb20146100f3578063901e9bd11461011a575b600080fd5b6100bf7f000000000000000000000000c76dfb89ff298145b417d221b2c747d84952e01d81565b6040516001600160a01b0390911681526020015b60405180910390f35b6100e560015481565b6040519081526020016100d3565b6100e57f000000000000000000000000000000000000000000000000000000000000001e81565b600254600354610144916001600160501b03811691600160501b9091046001600160801b03169083565b604080516001600160501b0390941684526001600160801b039092166020840152908201526060016100d3565b6100e560005481565b6100e56101d8565b6100e57f00000000000000000000000000000000000000000000000001525a8b03c0618681565b6100e57f000000000000000000000000000000000000000000000000000000000000001481565b6100e561027c565b6000806101e36103d2565b6001549091508082036101fa576000549250505090565b816102457f000000000000000000000000000000000000000000000000000000000000001e7f0000000000000000000000000000000000000000000000000000000000000028610c36565b61024f9083610c4d565b111561026a5761025f828261040e565b509093506102779050565b610273826105e8565b5092505b505090565b6000806102876103d2565b60015490915080820361029e576000549250505090565b816102e97f000000000000000000000000000000000000000000000000000000000000001e7f0000000000000000000000000000000000000000000000000000000000000028610c36565b6102f39083610c4d565b1115610378576040805160608101825260008082526020820181905291810182905261031f848461040e565b919650909250905081156103715780516002805460208401516001600160801b0316600160501b026001600160d01b03199091166001600160501b039093169290921791909117905560408101516003555b50506103c6565b610381826105e8565b80516002805460208401516001600160801b0316600160501b026001600160d01b03199091166001600160501b03909316929092179190911790556040015160035592505b50600155600081905590565b60007f000000000000000000000000000000000000000000000000000000000000001e6103ff8142610c60565b6104099190610c36565b905090565b60408051606081018252600080825260208201819052918101829052815491610435610823565b604080516060810182526002546001600160501b03808216808452600160501b9092046001600160801b031660208401526003549383019390935283519395509092909116036104d65760408101515b868610156104ca576104b77f000000000000000000000000000000000000000000000000000000000000001e87610c4d565b95506104c381866108e2565b9450610485565b50600091506105e19050565b6040805160608101825260008082526020820181905291810182905282516104ff906001610c82565b6001600160501b031685600001516001600160501b0316111561052e57825161052790610968565b9050610531565b50835b878710156105d9576105637f000000000000000000000000000000000000000000000000000000000000001e88610c4d565b96505b8115801561058057508681602001516001600160801b0316105b156105c45780925084600001516001600160501b031681600001516001600160501b0316036105b25760019150610566565b80516105bd90610968565b9050610566565b6105d28360400151876108e2565b9550610531565b506001925050505b9250925092565b6040805160608101825260008082526020820181905291810182905261060c610823565b90508060007f000000000000000000000000000000000000000000000000000000000000002867ffffffffffffffff81111561064a5761064a610ca9565b604051908082528060200260200182016040528015610673578160200160208202803683370190505b5090507f00000000000000000000000000000000000000000000000000000000000000285b8583602001516001600160801b0316106106dd57825167ffffffffffffffff166001146106dd5782516106d6906106d190600190610cbf565b610a84565b9250610698565b8583602001516001600160801b03161061072b577f0000000000000000000000000000000000000000000000000000000000000028810361072657505060400151939092509050565b610791565b8061073581610cdf565b915050826040015182828151811061074f5761074f610cf6565b602090810291909101015280156107915761078a7f000000000000000000000000000000000000000000000000000000000000001e87610d0c565b9550610698565b8181815181106107a3576107a3610cf6565b6020026020010151945080806107b890610d1f565b9150505b7f000000000000000000000000000000000000000000000000000000000000002881101561081b576108078282815181106107f9576107f9610cf6565b6020026020010151866108e2565b94508061081381610d1f565b9150506107bc565b505050915091565b604080516060810182526000808252602082018190529181019190915260008060007f000000000000000000000000c76dfb89ff298145b417d221b2c747d84952e01d6001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa1580156108a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108c79190610d54565b50935050925092506108da838383610b55565b935050505090565b6000670de0b6b3a76400006109177f00000000000000000000000000000000000000000000000001525a8b03c0618682610d0c565b6109219084610c36565b61094b7f00000000000000000000000000000000000000000000000001525a8b03c0618686610c36565b6109559190610c4d565b61095f9190610c60565b90505b92915050565b60408051606081018252600080825260208201819052918101919091526001600160a01b037f000000000000000000000000c76dfb89ff298145b417d221b2c747d84952e01d16639a6fc8f56109bf846001610c82565b6040516001600160e01b031960e084901b1681526001600160501b03909116600482015260240160a060405180830381865afa925050508015610a1f575060408051601f3d908101601f19168201909252610a1c91810190610d54565b60015b15610a46578115610a4057610a35858584610b55565b979650505050505050565b50505050505b60006040610a5b61ffff85831c166001610c82565b6001600160501b0316901b6001610a729190610c82565b9050610a7d81610a84565b9392505050565b6040805160608101825260008082526020820181905291810191909152604051639a6fc8f560e01b81526001600160501b0383166004820152600090819081906001600160a01b037f000000000000000000000000c76dfb89ff298145b417d221b2c747d84952e01d1690639a6fc8f59060240160a060405180830381865afa158015610b15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b399190610d54565b5093505092509250610b4c838383610b55565b95945050505050565b60408051606081018252600080825260208201819052918101829052908313610bc45760405162461bcd60e51b815260206004820152601d60248201527f44464d3a20436861696e6c696e6b20616e7377657220746f6f206c6f77000000604482015260640160405180910390fd5b604080516060810182526001600160501b03861681526001600160801b0384166020820152908101610c167f00000000000000000000000000000000000000000000000000000002540be40086610c36565b9052949350505050565b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761096257610962610c20565b8082018082111561096257610962610c20565b600082610c7d57634e487b7160e01b600052601260045260246000fd5b500490565b6001600160501b03818116838216019080821115610ca257610ca2610c20565b5092915050565b634e487b7160e01b600052604160045260246000fd5b6001600160501b03828116828216039080821115610ca257610ca2610c20565b600081610cee57610cee610c20565b506000190190565b634e487b7160e01b600052603260045260246000fd5b8181038181111561096257610962610c20565b600060018201610d3157610d31610c20565b5060010190565b80516001600160501b0381168114610d4f57600080fd5b919050565b600080600080600060a08688031215610d6c57600080fd5b610d7586610d38565b9450602086015193506040860151925060608601519150610d9860808701610d38565b9050929550929590935056fea26469706673582212203ec01c2730d64229b1b54a9d14f41d71f960ba9dccc6dd04ceab050008ed3e1964736f6c63430008190033

Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.