# zkEVM Oracles adaptation ## Context and use case Tropykus is a lending protocol based on Compound deployed in the RSK network, looking to close the financial access gap in developing countries. As Tropykus progressed, we found out that most of our target users base need undercollateralized loans given that most of them do not have crypto assets to use as a collateral. Therefore we started a research program to find a solution to this issue. As a result of this research we decided to deploy a new protocol forked from AAVE, to include new use cases and new assets in Layer 2 blockchains. ## Oracles in AAVE AAVE has historically worked with Chainlink oracles. As of today, Chainlink does not support zkEVM oracles and we are looking to integrate another feed price provider to fill this role within the AAVE forked contracts. AAVE centralizes the price feeds using its AAVEOracle smart contract. This is the extract of its interface ``` // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; interface IPriceOracleGetter { function getAssetPrice(address _asset) external view returns (uint256); function getAssetsPrices(address[] calldata _assets) external view returns(uint256[] memory); function getSourceOfAsset(address _asset) external view returns(address); function getFallbackOracle() external view returns(address); } ``` When we call the `getAssetPrice()` function we pass the address of the underlying token as a parameter and it returns the current price of the token. ## Chainlink Aggregators The current implementation of AAVE gets the price from Chainlink Aggregators. This is the interface of this contract. ``` // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external view returns (uint256); function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); } ``` ## Adapting Api3 to Chainlink Aggregators We deployed some mock contracts that get adapt the price feeds from Api3 and exposes them through the Aggregator interface in zkEVM Testnet. We would like to have some technical support on how to write a secure smart contract that acts as an adapter between the interface of Api3 and the one from Chainlink ## Mock adapters created We adapted the original DatFeedReader example from API3 to replicate the Chainlink Aggregator contract as this. ``` // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import "./Ownable.sol"; import "@api3/contracts/v0.8/interfaces/IProxy.sol"; contract MockApi3Aggregator is Ownable { // This contract reads from a single proxy. Your contract can read from // multiple proxies. address public proxy; event ProxyUpdated(address _proxy); constructor(address _proxy) { setProxy(_proxy); } function latestAnswer() external view returns (int256 value) { (value, ) = readDataFeed(); } function latestTimestamp() external view returns (uint256 timestamp) { ( , timestamp) = readDataFeed(); } function readDataFeed() internal view returns (int224 value, uint256 timestamp) { (value, timestamp) = IProxy(proxy).read(); // If you have any assumptions about `value` and `timestamp`, make sure // to validate them right after reading from the proxy. For example, // if the value you are reading is the spot price of an asset, you may // want to reject non-positive values... // require(value > 0, "Value not positive"); // ...and if the data feed is being updated with a one day-heartbeat // interval, you may want to check for that. // require( // timestamp + 1 days > block.timestamp, // "Timestamp older than one day" // ); // Try to be strict about validations, but be wary of: // (1) Overly strict validation that may invalidate valid values // (2) Mutable validation parameters that are controlled by a trusted // party (eliminates the trust-minimization guarantees of first-party // oracles) // (3) Validation parameters that need to be tuned according to // external conditions (if these are forgotten to be handled, it will // result in (1), look up the Venus Protocol exploit related to LUNA) // After validation, you can implement your contract logic here. } function getTokenType() external pure returns (uint256) { return 1; } // Updating the proxy address is a security-critical action. In this // example, only the owner is allowed to do so. // You may want to update your proxy to switch to another data feed, enable // OEV support, or even switch to another oracle solution. Implementing a // method to update proxies is highly recommended. function setProxy(address _proxy) public onlyOwner { proxy = _proxy; emit ProxyUpdated(_proxy); } } ``` The original MockAggregator is the following: ``` // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; contract MockAggregator { int256 private _latestAnswer; event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp); constructor(int256 _initialAnswer) public { _latestAnswer = _initialAnswer; emit AnswerUpdated(_initialAnswer, 0, now); } function latestAnswer() external view returns (int256) { return _latestAnswer; } function getTokenType() external view returns (uint256) { return 1; } // function getSubTokens() external view returns (address[] memory) { // TODO: implement mock for when multiple subtokens. Maybe we need to create diff mock contract // to call it from the migration for this case?? // } } ``` ## Deployed contracts in zkEVM Mainnet | Contract | Address | | -------- | -------- | | Api3Aggregator USDC/USD | 0xbb08684ad198410a19cfa8f80b90f0ae99323a76 | | Api3Aggregator ETH/USD | 0x9660310567bfE9c7555E5FBdbB8DD30518983C08 | | Api3Aggregator WBTC/USD | 0xEc36899D4Cd6f72ba610aF6AC3B60ed1e954124a | | API3 USDC/USD Proxy | 0x8DF7d919Fe9e866259BB4D135922c5Bd96AF6A27 | | API3 ETH/USD Proxy | 0x26690F9f17FdC26D419371315bc17950a0FC90eD | | API3 WBTC/USD Proxy | 0x28Cac6604A8f2471E19c8863E8AfB163aB60186a | ## ABIs ### Aggregators ``` [ { "inputs": [ { "internalType": "address", "name": "_proxy", "type": "address" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "int256", "name": "current", "type": "int256" }, { "indexed": true, "internalType": "uint256", "name": "roundId", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" } ], "name": "AnswerUpdated", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "OwnershipTransferred", "type": "event" }, { "inputs": [], "name": "getTokenType", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestAnswer", "outputs": [ { "internalType": "int256", "name": "value", "type": "int256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestTimestamp", "outputs": [ { "internalType": "uint256", "name": "timestamp", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "proxy", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "_proxy", "type": "address" } ], "name": "setProxy", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ], ``` ### Proxy ``` [ { "inputs": [], "name": "api3ServerV1", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "read", "outputs": [ { "internalType": "int224", "name": "value", "type": "int224" }, { "internalType": "uint32", "name": "timestamp", "type": "uint32" } ], "stateMutability": "view", "type": "function" } ], ```