diff --git a/contracts/interpreter/deploy/LibIntegrityCheck.sol b/contracts/interpreter/deploy/LibIntegrityCheck.sol index 8cace1c22..e865d2e28 100644 --- a/contracts/interpreter/deploy/LibIntegrityCheck.sol +++ b/contracts/interpreter/deploy/LibIntegrityCheck.sol @@ -16,6 +16,16 @@ StackPointer constant INITIAL_STACK_BOTTOM = StackPointer.wrap( type(uint256).max / 2 ); +/// Thrown by an integrity check on an op if the operand exceeds the max value +/// that it can handle. MAY be for a subset of all the operand bits if the range +/// is over a destructured value from the whole operand. +error OperandOverflow(uint256 maximum, uint256 actual); + +/// Thrown by an integrity check on an op if the operand subseeds the min value +/// that it can handle. MAY be for a subset of all the operand bits if the range +/// is over a destructured value from the whole operand. +error OperandUnderflow(uint256 minimum, uint256 actual); + /// It is a misconfiguration to set the initial stack bottom to zero or some /// small value as this trivially exposes the integrity check to potential /// underflow issues that are gas intensive to repeatedly guard against on every diff --git a/contracts/interpreter/ops/crypto/OpHash.sol b/contracts/interpreter/ops/crypto/OpHash.sol index ea7af1c42..21bd0cc3b 100644 --- a/contracts/interpreter/ops/crypto/OpHash.sol +++ b/contracts/interpreter/ops/crypto/OpHash.sol @@ -6,6 +6,7 @@ import "../../../array/LibUint256Array.sol"; import "../../../type/LibCast.sol"; import "../../run/LibInterpreterState.sol"; import "../../deploy/LibIntegrityCheck.sol"; +import "hardhat/console.sol"; /// @title OpHash /// @notice Opcode for hashing a list of values. @@ -23,6 +24,14 @@ library OpHash { Operand operand_, StackPointer stackTop_ ) internal pure returns (StackPointer) { + if (Operand.unwrap(operand_) == 0) { + revert OperandUnderflow(1, 0); + } + + if (Operand.unwrap(operand_) > 255) { + revert OperandOverflow(255, Operand.unwrap(operand_)); + } + return integrityCheckState_.applyFn( stackTop_, diff --git a/contracts/interpreter/ops/tier/OpSelectLte.sol b/contracts/interpreter/ops/tier/OpSelectLte.sol index affe09c5d..06cd44d4c 100644 --- a/contracts/interpreter/ops/tier/OpSelectLte.sol +++ b/contracts/interpreter/ops/tier/OpSelectLte.sol @@ -7,9 +7,6 @@ import "../../run/LibInterpreterState.sol"; import "../../deploy/LibIntegrityCheck.sol"; import "../../../math/Binary.sol"; -/// Zero inputs to select lte is NOT supported. -error ZeroInputs(); - /// @title OpSelectLte /// @notice Exposes `TierwiseCombine.selectLte` as an opcode. library OpSelectLte { @@ -23,14 +20,16 @@ library OpSelectLte { StackPointer stackTop_ ) internal pure returns (StackPointer) { unchecked { - uint256 inputs_ = Operand.unwrap(operand_) & MASK_8BIT; - if (inputs_ == 0) { - revert ZeroInputs(); + if (Operand.unwrap(operand_) == 0) { + revert OperandUnderflow(1, 0); } return integrityCheckState_.push( - integrityCheckState_.pop(stackTop_, inputs_) + integrityCheckState_.pop( + stackTop_, + Operand.unwrap(operand_) + ) ); } } diff --git a/contracts/interpreter/shared/RainterpreterExpressionDeployer.sol b/contracts/interpreter/shared/RainterpreterExpressionDeployer.sol index 62a9466b2..312dfc5fb 100644 --- a/contracts/interpreter/shared/RainterpreterExpressionDeployer.sol +++ b/contracts/interpreter/shared/RainterpreterExpressionDeployer.sol @@ -43,12 +43,12 @@ bytes constant OPCODE_FUNCTION_POINTERS = hex"0b360b450b540bd70be50c3b0c8d0d0b0d /// @dev Hash of the known interpreter bytecode. bytes32 constant INTERPRETER_BYTECODE_HASH = bytes32( - 0x46fd8b5ce435ff6aca550c3e278743894f4cea2b97c2b47ff4ce3aabbfced341 + 0x078bc41915d9b0546cf9af619740692cca28489e388a79d381c14e7752de9baf ); /// @dev Hash of the known store bytecode. bytes32 constant STORE_BYTECODE_HASH = bytes32( - 0x33612e3d92c79aeb4108030de9f132698ba8563f5219fa6c32d88b3ea02040ae + 0x529e4a275f269ea91d28c434c037bc10c8a85f2f35a83201b7883b817edc254d ); /// @dev Hash of the known op meta. diff --git a/test/Interpreter/Ops/Crypto/validateMeta.ts b/test/Interpreter/Ops/Crypto/validateMeta.ts new file mode 100644 index 000000000..92a30224b --- /dev/null +++ b/test/Interpreter/Ops/Crypto/validateMeta.ts @@ -0,0 +1,151 @@ +import { assert } from "chai"; +import { BytesLike, concat } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { IInterpreterV1Consumer, Rainterpreter } from "../../../../typechain"; +import { + assertError, + max_uint256, + memoryOperand, + MemoryType, + op, + Opcode, + randomUint256, + standardEvaluableConfig, +} from "../../../../utils"; +import { rainterpreterDeploy } from "../../../../utils/deploy/interpreter/shared/rainterpreter/deploy"; +import deploy1820 from "../../../../utils/deploy/registry1820/deploy"; +import { expressionConsumerDeploy } from "../../../../utils/deploy/test/iinterpreterV1Consumer/deploy"; +import OpHash from "../../../../contracts/interpreter/ops/crypto/OpHash.opmeta.json"; +import { constructByBits, OperandArgs } from "rainlang"; + +describe("HASH Opcode test", async function () { + let rainInterpreter: Rainterpreter; + let logic: IInterpreterV1Consumer; + + before(async () => { + // Deploy ERC1820Registry + const signers = await ethers.getSigners(); + await deploy1820(signers[0]); + rainInterpreter = await rainterpreterDeploy(); + + const consumerFactory = await ethers.getContractFactory( + "IInterpreterV1Consumer" + ); + logic = (await consumerFactory.deploy()) as IInterpreterV1Consumer; + await logic.deployed(); + }); + + // get array of a particualr length + const getArrayOfLength = (length: number) => { + let arr = []; + for (let i = 0; i < length; i++) { + arr.push(randomUint256()); + } + return arr; + }; + + // get a int between min and max inclusive + const randomIntFromInterval = (min: number, max: number) => { + return Math.floor(Math.random() * (max - min + 1) + min); + }; + + // build source array from constants array + const buildSources = (constants: Array, op_: number | number[]) => { + let soruceArray = []; + for (let i = 0; i < constants.length; i++) { + soruceArray.push( + op(Opcode.read_memory, memoryOperand(MemoryType.Constant, i)) + ); + } + soruceArray.push(op(Opcode.hash, op_)); + let sources = concat(soruceArray); + return sources; + }; + + it("should build operand from array of fuzzed? operand values ", async () => { + for (let i = 0; i < 30; i++) { + // get random value for range + let range = randomIntFromInterval(1, 300); + + let operandArgs: OperandArgs = [ + { + name: "", + bits: [0, 7], + validRange: [[1, range]], + }, + ]; + + // array of values that will be build as a single operand + let values = [range]; + const constants = getArrayOfLength(range); + + // Constructing arguments for the constructByBits function + // ref resolveOp method of rain parser + let constructArgs = operandArgs.map((e, i) => { + return { + value: values[i], + bits: e.bits, + computation: e.computation, + validRange: e.validRange, + }; + }); + + // getting the operand + let op_ = constructByBits(constructArgs); + let source = buildSources(constants, op_); + + // Case if operand is zero + if (op_[0] === 0) { + await assertError( + async () => + await expressionConsumerDeploy( + [source], + constants, + rainInterpreter, + 1 + ), + "OperandUnderflow", + "Underflow" + ); + } else if (op_[0] > 255) { + // Case if operand is greater than enforced length + + await assertError( + async () => + await expressionConsumerDeploy( + [source], + constants, + rainInterpreter, + 1 + ), + "OperandOverflow", + "Overflow" + ); + } else { + const expression0 = await expressionConsumerDeploy( + [source], + constants, + rainInterpreter, + 1 + ); + + await logic["eval(address,uint256,uint256[][])"]( + rainInterpreter.address, + expression0.dispatch, + [] + ); + const result = await logic.stackTop(); + + const expectedValue = ethers.utils.solidityKeccak256( + ["uint256[]"], + [constants] + ); + + assert( + result.eq(expectedValue), + `Invalid output, expected ${expectedValue}, actual ${result}` + ); + } + } + }); +}); diff --git a/test/Interpreter/Ops/Token/erc20.ts b/test/Interpreter/Ops/Token/erc20.ts index 297c6bf0c..eac570653 100644 --- a/test/Interpreter/Ops/Token/erc20.ts +++ b/test/Interpreter/Ops/Token/erc20.ts @@ -1,21 +1,30 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { assert } from "chai"; +import { concat } from "ethers/lib/utils"; import { ethers } from "hardhat"; import type { IInterpreterV1Consumer, Rainterpreter, ReserveToken, } from "../../../../typechain"; +import { assertError, randomUint256 } from "../../../../utils"; import { basicDeploy } from "../../../../utils/deploy/basicDeploy"; import { rainterpreterDeploy } from "../../../../utils/deploy/interpreter/shared/rainterpreter/deploy"; import deploy1820 from "../../../../utils/deploy/registry1820/deploy"; import { expressionConsumerDeploy } from "../../../../utils/deploy/test/iinterpreterV1Consumer/deploy"; -import { standardEvaluableConfig } from "../../../../utils/interpreter/interpreter"; +import { + memoryOperand, + MemoryType, + op, + standardEvaluableConfig, +} from "../../../../utils/interpreter/interpreter"; +import { AllStandardOps } from "../../../../utils/interpreter/ops/allStandardOps"; let signers: SignerWithAddress[]; let signer1: SignerWithAddress; let tokenERC20: ReserveToken; +const Opcode = AllStandardOps; describe("RainInterpreter ERC20 ops", async function () { let rainInterpreter: Rainterpreter; @@ -43,6 +52,10 @@ describe("RainInterpreter ERC20 ops", async function () { await tokenERC20.initialize(); }); + const randomUintLen = (len: number): string => { + return ethers.utils.hexZeroPad(ethers.utils.randomBytes(len), len); + }; + it("should return ERC20 total supply", async () => { const { sources, constants } = await standardEvaluableConfig( `_: erc-20-total-supply(${tokenERC20.address});`