Skip to content

Commit

Permalink
Added oracle mocking provider structure
Browse files Browse the repository at this point in the history
  • Loading branch information
alejandro-immunefi committed Dec 29, 2023
1 parent 2c26129 commit 1096c55
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 0 deletions.
37 changes: 37 additions & 0 deletions src/oracle/OracleMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pragma solidity ^0.8.0;

import "./OracleProvider.sol";

import "forge-std/console.sol";

abstract contract OracleMock {
using OracleProvider for OracleProviders;

/**
* @dev Stack of oracle providers used for managing multiple oracle interactions.
*/
OracleProviders[] internal _ops;

/**
* @dev Mocks oracle data by pushing an oracle provider onto the stack, calling its mockOracleData method,
* and then popping it from the stack. This simulates the behavior of an oracle provider in a controlled manner.
* @param op The OracleProvider instance to use for mocking data.
* @param data The data to be used in the mock call to the oracle.
*/
function mockOracleData(OracleProviders op, bytes memory data) internal virtual {
_ops.push(op);
op.mockOracleData(data);
_ops.pop();
}

/**
* @dev Retrieves the top-most oracle provider from the stack.
* @return op The current oracle provider at the top of the stack.
*/
function currentOracleProvider() internal view returns (OracleProviders op) {
if (_ops.length > 0) {
return _ops[_ops.length - 1];
}
return OracleProviders.NONE;
}
}
36 changes: 36 additions & 0 deletions src/oracle/OracleProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
pragma solidity ^0.8.0;

import {ChainlinkOracle} from "./lib/ChainlinkOracle.sol";

enum OracleProviders {
NONE,
CHAINLINK
}

library OracleProvider {
/**
* @dev Returns the name of the specified oracle provider.
* @param op The oracle provider enumeration value.
* @return A string representing the name of the oracle provider.
*/
function name(OracleProviders op) internal pure returns (string memory) {
if (op == OracleProviders.CHAINLINK) {
return "Chainlink";
} else {
return "None";
}
}

/**
* @dev Mocks oracle data for the specified OracleProvider. This function is primarily used for testing.
* @param op The oracle provider to mock data for.
* @param data The mock data to be used.
*/
function mockOracleData(OracleProviders op, bytes memory data) internal {
if (op == OracleProviders.CHAINLINK) {
ChainlinkOracle.mockOracleData(data);
} else {
revert("OracleProvider: Provider is not supported");
}
}
}
57 changes: 57 additions & 0 deletions src/oracle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## Vulnerability Type
This template is designed for developing attack proof of concepts (PoCs) that exploit vulnerabilities which rely on data returned by an oracle. Please note: When crafting a PoC which manipulates oracle data, the oracle manipulation must be achievable independently from the attack. Oracle mocking allows for easier testing of situations where the mocked oracle data is known to be achievable by other means. If the data returned is not achievable in normal operation of the Oracle, the vulnerability is not considered valid. The template supports manipulation of the following oracles:

<details>
<summary>

### Ethereum
</summary>

| Network | Oracle Provider | Library |
| ------- | --------------- | ------- |
| Ethereum | Chainlink | [Chainlink](./lib/ChainlinkOracle.sol) |

</details>

## Usage
The following attack contract demonstrates simple oracle data manipulation:
* [OracleManipulationExample](./examples/OracleManipulationExample.sol)

Extend the `OracleMock` contract:
```Solidity
contract Attack is OracleMock { }
```

Call mockOracleData(OracleProviders op, bytes memory data) to manipulate the data returned by the oracle. This function will simulate oracle data, which can be used in _executeAttack() to carry out the attack logic.

```Solidity
pragma solidity ^0.8.0;
import "@immunefi/src/oracle/OracleMock.sol";
import { OracleProviders } from "@immunefi/src/oracle/OracleProvider.sol";
contract Attack is PoC, OracleMock {
// Other contract details...
function initiateAttack() external {
// Call initiateAttack to start the attack process
// This could include setting up necessary state variables
}
function _executeAttack() internal override {
// Example data to be used for the mock oracle
bytes memory data = abi.encodePacked(/* encoded data here */);
// Mocking oracle data for the Chainlink provider
mockOracleData(OracleProviders.CHAINLINK, data);
// Implement your attack logic here, using the mocked data
// ...
}
function _completeAttack() internal override {
// Complete the attack logic, clean up, etc.
// ...
}
}
```
118 changes: 118 additions & 0 deletions src/oracle/lib/ChainlinkOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
pragma solidity ^0.8.0;

import "../../tokens/Tokens.sol";

/**
* ChainlinkOracle is a library used to facilitate interactions with Chainlink Oracles.
* This includes functions to mock oracle data for testing and to retrieve context information
* specific to the blockchain environment the contract is operating in.
*/
library ChainlinkOracle {
struct Context {
EACAggregatorProxy aggregator;
}

/**
* @dev Mocks oracle data for testing purposes. This function allows simulating oracle data
* within a test environment, enabling the testing of contract interactions with oracles.
* @param data Byte array containing the mocked data to be used in the oracle simulation.
*/
function mockOracleData(bytes memory data) internal {
Context memory context = context();

// It's likely we can generalize this function to take a token pair to mock the returned price of, but we should provide a generic
// function which can be used to mock any oracle data, not just price data. This way the Oracle specific library can have their own
// wrapper function which serializes/deserializes the generic mock oracle data into the appropriate parameters.

// Code to mock the oracle call...
}

/**
* @dev Retrieves the context information relevant to the current blockchain environment.
* This function checks the chain ID and returns the corresponding context information,
* allowing the contract to adapt to different blockchain environments.
* @return Context The context information for the current blockchain.
*/
function context() internal view returns (Context memory) {
EACAggregatorProxy aggregator;

if (block.chainid == 1) {
// Ethereum mainnet
aggregator = EACAggregatorProxy(0x72AFAECF99C9d9C8215fF44C77B94B99C28741e8);
} else {
revert("ChainlinkOracle: Chain not supported");
}

return Context(aggregator);
}

// /**
// * @dev Helper function which encode the mock oracle data
// * @param param1
// * @param param2
// * @param param3
// */
// function unpackData(type param1, type param2, type param3)
// internal
// pure
// returns (bytes memory data)
// {
// return abi.encode(param1, param2, param3);
// }

// /**
// * @dev Helper function which decodes the mock oracle data
// * @param data The data of the mock oracle call
// */
// function unpackData(bytes calldata data)
// internal
// pure
// returns (address token0, address token1, uint256 price)
// {
// (token0, token1, price) = abi.decode(data, (address, address, uint256));
// return (asset, amount, fee, params);
// }
}

interface EACAggregatorProxy {
event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);
event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
event OwnershipTransferRequested(address indexed from, address indexed to);
event OwnershipTransferred(address indexed from, address indexed to);

function acceptOwnership() external;
function accessController() external view returns (address);
function aggregator() external view returns (address);
function confirmAggregator(address _aggregator) external;
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function getAnswer(uint256 _roundId) external view returns (int256);
function getRoundData(uint80 _roundId)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function getTimestamp(uint256 _roundId) external view returns (uint256);
function latestAnswer() external view returns (int256);
function latestRound() external view returns (uint256);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function latestTimestamp() external view returns (uint256);
function owner() external view returns (address);
function phaseAggregators(uint16) external view returns (address);
function phaseId() external view returns (uint16);
function proposeAggregator(address _aggregator) external;
function proposedAggregator() external view returns (address);
function proposedGetRoundData(uint80 _roundId)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function proposedLatestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function setController(address _accessController) external;
function transferOwnership(address _to) external;
function version() external view returns (uint256);
}

0 comments on commit 1096c55

Please sign in to comment.