Rainlang is untyped, so a word receives raw stack bits and has no way to know what they were meant to be. Whether a word can check its own operand is an accident of that operand's encoding, not a property of the language — and where the encoding carries no tag, the check is not merely unimplemented but impossible.
This is filed as the language-level home for a finding that keeps recurring per-word and per-repo with no general answer.
The concrete case
rainlanguage/rain.dia#40 asked LibOpDiaPrice.run to validate that inputs[1] (staleAfter) is a legitimate Float before toFixedDecimalLossless reads it, proposing LibDecimalFloat.isFloat(staleAfter).
It cannot be built as specified:
Float is a bare alias — type Float is bytes32 — and unpack() is bit reinterpretation with no discriminant and no failure mode. Every 256-bit word is a valid Float. There is no representation to validate.
LibDecimalFloat.isFloat exists in no version of rain.math.float, pinned or on main.
Contrast the sibling finding on input 0. V3 IntOrAString carries a 0xE0 type tag in its low byte, so feedKey can be checked, and was. That is the whole asymmetry: one operand is checkable because its encoding happens to reserve bits for a tag, the other is not because it does not. Neither fact is a decision anyone made about the word.
Why this does not belong in the words repos
- It recurs identically across them.
rain.pyth's LibOpPythPrice.run assembly-loads inputs[1] and calls toFixedDecimalLossless(staleAfter, 0) byte-identically. Any future oracle word will do the same.
- A repo-local runtime guard would diverge the family. Adding a bound in
rain.dia and not rain.pyth makes two words that are meant to be the same word behave differently, and the divergence would be invisible from either repo.
- The only implementable guard is semantic, not representational — a maximum
staleAfter bound. That is a different thing from what the audit asked for, and choosing a number is a design decision about the words family rather than a fix to a validation gap.
So the per-word answer is either "impossible as asked" or "invent a policy", and neither is a bug fix. The place where an operand's intended type could actually be known is the compiler.
What this issue is asking
Whether rainlang should carry operand types at all, and if so at what granularity — that is a language design question, not a defect report, and it is the reason this is filed here rather than as a fix.
Worth settling rather than assuming:
- Granularity. Literal types (
#218 is adjacent — 1_d6 / 1_d0 / 1_u), operand types on word signatures, stack-slot types carried through the expression, or some subset. These have very different costs.
- What existing rainlang does. Every expression written to date is untyped by construction. Whether typing is opt-in, inferred, or a break is the question that decides whether this is buildable at all.
- Whether the interpreter still needs runtime checks. A type system that only runs at parse time is a tool for authors. It is worth being explicit that it would not, on its own, replace anything.
The limit this does NOT clear, and it should be stated up front
Compile-time typing does not guard a direct external call. DiaWords.extern is publicly callable on-chain; a caller invoking it directly never goes through the parser, so no amount of language-level typing constrains the bits they push. On the specific case that prompted this, that exposure is bounded — the extern is view and stateless, so a caller passing a nonsense staleAfter gets a stale price returned to itself, and toFixedDecimalLossless already reverts on everything rejectable (NegativeFixedDecimalConversion, FixedDecimalOverflow, LossyConversionFromFloat), leaving an accepted set that is exactly the non-negative-integer-seconds domain staleAfter is defined over.
So the honest scope is: typing would catch authoring mistakes at compile time. It would not make a word safe against an adversarial direct caller, and any word whose correctness depends on that still needs its own guard. If the answer to this issue is "rainlang stays untyped", that is a legitimate outcome and it makes the per-word findings wont-fix on a stated principle rather than one at a time.
Related
rainlanguage/rain.dia#40 — the finding that prompted this; impossible as specified, with the full analysis on the issue
rainlanguage/rain.pyth — LibOpPythPrice.run, the byte-identical sibling that would diverge under a repo-local fix
#218 — types in decimal literals, adjacent granularity question
Rainlang is untyped, so a word receives raw stack bits and has no way to know what they were meant to be. Whether a word can check its own operand is an accident of that operand's encoding, not a property of the language — and where the encoding carries no tag, the check is not merely unimplemented but impossible.
This is filed as the language-level home for a finding that keeps recurring per-word and per-repo with no general answer.
The concrete case
rainlanguage/rain.dia#40askedLibOpDiaPrice.runto validate thatinputs[1](staleAfter) is a legitimateFloatbeforetoFixedDecimalLosslessreads it, proposingLibDecimalFloat.isFloat(staleAfter).It cannot be built as specified:
Floatis a bare alias —type Float is bytes32— andunpack()is bit reinterpretation with no discriminant and no failure mode. Every 256-bit word is a validFloat. There is no representation to validate.LibDecimalFloat.isFloatexists in no version ofrain.math.float, pinned or on main.Contrast the sibling finding on input 0. V3
IntOrAStringcarries a0xE0type tag in its low byte, sofeedKeycan be checked, and was. That is the whole asymmetry: one operand is checkable because its encoding happens to reserve bits for a tag, the other is not because it does not. Neither fact is a decision anyone made about the word.Why this does not belong in the words repos
rain.pyth'sLibOpPythPrice.runassembly-loadsinputs[1]and callstoFixedDecimalLossless(staleAfter, 0)byte-identically. Any future oracle word will do the same.rain.diaand notrain.pythmakes two words that are meant to be the same word behave differently, and the divergence would be invisible from either repo.staleAfterbound. That is a different thing from what the audit asked for, and choosing a number is a design decision about the words family rather than a fix to a validation gap.So the per-word answer is either "impossible as asked" or "invent a policy", and neither is a bug fix. The place where an operand's intended type could actually be known is the compiler.
What this issue is asking
Whether rainlang should carry operand types at all, and if so at what granularity — that is a language design question, not a defect report, and it is the reason this is filed here rather than as a fix.
Worth settling rather than assuming:
#218is adjacent —1_d6/1_d0/1_u), operand types on word signatures, stack-slot types carried through the expression, or some subset. These have very different costs.The limit this does NOT clear, and it should be stated up front
Compile-time typing does not guard a direct external call.
DiaWords.externis publicly callable on-chain; a caller invoking it directly never goes through the parser, so no amount of language-level typing constrains the bits they push. On the specific case that prompted this, that exposure is bounded — the extern isviewand stateless, so a caller passing a nonsensestaleAftergets a stale price returned to itself, andtoFixedDecimalLosslessalready reverts on everything rejectable (NegativeFixedDecimalConversion,FixedDecimalOverflow,LossyConversionFromFloat), leaving an accepted set that is exactly the non-negative-integer-seconds domainstaleAfteris defined over.So the honest scope is: typing would catch authoring mistakes at compile time. It would not make a word safe against an adversarial direct caller, and any word whose correctness depends on that still needs its own guard. If the answer to this issue is "rainlang stays untyped", that is a legitimate outcome and it makes the per-word findings
wont-fixon a stated principle rather than one at a time.Related
rainlanguage/rain.dia#40— the finding that prompted this; impossible as specified, with the full analysis on the issuerainlanguage/rain.pyth—LibOpPythPrice.run, the byte-identical sibling that would diverge under a repo-local fix#218— types in decimal literals, adjacent granularity question