Skip to content

Commit

Permalink
test(evm-e2e): add E2E test using the Nibiru Oracle's ChainLink impl (#…
Browse files Browse the repository at this point in the history
…2156)

* test(evm-e2e): add E2E test using the Nibiru Oracle's ChainLink impl

* chore: changelog
  • Loading branch information
Unique-Divine authored Jan 10, 2025
1 parent 5533a2a commit da4c2c7
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ the `@nibiruchain/solidity` package.
- [#2154](https://github.com/NibiruChain/nibiru/pull/2154) - fix(evm):
JSON encoding for the `EIP55Addr` struct was not following the Go conventions and
needed to include double quotes around the hexadecimal string.
- [#2156](https://github.com/NibiruChain/nibiru/pull/2156) - test(evm-e2e): add E2E test using the Nibiru Oracle's ChainLink impl

#### Nibiru EVM | Before Audit 2 - 2024-12-06

Expand Down
98 changes: 98 additions & 0 deletions evm-e2e/contracts/NibiruOracleChainLinkLike.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import '@nibiruchain/solidity/contracts/IOracle.sol';

/// @title NibiruOracleChainLinkLike
/// @notice This contract serves as a ChainLink-like data feed that sources its
/// "answer" value from the Nibiru Oracle system. The Nibiru Oracle gives price
/// data with 18 decimals universally, and that 18-decimal answer is scaled to
/// have the number of decimals specified by "decimals()". This is set at the
/// time of deployment.
/// _ _ _____ ____ _____ _____ _ _
/// | \ | ||_ _|| _ \|_ _|| __ \ | | | |
/// | \| | | | | |_) | | | | |__) || | | |
/// | . ` | | | | _ < | | | _ / | | | |
/// | |\ | _| |_ | |_) |_| |_ | | \ \ | |__| |
/// |_| \_||_____||____/|_____||_| \_\ \____/
///
contract NibiruOracleChainLinkLike is ChainLinkAggregatorV3Interface {
string public pair;
uint8 public _decimals;

constructor(string memory _pair, uint8 _dec) {
require(_dec <= 18, 'Decimals cannot exceed 18');
require(bytes(_pair).length > 0, 'Pair string cannot be empty');
pair = _pair;
_decimals = _dec;
}

function decimals() external view override returns (uint8) {
return _decimals;
}

/// @notice Returns a human-readable description of the oracle and its data
/// feed identifier (pair) in the Nibiru Oracle system
function description() external view override returns (string memory) {
return string.concat('Nibiru Oracle ChainLink-like price feed for ', pair);
}

/// @notice Oracle version number. Hardcoded to 1.
function version() external pure override returns (uint256) {
return 1;
}

/// @notice Returns the latest data from the Nibiru Oracle.
/// @return roundId The block number when the answer was published onchain.
/// @return answer Data feed result scaled to the precision specified by
/// "decimals()"
/// @return startedAt UNIX timestamp in seconds when "answer" was published.
/// @return updatedAt UNIX timestamp in seconds when "answer" was published.
/// @return answeredInRound The ID of the round where the answer was computed.
/// Since the Nibiru Oracle does not have ChainLink's system of voting
/// rounds, this argument is a meaningless, arbitrary constant.
function latestRoundData()
public
view
override
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
(
uint80 _roundId,
int256 answer18Dec,
uint256 _startedAt,
uint256 _updatedAt,
uint80 _answeredInRound
) = NIBIRU_ORACLE.chainLinkLatestRoundData(pair);
answer = scaleAnswerToDecimals(answer18Dec);
return (_roundId, answer, _startedAt, _updatedAt, _answeredInRound);
}

/// @notice Returns the latest data from the Nibiru Oracle. Historical round
/// retrieval is not supported. This method is a duplicate of
/// "latestRoundData".
/// @return roundId The block number when the answer was published onchain.
/// @return answer Data feed result scaled to the precision specified by
/// "decimals()"
/// @return startedAt UNIX timestamp in seconds when "answer" was published.
/// @return updatedAt UNIX timestamp in seconds when "answer" was published.
/// @return answeredInRound The ID of the round where the answer was computed.
/// Since the Nibiru Oracle does not have ChainLink's system of voting
/// rounds, this argument is a meaningless, arbitrary constant.
function getRoundData(
uint80
)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return latestRoundData();
}

function scaleAnswerToDecimals(int256 answer18Dec) internal view returns (int256 answer) {
// Default answers are in 18 decimals.
// Scale down to the decimals specified in the constructor.
uint8 pow10 = 18 - _decimals;
return answer18Dec / int256(10 ** pow10);
}
}
40 changes: 40 additions & 0 deletions evm-e2e/test/nibiru_oracle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect, test } from '@jest/globals';
import { toBigInt } from 'ethers';
import { deployContractNibiruOracleChainLinkLike } from './utils';

test('NibiruOracleChainLinkLike implements ChainLink AggregatorV3Interface', async () => {
const { oraclePair, contract } = await deployContractNibiruOracleChainLinkLike();

const oracleAddr = await contract.getAddress();
expect(oracleAddr).not.toBeFalsy();

const decimals = await contract.decimals();
expect(decimals).toEqual(BigInt(8));

const description = await contract.description();
expect(description).toEqual(`Nibiru Oracle ChainLink-like price feed for ${oraclePair}`);

const version = await contract.version();
expect(version).toEqual(1n);

// latestRoundData
const genesisEthUsdPrice = 2000n;
{
const { roundId, answer, startedAt, updatedAt, answeredInRound } = await contract.latestRoundData();
expect(roundId).toEqual(0n); // price is from genesis block
expect(startedAt).toBeGreaterThan(1n);
expect(updatedAt).toBeGreaterThan(1n);
expect(answeredInRound).toEqual(420n);
expect(answer).toEqual(genesisEthUsdPrice * toBigInt(1e8));
}

// getRoundData
{
const { roundId, answer, startedAt, updatedAt, answeredInRound } = await contract.getRoundData(0n);
expect(roundId).toEqual(0n); // price is from genesis block
expect(startedAt).toBeGreaterThan(1n);
expect(updatedAt).toBeGreaterThan(1n);
expect(answeredInRound).toEqual(420n);
expect(answer).toEqual(genesisEthUsdPrice * toBigInt(1e8));
}
});
17 changes: 16 additions & 1 deletion evm-e2e/test/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { account } from './setup';
import { parseEther, toBigInt, TransactionRequest, Wallet } from 'ethers';
import { ContractTransactionResponse, parseEther, toBigInt, TransactionRequest, Wallet } from 'ethers';
import {
InifiniteLoopGas__factory,
SendNibi__factory,
TestERC20__factory,
EventsEmitter__factory,
TransactionReverter__factory,
NibiruOracleChainLinkLike__factory,
NibiruOracleChainLinkLike,
} from '../types';

export const alice = Wallet.createRandom();
Expand Down Expand Up @@ -66,3 +68,16 @@ export const sendTestNibi = async () => {
console.log(txResponse);
return txResponse;
};

export const deployContractNibiruOracleChainLinkLike = async (): Promise<{
oraclePair: string;
contract: NibiruOracleChainLinkLike & {
deploymentTransaction(): ContractTransactionResponse;
};
}> => {
const oraclePair = 'ueth:uuusd';
const factory = new NibiruOracleChainLinkLike__factory(account);
const contract = await factory.deploy(oraclePair, toBigInt(8));
await contract.waitForDeployment();
return { oraclePair, contract };
};
8 changes: 5 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ clean-cache:
go clean -cache -testcache -modcache

# Generate protobuf-based types in Golang
proto-gen:
gen-proto:
#!/usr/bin/env bash
make proto-gen
Expand All @@ -42,8 +42,6 @@ gen-embeds:
go run "gen-abi/main.go"
log_success "Saved ABI JSON files to $embeds_dir/abi for npm publishing"

alias gen-proto := proto-gen

# Generate the Nibiru Token Registry files
gen-token-registry:
go run token-registry/main/main.go
Expand Down Expand Up @@ -121,6 +119,10 @@ test-chaosnet:
which_ok nibid
bash contrib/scripts/chaosnet.sh
# Alias for "gen-proto"
proto-gen:
just gen-proto

# Stops any `nibid` processes, even if they're running in the background.
stop:
kill $(pgrep -x nibid) || true
Expand Down

0 comments on commit da4c2c7

Please sign in to comment.