• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RigoBlock / v3-contracts / 13261352678

11 Feb 2025 10:59AM UTC coverage: 84.94% (+2.6%) from 82.368%
13261352678

Pull #622

github

web-flow
Merge f22d260e3 into 08bd3b51b
Pull Request #622: feat: automated nav

761 of 962 branches covered (79.11%)

Branch coverage included in aggregate %.

523 of 711 new or added lines in 28 files covered. (73.56%)

18 existing lines in 5 files now uncovered.

1698 of 1933 relevant lines covered (87.84%)

44.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.14
/contracts/protocol/extensions/EOracle.sol
1
// SPDX-License-Identifier: Apache-2.0-or-later
2
/*
3

4
 Copyright 2025 Rigo Intl.
5

6
 Licensed under the Apache License, Version 2.0 (the "License");
7
 you may not use this file except in compliance with the License.
8
 You may obtain a copy of the License at
9

10
     http://www.apache.org/licenses/LICENSE-2.0
11

12
 Unless required by applicable law or agreed to in writing, software
13
 distributed under the License is distributed on an "AS IS" BASIS,
14
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 See the License for the specific language governing permissions and
16
 limitations under the License.
17

18
*/
19

20
pragma solidity 0.8.28;
21

22
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
23
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
24
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
25
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
26
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
27
import {IEOracle} from "./adapters/interfaces/IEOracle.sol";
28
import {IOracle} from "../interfaces/IOracle.sol";
29
import {Observation} from "../types/Observation.sol";
30

31
contract EOracle is IEOracle {
32
    using TickMath for int24;
33

34
    address private constant _ZERO_ADDRESS = address(0);
35

36
    IOracle private immutable _oracle;
37

38
    constructor(address oracleHookAddress) {
39
        _oracle = IOracle(oracleHookAddress);
13✔
40
    }
41

42
    // TODO: check if good idea to use tload to read cached ticks
43
    /// @inheritdoc IEOracle
44
    // TODO: as we will use a try statement in the calling method, we could avoid using try/catch statements here to save gas
45
    // provided we do not need to return important information
46
    function convertTokenAmount(
47
        address token,
48
        uint256 amount,
49
        address targetToken
50
    ) external view override returns (uint256 value) {
51
        PoolKey memory key;
45✔
52
        IOracle.ObservationState memory state;
45✔
53

54
        // TODO: no need to use try/catch with known oracle contract
55
        if (token == _ZERO_ADDRESS) {
45✔
56
            (key, state) = _getPool(_ZERO_ADDRESS, targetToken, _getOracle());
14✔
57
            try _getOracle().observe(key, _getSecondsAgos(state.cardinality)) returns (
14✔
58
                int48[] memory tickCumulatives,
59
                uint144[] memory
60
            ) {
61
                uint256 priceX128 = _getPriceX128(tickCumulatives, _getSecondsAgos(state.cardinality));
14✔
62
                value = FullMath.mulDiv(amount, priceX128, 1 << 128); // convert native to token
14✔
63
                return value;
14✔
64
            } catch {
NEW
65
                return value = 0; // Oracle failure or no pair available
×
66
            }
67
        }
68

69
        if (targetToken == _ZERO_ADDRESS) {
31✔
70
            // Convert directly to native
71
            (key, state) = _getPool(_ZERO_ADDRESS, token, _getOracle());
15✔
72

73
            try _getOracle().observe(key, _getSecondsAgos(state.cardinality)) returns (
15✔
74
                int48[] memory tickCumulatives,
75
                uint144[] memory
76
            ) {
77
                uint256 priceX128 = _getPriceX128(tickCumulatives, _getSecondsAgos(state.cardinality));
15✔
78
                value = FullMath.mulDiv(amount, 1 << 128, priceX128); // convert token to ETH
15✔
79
                return value;
15✔
80
            } catch {
NEW
81
                return value = 0; // Oracle failure or no pair available
×
82
            }
83
        }
84

85
        // try and convert token to chain currency
86
        (key, state) = _getPool(_ZERO_ADDRESS, token, _getOracle());
16✔
87

88
        try _getOracle().observe(key, _getSecondsAgos(state.cardinality)) returns (
16✔
89
            int48[] memory tickCumulatives,
90
            uint144[] memory
91
        ) {
92
            uint256 priceX128 = _getPriceX128(tickCumulatives, _getSecondsAgos(state.cardinality));
16✔
93
            uint256 ethAmount = FullMath.mulDiv(amount, 1 << 128, priceX128); // convert token to native
16✔
94

95
            // try and convert chain currency to the target token
96
            (key, state) = _getPool(_ZERO_ADDRESS, targetToken, _getOracle());
16✔
97

98
            // TODO: if base token does not have price feed, nav will be 0. Assert that.
99
            // try to get first conversion
100
            try _getOracle().observe(key, _getSecondsAgos(state.cardinality)) returns (
16✔
101
                int48[] memory tickCumulativesTarget,
102
                uint144[] memory
103
            ) {
104
                priceX128 = _getPriceX128(tickCumulativesTarget, _getSecondsAgos(state.cardinality));
16✔
105
                value = FullMath.mulDiv(ethAmount, priceX128, 1 << 128); // convert native to base token
16✔
106
                return value;
16✔
107
            } catch {
NEW
108
                return value = 0;
×
109
            }
110
        } catch {
NEW
111
            return value = 0;
×
112
        }
113
    }
114

115
    /// @inheritdoc IEOracle
116
    function getOracleAddress() external view override returns (address) {
NEW
117
        return address(_getOracle());
×
118
    }
119

120
    /// @dev This method will return true if the last stored observation has a non-nil timestamp.
121
    /// @dev Adding wrapped native token requires a price feed against navite, as otherwise must warm up EApps in order
122
    /// to have same contract address on all chains.
123
    function hasPriceFeed(address token) external view returns (bool) {
124
        if (token == _ZERO_ADDRESS) {
41✔
125
            return true;
17✔
126
        } else {
127
            // TODO: if we just verify that the observations[0] exists, we can save 1 storage read for this assertion (need to modify internal getter)
128
            (PoolKey memory key, IOracle.ObservationState memory state) = _getPool(_ZERO_ADDRESS, token, _getOracle());
24✔
129

130
            // try and get the last stored observation
131
            try _getOracle().getObservation(key, state.index) returns (
24✔
132
                Observation memory observation
133
            ) {
134
                return observation.blockTimestamp != 0;
24✔
135
            } catch {
NEW
136
                return false;
×
137
            }
138
        }
139
    }
140

141
    /// @notice Returns positive values if token has price feed against chain currency
142
    function getTick(address token) external view override returns (int24 tick, uint16 cardinality) {
143
        PoolKey memory key;
40✔
144
        IOracle.ObservationState memory state;
40✔
145

146
        if (token == _ZERO_ADDRESS) {
40✔
147
            // tick = 0 implies price of 1
148
            return (0, 1);
23✔
149
        } else {
150
            (key, state) = _getPool(_ZERO_ADDRESS, token, _getOracle());
17✔
151

152
            // TODO: should we use a TWAP instead?
153
            // get last stored observation from oracle
154
            (Observation memory observation) = _getOracle().getObservation(key, state.index);
17✔
155

156
            // TODO: a tick could be 0, but this does not mean it is a valid price
157
            return (observation.prevTick, state.cardinality);
17✔
158
        }
159
    }
160

161
    /// @dev Private method to fetch the oracle address
162
    function _getOracle() private view returns (IOracle) {
163
        return _oracle;
204✔
164
    }
165

166
    function _getPool(
167
        address token0,
168
        address token1,
169
        IOracle oracle
170
    ) private view returns (PoolKey memory key, IOracle.ObservationState memory state) {
171
        key = PoolKey({
102✔
172
            currency0: Currency.wrap(token0),
173
            currency1: Currency.wrap(token1),
174
            fee: 0,
175
            tickSpacing: TickMath.MAX_TICK_SPACING,
176
            hooks: IHooks(address(oracle))
177
        });
178
        state = oracle.getState(key);
102✔
179
    }
180

181
    function _getPriceX128(
182
        int48[] memory tickCumulatives,
183
        uint32[] memory secondsAgos
184
    ) private pure returns (uint256 priceX128) {
185
        int56 tickCumulativesDelta = int56(tickCumulatives[1] - tickCumulatives[0]);
61✔
186
        int24 twapTick = int24(tickCumulativesDelta / int56(int32(secondsAgos[0])));
61✔
187
        if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(int32(secondsAgos[0])) != 0)) twapTick--;
61!
188

189
        uint160 sqrtPriceX96 = twapTick.getSqrtPriceAtTick();
61✔
190
        priceX128 = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, 1 << 64);
61✔
191
    }
192

193
    function _getSecondsAgos(uint16 cardinality) private view returns (uint32[] memory) {
194
        // blocktime cannot be lower than 8 seconds on Ethereum, 1 seconds on any other chain
195
        uint16 blockTime = block.chainid == 1 ? 8 : 1;
122!
196
        uint32 maxSecondsAgos = uint32(cardinality * blockTime);
122✔
197
        uint32[] memory secondsAgos = new uint32[](2);
122✔
198
        secondsAgos[0] = maxSecondsAgos > 300 ? 300 : maxSecondsAgos;
122!
199
        secondsAgos[1] = 0;
122✔
200
        return secondsAgos;
122✔
201
    }
202
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc