Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/generated/Rainterpreter.pointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
pragma solidity =0.8.25;

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0x7844dd6794cf33b630c441de28241af557bf1cecb3463e8dd2ba51da7b32fa96);
bytes32 constant BYTECODE_HASH = bytes32(0x6ba365de507490ff7cb262df51a7bb9b131cf720d1268f29e533709a7cd70c61);

/// @dev The function pointers known to the interpreter for dynamic dispatch.
/// By setting these as a constant they can be inlined into the interpreter
/// and loaded at eval time for very low gas (~100) due to the compiler
/// optimising it to a single `codecopy` to build the in memory bytes array.
bytes constant OPCODE_FUNCTION_POINTERS =
hex"06f8072a074e08da09a309b509c709e00a040a380a490a5a0afc0b1b0bca0c4e0c5f0c700c700c810d0e0d270d3b0d4e0db00dfe0e2f0e7d0f64";
hex"07010733075708e309ac09be09d009e90a0d0a410a520a630b050b240bd30c570c680c790c790c8a0d170d300d440d570db90e070e380e860ed40fbb";
6 changes: 3 additions & 3 deletions src/generated/RainterpreterExpressionDeployer.pointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
pragma solidity =0.8.25;

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0x02455b61a2b9ddfcc272f62e4a9baffdfc9277cb25fdee9de1103210d24ff15e);
bytes32 constant BYTECODE_HASH = bytes32(0x010a570b11206177d53f0f66cd89962bbfca589385bcb0ec0ab4bc6c1dd3b35c);

/// @dev The hash of the meta that describes the contract.
bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x0fe1540732ed38e2456cd4c64af90025053a1def08aa2623d8708f4173c7771b);
bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x55ffa49be99c9effc2f611777c0d7d88251c9d4ca0c66dbfb3f520cd675940bb);

/// @dev The function pointers for the integrity check fns.
bytes constant INTEGRITY_FUNCTION_POINTERS =
hex"0da00e1e0e820ffc1006100610101019103410da10da113611ae100610100ffc0ffc0ffc0ffc11bb100610060ffc11c511ed0ffc11ed101011bb";
hex"0da80e260e8a1004100e100e10181021103c10e210e2113e11b6100e1018100410041004100411c3100e100e100411cd11f5100411f511f5101811c3";
8 changes: 4 additions & 4 deletions src/generated/RainterpreterParser.pointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
pragma solidity =0.8.25;

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0x5ae79f118873b18e79dd113453e372bf50fea0bab70b88a50cdf6e33a57c1653);
bytes32 constant BYTECODE_HASH = bytes32(0xb416945b30bdad60766ef143f600b093a45edb74bf6b644c2d72a654960b6a85);

/// @dev The parse meta that is used to lookup word definitions.
/// The structure of the parse meta is:
Expand All @@ -29,7 +29,7 @@ bytes32 constant BYTECODE_HASH = bytes32(0x5ae79f118873b18e79dd113453e372bf50fea
/// bit count of the previous bloom filter. If we reach the end of the bloom
/// filters then we have a miss.
bytes constant PARSE_META =
hex"01092404900810001008201088000400e0010080000800001028010100080800090002cd70471b14fbe600f861d118d0e20c0e8be82d1c7c7f8004e5ab37174d06d00d67df8415fedd510602c61f08bcae760bcd088610f1c17805ac4b9f0a09bc09160ce432091ef7b011dfb72601a91367121faf890c2b9f4819bd68ce14811fdb034a067c0fffb07907743c4a1a15024513edf10f";
hex"01092404900810009008201088000400e0010080000800001028010100080800090002cd70471c14fbe600f861d118d0e20c0e8be82d1d7c7f8004e5ab37174d06d00d67df8415fedd510602c61f08bcae760bcd088610f1c17805ac4b9f0a09bc09160ce432091ef7b011dfb72601a91367121faf890c2b9f481ae804c119bd68ce14811fdb034a067c0fffb07907743c4a1b15024513edf10f";

/// @dev The build depth of the parser meta.

Expand All @@ -39,11 +39,11 @@ uint8 constant PARSE_META_BUILD_DEPTH = 2;
/// These positional indexes all map to the same indexes looked up in the parse
/// meta.
bytes constant OPERAND_HANDLER_FUNCTION_POINTERS =
hex"1982198219821a571b6e1b6e1b6e1a571a571982198219821b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e19821b6e1b6e";
hex"198a198a198a1a5f1b761b761b761a5f1a5f198a198a198a1b761b761b761b761b761b761b761b761b761b761b761b761b761b761b76198a1b761b76";

/// @dev Every two bytes is a function pointer for a literal parser.
/// Literal dispatches are determined by the first byte(s) of the literal
/// rather than a full word lookup, and are done with simple conditional
/// jumps as the possibilities are limited compared to the number of words we
/// have.
bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"14ca16fc173f17dd";
bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"14d21704174717e5";
14 changes: 7 additions & 7 deletions src/lib/op/LibAllStandardOps.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import {LibOpUint256Pow} from "./math/uint256/LibOpUint256Pow.sol";
import {LibOpAdd} from "./math/LibOpAdd.sol";
// import {LibOpAvg} from "./math/LibOpAvg.sol";
// import {LibOpCeil} from "./math/LibOpCeil.sol";
// import {LibOpMul} from "./math/LibOpMul.sol";
import {LibOpMul} from "./math/LibOpMul.sol";
import {LibOpDiv} from "./math/LibOpDiv.sol";
// import {LibOpE} from "./math/LibOpE.sol";
// import {LibOpExp} from "./math/LibOpExp.sol";
Expand Down Expand Up @@ -105,7 +105,7 @@ import {LibParseLiteralHex} from "../parse/literal/LibParseLiteralHex.sol";
import {LibParseLiteralSubParseable} from "../parse/literal/LibParseLiteralSubParseable.sol";

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

/// @title LibAllStandardOps
/// @notice Every opcode available from the core repository laid out as a single
Expand Down Expand Up @@ -271,7 +271,7 @@ library LibAllStandardOps {
),
// AuthoringMetaV2("min", "Finds the minimum number from all inputs."),
// AuthoringMetaV2("mod", "Modulos the first number by all other numbers. Errors if any divisor is zero."),
// AuthoringMetaV2("mul", "Multiplies all numbers together. Errors if the multiplication exceeds `max-value()`."),
AuthoringMetaV2("mul", "Multiplies all numbers together."),
// AuthoringMetaV2(
// "power",
// "Raises the first number to the power of the second number. Errors if the exponentiation exceeds `max-value()`."
Expand Down Expand Up @@ -485,8 +485,8 @@ library LibAllStandardOps {
// LibParseOperand.handleOperandDisallowed,
// // mod
// LibParseOperand.handleOperandDisallowed,
// // mul
// LibParseOperand.handleOperandDisallowed,
// mul
LibParseOperand.handleOperandDisallowed,
// // power
// LibParseOperand.handleOperandDisallowed,
// // scale-18
Expand Down Expand Up @@ -605,7 +605,7 @@ library LibAllStandardOps {
LibOpMaxValue.integrity,
// LibOpMin.integrity,
// LibOpMod.integrity,
// LibOpMul.integrity,
LibOpMul.integrity,
// LibOpPow.integrity,
// LibOpScale18.integrity,
// // uint256-to-decimal18 is a repeat of scale18.
Expand Down Expand Up @@ -717,7 +717,7 @@ library LibAllStandardOps {
LibOpMaxValue.run,
// LibOpMin.run,
// LibOpMod.run,
// LibOpMul.run,
LibOpMul.run,
// LibOpPow.run,
// LibOpScale18.run,
// // uint256-to-decimal18 is a repeat of scale18.
Expand Down
165 changes: 92 additions & 73 deletions src/lib/op/math/LibOpMul.sol
Original file line number Diff line number Diff line change
@@ -1,80 +1,99 @@
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.18;

// /// Used for reference implementation so that we have two independent
// /// upstreams to compare against.
// import {Math as OZMath} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
// import {UD60x18, mul} from "prb-math/UD60x18.sol";
// import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol";
// import {Pointer} from "rain.solmem/lib/LibPointer.sol";
// import {InterpreterState} from "../../state/LibInterpreterState.sol";
// import {IntegrityCheckState} from "../../integrity/LibIntegrityCheck.sol";
// import {LibWillOverflow} from "rain.math.fixedpoint/lib/LibWillOverflow.sol";
import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol";
import {Pointer} from "rain.solmem/lib/LibPointer.sol";
import {InterpreterState} from "../../state/LibInterpreterState.sol";
import {IntegrityCheckState} from "../../integrity/LibIntegrityCheck.sol";
import {LibDecimalFloat, Float} from "rain.math.float/lib/LibDecimalFloat.sol";
import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol";
import {LibDecimalFloatImplementation} from "rain.math.float/lib/implementation/LibDecimalFloatImplementation.sol";

// /// @title LibOpMul
// /// @notice Opcode to mul N 18 decimal fixed point values. Errors on overflow.
// library LibOpMul {
// function integrity(IntegrityCheckState memory, Operand operand) internal pure returns (uint256, uint256) {
// // There must be at least two inputs.
// uint256 inputs = (Operand.unwrap(operand) >> 0x10) & 0x0F;
// inputs = inputs > 1 ? inputs : 2;
// return (inputs, 1);
// }
/// @title LibOpMul
/// @notice Opcode to mul N 18 floating point values.
library LibOpMul {
function integrity(IntegrityCheckState memory, OperandV2 operand) internal pure returns (uint256, uint256) {
// There must be at least two inputs.
uint256 inputs = uint256(OperandV2.unwrap(operand) >> 0x10) & 0x0F;
inputs = inputs > 1 ? inputs : 2;
return (inputs, 1);
}

// /// mul
// /// 18 decimal fixed point multiplication with implied overflow checks from
// /// PRB Math.
// function run(InterpreterState memory, Operand operand, Pointer stackTop) internal pure returns (Pointer) {
// uint256 a;
// uint256 b;
// assembly ("memory-safe") {
// a := mload(stackTop)
// b := mload(add(stackTop, 0x20))
// stackTop := add(stackTop, 0x40)
// }
// a = UD60x18.unwrap(mul(UD60x18.wrap(a), UD60x18.wrap(b)));
/// mul
function run(InterpreterState memory, OperandV2 operand, Pointer stackTop) internal pure returns (Pointer) {
Float a;
Float b;
assembly ("memory-safe") {
a := mload(stackTop)
b := mload(add(stackTop, 0x20))
stackTop := add(stackTop, 0x40)
}
a = LibDecimalFloat.mul(a, b);

// {
// uint256 inputs = (Operand.unwrap(operand) >> 0x10) & 0x0F;
// uint256 i = 2;
// while (i < inputs) {
// assembly ("memory-safe") {
// b := mload(stackTop)
// stackTop := add(stackTop, 0x20)
// }
// a = UD60x18.unwrap(mul(UD60x18.wrap(a), UD60x18.wrap(b)));
// unchecked {
// i++;
// }
// }
// }
// assembly ("memory-safe") {
// stackTop := sub(stackTop, 0x20)
// mstore(stackTop, a)
// }
// return stackTop;
// }
{
uint256 inputs = uint256(OperandV2.unwrap(operand) >> 0x10) & 0x0F;
uint256 i = 2;
while (i < inputs) {
assembly ("memory-safe") {
b := mload(stackTop)
stackTop := add(stackTop, 0x20)
}
a = LibDecimalFloat.mul(a, b);
unchecked {
i++;
}
}
}
assembly ("memory-safe") {
stackTop := sub(stackTop, 0x20)
mstore(stackTop, a)
}
return stackTop;
}

// /// Gas intensive reference implementation of multiplication for testing.
// function referenceFn(InterpreterState memory, Operand, uint256[] memory inputs)
// internal
// pure
// returns (uint256[] memory outputs)
// {
// // Unchecked so that when we assert that an overflow error is thrown, we
// // see the revert from the real function and not the reference function.
// unchecked {
// uint256 a = inputs[0];
// for (uint256 i = 1; i < inputs.length; i++) {
// uint256 b = inputs[i];
// if (LibWillOverflow.mulDivWillOverflow(a, b, 1e18)) {
// a = uint256(keccak256(abi.encodePacked("overflow sentinel")));
// break;
// }
// a = OZMath.mulDiv(a, b, 1e18);
// }
// outputs = new uint256[](1);
// outputs[0] = a;
// }
// }
// }
/// Gas intensive reference implementation of multiplication for testing.
function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs)
internal
pure
returns (StackItem[] memory outputs)
{
// Unchecked so that when we assert that an overflow error is thrown, we
// see the revert from the real function and not the reference function.
unchecked {
Float a;
uint256 overflows = 0;
(int256 signedCoefficientA, int256 exponentA) =
LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[0])));
if (int32(exponentA) != exponentA) {
overflows++;
}
for (uint256 i = 1; i < inputs.length; i++) {
(int256 signedCoefficientB, int256 exponentB) =
LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[i])));
if (int32(exponentB) != exponentB) {
overflows++;
break;
}

(signedCoefficientA, exponentA) =
LibDecimalFloatImplementation.mul(signedCoefficientA, exponentA, signedCoefficientB, exponentB);

if (int32(exponentA) != exponentA) {
overflows++;
break;
}
}
outputs = new StackItem[](1);

if (overflows > 0) {
a = Float.wrap(keccak256(abi.encodePacked("overflow sentinel")));
} else {
a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA);
}

outputs[0] = StackItem.wrap(Float.unwrap(a));

return outputs;
}
}
Comment on lines +54 to +98

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Well-structured reference implementation with explicit overflow detection.

The reference function provides a comprehensive test implementation with explicit overflow checking for exponents that exceed int32 bounds. The unchecked block is appropriate for testing scenarios.

Consider using a constant for the overflow sentinel instead of the magic string:

+    bytes32 constant OVERFLOW_SENTINEL = keccak256("overflow sentinel");
     
     function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs)
         internal
         pure
         returns (StackItem[] memory outputs)
     {
         // ... existing code ...
         
         if (overflows > 0) {
-            a = Float.wrap(keccak256(abi.encodePacked("overflow sentinel")));
+            a = Float.wrap(OVERFLOW_SENTINEL);
         } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Gas intensive reference implementation of multiplication for testing.
function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs)
internal
pure
returns (StackItem[] memory outputs)
{
// Unchecked so that when we assert that an overflow error is thrown, we
// see the revert from the real function and not the reference function.
unchecked {
Float a;
uint256 overflows = 0;
(int256 signedCoefficientA, int256 exponentA) =
LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[0])));
if (int32(exponentA) != exponentA) {
overflows++;
}
for (uint256 i = 1; i < inputs.length; i++) {
(int256 signedCoefficientB, int256 exponentB) =
LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[i])));
if (int32(exponentB) != exponentB) {
overflows++;
break;
}
(signedCoefficientA, exponentA) =
LibDecimalFloatImplementation.mul(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
if (int32(exponentA) != exponentA) {
overflows++;
break;
}
}
outputs = new StackItem[](1);
if (overflows > 0) {
a = Float.wrap(keccak256(abi.encodePacked("overflow sentinel")));
} else {
a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA);
}
outputs[0] = StackItem.wrap(Float.unwrap(a));
return outputs;
}
}
// Add at contract scope, above referenceFn
bytes32 constant OVERFLOW_SENTINEL = keccak256("overflow sentinel");
/// Gas intensive reference implementation of multiplication for testing.
function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs)
internal
pure
returns (StackItem[] memory outputs)
{
// Unchecked so that when we assert that an overflow error is thrown, we
// see the revert from the real function and not the reference function.
unchecked {
Float a;
uint256 overflows = 0;
(int256 signedCoefficientA, int256 exponentA) =
LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[0])));
if (int32(exponentA) != exponentA) {
overflows++;
}
for (uint256 i = 1; i < inputs.length; i++) {
(int256 signedCoefficientB, int256 exponentB) =
LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[i])));
if (int32(exponentB) != exponentB) {
overflows++;
break;
}
(signedCoefficientA, exponentA) =
LibDecimalFloatImplementation.mul(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
if (int32(exponentA) != exponentA) {
overflows++;
break;
}
}
outputs = new StackItem[](1);
if (overflows > 0) {
- a = Float.wrap(keccak256(abi.encodePacked("overflow sentinel")));
+ a = Float.wrap(OVERFLOW_SENTINEL);
} else {
a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA);
}
outputs[0] = StackItem.wrap(Float.unwrap(a));
return outputs;
}
}
🤖 Prompt for AI Agents
In src/lib/op/math/LibOpMul.sol between lines 54 and 98, replace the magic
string "overflow sentinel" used in keccak256 for the overflow sentinel with a
named constant defined at the contract or library level. Define a constant
bytes32 variable to hold the keccak256 hash of the overflow sentinel string and
use that constant in the referenceFn function to improve code clarity and
maintainability.

}
Loading
Loading