The current price implementation for the VotiumStrategy token uses a potentially invalid Chainlink response. This price is then used to calculate the price of AfEth and, subsequently, the amount of tokens to mint while depositing.
The price of VotiumStrategy tokens are determined by taking the amount of deposited CVX in the strategy, and multiplied by the current price of CVX in terms of ETH. This price is fetched using Chainlink in the ethPerCvx() function:
156: function ethPerCvx(bool _validate) public view returns (uint256) {
157: ChainlinkResponse memory cl;
158: try chainlinkCvxEthFeed.latestRoundData() returns (
159: uint80 roundId,
160: int256 answer,
161: uint256 /* startedAt */,
162: uint256 updatedAt,
163: uint80 /* answeredInRound */
164: ) {
165: cl.success = true;
166: cl.roundId = roundId;
167: cl.answer = answer;
168: cl.updatedAt = updatedAt;
169: } catch {
170: cl.success = false;
171: }
172: // verify chainlink response
173: if (
174: (!_validate ||
175: (cl.success == true &&
176: cl.roundId != 0 &&
177: cl.answer >= 0 &&
178: cl.updatedAt != 0 &&
179: cl.updatedAt <= block.timestamp &&
180: block.timestamp - cl.updatedAt <= 25 hours))
181: ) {
182: return uint256(cl.answer);
183: } else {
184: revert ChainlinkFailed();
185: }
186: }
As we can see from the previous snippet of code, if the _validate flag is off, then no validation is done, it can even return an uninitialized response from a failed call given the usage of the try/catch structure. This means that it can invalid price, stale price, or even zero when the call fails.
The VotiumStrategy price() function calls ethPerCvx(false), which means it carries forward any invalid CVX/ETH price.
31: function price() external view override returns (uint256) {
32: return (cvxPerVotium() * ethPerCvx(false)) / 1e18;
33: }
The price of VotiumStrategy is then used in the AfEth contract to calculate its price and determine the amount of tokens to mint in deposit()
<https://github.com/code-423n4/2023-09-asymmetry/blob/main/contracts/AfEth.sol#L133-L169>
133: function price() public view returns (uint256) {
134: if (totalSupply() == 0) return 1e18;
135: AbstractStrategy vEthStrategy = AbstractStrategy(vEthAddress);
136: uint256 safEthValueInEth = (ISafEth(SAF_ETH_ADDRESS).approxPrice(true) *
137: safEthBalanceMinusPending()) / 1e18;
138: uint256 vEthValueInEth = (vEthStrategy.price() *
139: vEthStrategy.balanceOf(address(this))) / 1e18;
140: return ((vEthValueInEth + safEthValueInEth) * 1e18) / totalSupply();
141: }
148: function deposit(uint256 _minout) external payable virtual {
149: if (pauseDeposit) revert Paused();
150: uint256 amount = msg.value;
151: uint256 priceBeforeDeposit = price();
152: uint256 totalValue;
153:
154: AbstractStrategy vStrategy = AbstractStrategy(vEthAddress);
155:
156: uint256 sValue = (amount * ratio) / 1e18;
157: uint256 sMinted = sValue > 0
158: ? ISafEth(SAF_ETH_ADDRESS).stake{value: sValue}(0)
159: : 0;
160: uint256 vValue = (amount * (1e18 - ratio)) / 1e18;
161: uint256 vMinted = vValue > 0 ? vStrategy.deposit{value: vValue}() : 0;
162: totalValue +=
163: (sMinted * ISafEth(SAF_ETH_ADDRESS).approxPrice(true)) +
164: (vMinted * vStrategy.price());
165: if (totalValue == 0) revert FailedToDeposit();
166: uint256 amountToMint = totalValue / priceBeforeDeposit;
167: if (amountToMint < _minout) revert BelowMinOut();
168: _mint(msg.sender, amountToMint);
169: }
The VotiumStrategy price is first used in line 138 to calculate its TVL (vEthValueInEth). Any invalid price here will also mean an invalid price for AfEth.
Then both the AfEth price (line 151) and again the VotiumStrategy price (line 164) are used in deposit() to calculate the number of minted tokens. Depending on the direction of the wrong price, this means that the user will be minted more or less tokens than it should.
Let’s suppose the Chainlink feed is stale and the current price of CVX/ETH has increased since then.
Change the ethPerCvx() argument to true to make sure prices coming from Chainlink are correctly validated.
function price() external view override returns (uint256) {
- return (cvxPerVotium() * ethPerCvx(false)) / 1e18;
+ return (cvxPerVotium() * ethPerCvx(true)) / 1e18;
}
Oracle
The text was updated successfully, but these errors were encountered:
All reactions