Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Merge pull request #526 from rainprotocol/2023-01-22-op-extern
Browse files Browse the repository at this point in the history
extern op interface extern
  • Loading branch information
thedavidmeister authored Jan 31, 2023
2 parents 4543fe8 + 0cfb12f commit 891b44d
Show file tree
Hide file tree
Showing 14 changed files with 492 additions and 12 deletions.
5 changes: 4 additions & 1 deletion contracts/chainlink/LibChainlink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ library LibChainlink {
}

// Safely cast the answer to uint256 and scale it to 18 decimal FP.
// We round up because reporting a non-zero price as zero can cause
// issues downstream. This rounding up only happens if the values are
// being scaled down.
return
answer_.toUint256().scale18(
AggregatorV3Interface(feed_).decimals(),
Math.Rounding.Down
Math.Rounding.Up
);
}
}
2 changes: 1 addition & 1 deletion contracts/interpreter/deploy/IExpressionDeployerV1.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.17;
pragma solidity ^0.8.17;

/// Config required to build a new `State`.
/// @param sources Sources verbatim. These sources MUST be provided in their
Expand Down
24 changes: 24 additions & 0 deletions contracts/interpreter/extern/IInterpreterExternV1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.15;

type EncodedExternDispatch is uint256;
type ExternDispatch is uint256;

/// @title IInterpreterExternV1
/// Handle a single dispatch from some calling contract with an array of
/// inputs and array of outputs. Ostensibly useful to build "word packs" for
/// `IInterpreterV1` so that less frequently used words can be provided in
/// a less efficient format, but without bloating the base interpreter in
/// terms of code size. Effectively allows unlimited words to exist as externs
/// alongside interpreters.
interface IInterpreterExternV1 {
/// Handles a single dispatch.
/// @param dispatch_ Encoded information about the extern to dispatch.
/// Analogous to the opcode/operand in the interpreter.
/// @param inputs_ The array of inputs for the dispatched logic.
/// @return outputs_ The result of the dispatched logic.
function extern(
ExternDispatch dispatch_,
uint256[] memory inputs_
) external view returns (uint256[] memory outputs_);
}
17 changes: 17 additions & 0 deletions contracts/interpreter/extern/LibExtern.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.15;

import "./IInterpreterExternV1.sol";

library LibExtern {
function decode(
EncodedExternDispatch dispatch_
) internal pure returns (IInterpreterExternV1, ExternDispatch) {
return (
IInterpreterExternV1(
address(uint160(EncodedExternDispatch.unwrap(dispatch_)))
),
ExternDispatch.wrap(EncodedExternDispatch.unwrap(dispatch_) >> 160)
);
}
}
5 changes: 4 additions & 1 deletion contracts/interpreter/ops/AllStandardOps.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "./core/OpContext.sol";
import "./core/OpContextRow.sol";
import "./core/OpDebug.sol";
import "./core/OpDoWhile.sol";
import "./core/OpExtern.sol";
import "./core/OpFoldContext.sol";
import "./core/OpGet.sol";
import "./core/OpLoopN.sol";
Expand Down Expand Up @@ -71,7 +72,7 @@ import "./tier/OpUpdateTimesForTierRange.sol";
error BadDynamicLength(uint256 dynamicLength, uint256 standardOpsLength);

/// @dev Number of ops currently provided by `AllStandardOps`.
uint256 constant ALL_STANDARD_OPS_LENGTH = 61;
uint256 constant ALL_STANDARD_OPS_LENGTH = 62;

/// @title AllStandardOps
/// @notice Every opcode available from the core repository laid out as a single
Expand Down Expand Up @@ -203,6 +204,7 @@ library AllStandardOps {
OpContextRow.integrity,
OpDebug.integrity,
OpDoWhile.integrity,
OpExtern.integrity,
OpFoldContext.integrity,
OpGet.integrity,
OpLoopN.integrity,
Expand Down Expand Up @@ -292,6 +294,7 @@ library AllStandardOps {
OpContextRow.run,
OpDebug.run,
OpDoWhile.run,
OpExtern.intern,
OpFoldContext.run,
OpGet.run,
OpLoopN.run,
Expand Down
90 changes: 90 additions & 0 deletions contracts/interpreter/ops/core/OpExtern.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.15;

import "../../../math/Binary.sol";
import "../../deploy/LibIntegrityCheck.sol";
import "./OpReadMemory.sol";
import "../../extern/LibExtern.sol";
import "../../run/LibStackPointer.sol";

/// Thrown when the length of results from an extern don't match what the operand
/// defines. This is bad because it implies our integrity check miscalculated the
/// stack which is undefined behaviour.
/// @param expected The length we expected based on the operand.
/// @param actual The length that was returned from the extern.
error BadExternResultsLength(uint256 expected, uint256 actual);

library OpExtern {
using LibIntegrityCheck for IntegrityCheckState;
using LibStackPointer for StackPointer;

function integrity(
IntegrityCheckState memory integrityCheckState_,
Operand operand_,
StackPointer stackTop_
) internal pure returns (StackPointer) {
uint256 inputs_ = Operand.unwrap(operand_) & MASK_5BIT;
uint256 outputs_ = (Operand.unwrap(operand_) >> 5) & MASK_5BIT;
uint256 offset_ = Operand.unwrap(operand_) >> 10;

if (offset_ >= integrityCheckState_.constantsLength) {
revert OutOfBoundsConstantsRead(
integrityCheckState_.constantsLength,
offset_
);
}

return
integrityCheckState_.push(
integrityCheckState_.pop(stackTop_, inputs_),
outputs_
);
}

function intern(
InterpreterState memory interpreterState_,
Operand operand_,
StackPointer stackTop_
) internal view returns (StackPointer) {
IInterpreterExternV1 interpreterExtern_;
ExternDispatch externDispatch_;
uint256 head_;
uint256[] memory tail_;
{
uint256 inputs_ = Operand.unwrap(operand_) & MASK_5BIT;
uint256 offset_ = (Operand.unwrap(operand_) >> 10);

// Mirrors constant opcode.
EncodedExternDispatch encodedDispatch_;
assembly ("memory-safe") {
encodedDispatch_ := mload(add(
mload(add(interpreterState_, 0x20)),
mul(0x20, offset_)
))
}

(interpreterExtern_, externDispatch_) = LibExtern.decode(
encodedDispatch_
);
(head_, tail_) = stackTop_.list(inputs_);
stackTop_ = stackTop_.down(inputs_).down().push(head_);
}

{
uint256 outputs_ = (Operand.unwrap(operand_) >> 5) & MASK_5BIT;

uint256[] memory results_ = interpreterExtern_.extern(
externDispatch_,
tail_
);

if (results_.length != outputs_) {
revert BadExternResultsLength(outputs_, results_.length);
}

stackTop_ = stackTop_.push(results_);
}

return stackTop_;
}
}
2 changes: 1 addition & 1 deletion contracts/interpreter/shared/Rainterpreter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ error UnexpectedOpMetaHash(bytes32 actualOpMeta);

/// @dev Hash of the known store bytecode.
bytes32 constant STORE_BYTECODE_HASH = bytes32(
0xaa747007d5351b774ad5b5fcea64d2d3b0f43b4dcf5d366bf1a61455a3ecef7c
0xbc52db76e944bf5245c516990668f268c9d2f24dc3aa1b06b4f9d128914df383
);

/// @dev Hash of the known op meta.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ error MissingEntrypoint(uint256 expectedEntrypoints, uint256 actualEntrypoints);
/// immutable for any given interpreter so once the expression deployer is
/// constructed and has verified that this matches what the interpreter reports,
/// it can use this constant value to compile and serialize expressions.
bytes constant OPCODE_FUNCTION_POINTERS = hex"09ce09dc0a320a840b020b2e0bc70c910dc60dfb0e190ea10eb00ebe0ecc0eda0eb00ee80ef60f040f130f210f300f3e0f4c0fc40fd30fe20ff11000100f101e10671079108710b910c710d510e310f211011110111f112e113d114c115b116a11791188119611a411b211c011ce11dc11ea11f912081216128d";
bytes constant OPCODE_FUNCTION_POINTERS = hex"09d709e50a3b0a8d0b0b0b370bd00d5b0e250f5a0f8f0fad1035104410521060106e1044107c108a109810a710b510c410d210e01158116711761185119411a311b211fb120d121b124d125b126912771286129512a412b312c212d112e012ef12fe130d131c132a13381346135413621370137e138d139c13aa141c";

/// @title RainterpreterExpressionDeployer
/// @notice Minimal binding of the `IExpressionDeployerV1` interface to the
Expand Down
37 changes: 37 additions & 0 deletions contracts/interpreter/shared/RainterpreterExtern.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.15;

import "../extern/IInterpreterExternV1.sol";
import "../ops/chainlink/OpChainlinkOraclePrice.sol";
import "../run/LibStackPointer.sol";
import "../../array/LibUint256Array.sol";

/// Thrown when the inputs don't match the expected inputs.
error BadInputs(uint256 expected, uint256 actual);

/// EXPERIMENTAL implementation of `IInterpreterExternV1`.
/// Currently only implements the Chainlink oracle price opcode as a starting
/// point to test and flesh out externs generally.
/// Hopefully one day the idea of there being only a single extern contract seems
/// quaint.
contract RainterpreterExtern is IInterpreterExternV1 {
using LibStackPointer for uint256[];
using LibStackPointer for StackPointer;
using LibUint256Array for uint256;

/// @inheritdoc IInterpreterExternV1
function extern(
ExternDispatch,
uint256[] memory inputs_
) external view returns (uint256[] memory) {
if (inputs_.length != 2) {
revert BadInputs(2, inputs_.length);
}
return
inputs_
.asStackPointerAfter()
.applyFn(OpChainlinkOraclePrice.f)
.peek()
.arrayFrom();
}
}
46 changes: 40 additions & 6 deletions test/Interpreter/Ops/Chainlink/chainlinkPrice.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import type { AggregatorV3Interface } from "../../../../typechain";
import type {
AggregatorV3Interface,
RainterpreterExtern,
Rainterpreter,
IInterpreterV1Consumer,
} from "../../../../typechain";
import {
AllStandardOps,
assertError,
eighteenZeros,
externOperand,
getBlockTimestamp,
memoryOperand,
MemoryType,
Expand All @@ -13,18 +19,38 @@ import {
import { FakeContract, smock } from "@defi-wonderland/smock";
import { concat } from "ethers/lib/utils";
import { assert } from "chai";
import { iinterpreterV1ConsumerDeploy } from "../../../../utils/deploy/test/iinterpreterV1Consumer/deploy";
import {
expressionConsumerDeploy,
iinterpreterV1ConsumerDeploy,
} from "../../../../utils/deploy/test/iinterpreterV1Consumer/deploy";
import {
rainterpreterDeploy,
rainterpreterExtern,
} from "../../../../utils/deploy/interpreter/shared/rainterpreter/deploy";
import { ethers } from "hardhat";

const Opcode = AllStandardOps;

describe("CHAINLINK_PRICE Opcode tests", async function () {
let fakeChainlinkOracle: FakeContract<AggregatorV3Interface>;
let rainInterpreter: Rainterpreter;
let logic: IInterpreterV1Consumer;
let rainInterpreterExtern: RainterpreterExtern;


beforeEach(async () => {
fakeChainlinkOracle = await smock.fake("AggregatorV3Interface");
rainInterpreter = await rainterpreterDeploy();

rainInterpreterExtern = await rainterpreterExtern();
const consumerFactory = await ethers.getContractFactory(
"IInterpreterV1Consumer"
);
logic = (await consumerFactory.deploy()) as IInterpreterV1Consumer;
await logic.deployed();
});

it("should revert if price is stale", async () => {
it("should revert if price is stale", async () => {

const fakeChainlinkOracle = await smock.fake("AggregatorV3Interface");
const chainlinkPriceData = {
roundId: 1,
answer: 123 + eighteenZeros,
Expand Down Expand Up @@ -81,7 +107,9 @@ describe("CHAINLINK_PRICE Opcode tests", async function () {
);
});

it("should revert if price is 0", async () => {
it("should revert if price is 0", async () => {
const fakeChainlinkOracle = await smock.fake("AggregatorV3Interface");

const chainlinkPriceData = {
roundId: 1,
answer: 0 + eighteenZeros,
Expand Down Expand Up @@ -127,6 +155,8 @@ describe("CHAINLINK_PRICE Opcode tests", async function () {
});

it("should correctly scale answer from 6 decimal to 18 decimal FP", async () => {
const fakeChainlinkOracle = await smock.fake("AggregatorV3Interface");

const chainlinkPriceData = {
roundId: 1,
answer: 123 + sixZeros,
Expand Down Expand Up @@ -169,6 +199,8 @@ describe("CHAINLINK_PRICE Opcode tests", async function () {
});

it("should get price from chainlink oracle", async () => {
const fakeChainlinkOracle = await smock.fake("AggregatorV3Interface");

const chainlinkPriceData = {
roundId: 1,
answer: 123 + eighteenZeros,
Expand Down Expand Up @@ -210,4 +242,6 @@ describe("CHAINLINK_PRICE Opcode tests", async function () {

assert(price_.eq(123 + eighteenZeros));
});


});
Loading

0 comments on commit 891b44d

Please sign in to comment.