Skip to content
Closed
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
6 changes: 6 additions & 0 deletions packages/bugc-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ specification itself are tracked in the root

## Unreleased

### Added

- The CFG view shows a `block.prevrandao` read as `block.prevrandao`
([#363]).

## 0.1.0-preview.1 — 2026-10-03

Updated `@ethdebug/bugc` to `0.1.0-preview.1`.
Expand Down Expand Up @@ -34,3 +39,4 @@ First publication.

[#299]: https://github.com/ethdebug/format/pull/299
[#300]: https://github.com/ethdebug/format/pull/300
[#363]: https://github.com/ethdebug/format/pull/363
2 changes: 2 additions & 0 deletions packages/bugc-react/src/components/CfgView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ function CfgViewContent({ ir }: CfgViewProps): JSX.Element {
return `${envInst.dest} = block.timestamp`;
case "block_number":
return `${envInst.dest} = block.number`;
case "block_prevrandao":
return `${envInst.dest} = block.prevrandao`;
default:
return `${envInst.dest} = ${envInst.op}`;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/bugc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ support. Changes to the specification itself are tracked in the root

### Added

- `block.prevrandao`, the previous block's RANDAO value (EIP-4399), as a
`uint256`. It compiles to the EVM's `PREVRANDAO` (`0x44`) ([#363]).
- The `%` operator, with the precedence of `*` and `/`. It compiles to
the EVM's `MOD` (`SMOD` for signed operands), so `x % 0` is `0`, as
`x / 0` is ([#321]).
Expand Down Expand Up @@ -290,3 +292,4 @@ First publication.
[#349]: https://github.com/ethdebug/format/pull/349
[#351]: https://github.com/ethdebug/format/pull/351
[#352]: https://github.com/ethdebug/format/pull/352
[#363]: https://github.com/ethdebug/format/pull/363
24 changes: 23 additions & 1 deletion packages/bugc/src/ast/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,8 @@ export namespace Expression {
| Expression.Special.MsgValue
| Expression.Special.MsgData
| Expression.Special.BlockTimestamp
| Expression.Special.BlockNumber;
| Expression.Special.BlockNumber
| Expression.Special.BlockPrevrandao;

export const isSpecial = (
expression: Expression.Base,
Expand All @@ -1451,6 +1452,7 @@ export namespace Expression {
Expression.Special.isMsgSender,
Expression.Special.isBlockNumber,
Expression.Special.isBlockTimestamp,
Expression.Special.isBlockPrevrandao,
].some((guard) => guard(expression));

export namespace Special {
Expand Down Expand Up @@ -1532,6 +1534,26 @@ export namespace Expression {
export function blockNumber(id: Id, loc?: SourceLocation): BlockNumber {
return { id, kind: "expression:special:block.number", loc: loc ?? null };
}

export interface BlockPrevrandao extends Base {
kind: "expression:special:block.prevrandao";
}

export const isBlockPrevrandao = (
expression: Expression.Base,
): expression is BlockPrevrandao =>
expression.kind === "expression:special:block.prevrandao";

export function blockPrevrandao(
id: Id,
loc?: SourceLocation,
): BlockPrevrandao {
return {
id,
kind: "expression:special:block.prevrandao",
loc: loc ?? null,
};
}
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/bugc/src/ast/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export function visitDetailed<N extends Ast.Node, T, C = never>(
case "expression:special:msg.data":
case "expression:special:block.timestamp":
case "expression:special:block.number":
case "expression:special:block.prevrandao":
return visitor.expressionSpecial(node as Ast.Expression.Special, context);

default:
Expand Down
33 changes: 33 additions & 0 deletions packages/bugc/src/evmgen/behavioral.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, it, expect } from "vitest";
import { keccak256 } from "ethereum-cryptography/keccak";
import { bytesToHex } from "ethereum-cryptography/utils";

import { compile } from "#compiler";
import { executeProgram } from "#test/evm/behavioral";

describe("behavioral tests", () => {
Expand Down Expand Up @@ -1289,6 +1290,38 @@ code {
}
});

describe("block.prevrandao", () => {
const source = `name Randao;
storage { [0] out: uint256; }
code {
out = block.prevrandao + 1;
}`;

for (const level of [0, 1, 2, 3] as const) {
it(`should read PREVRANDAO (level ${level})`, async () => {
const compiled = await compile({
to: "bytecode",
source,
optimizer: { level },
});
if (!compiled.success) throw new Error("compile failed");
const mnemonics =
compiled.value.bytecode.runtimeProgram.instructions.map(
(instruction) => instruction.operation?.mnemonic,
);
expect(mnemonics).toContain("PREVRANDAO");

// @ethdebug/evm runs each call in a block whose prevrandao is 0
const result = await executeProgram(source, {
calldata: "",
optimizationLevel: level,
});
expect(result.callSuccess).toBe(true);
expect(await result.getStorage(0n)).toBe(1n);
});
}
});

describe("modulo", () => {
const program = (expr: string) => `name Modulo;

Expand Down
3 changes: 2 additions & 1 deletion packages/bugc/src/evmgen/generation/instructions/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { type Transition, pipe, operations } from "#evmgen/operations";

import { storeValueIfNeeded } from "../values/index.js";

const { CALLER, CALLVALUE, PUSH0, TIMESTAMP, NUMBER } = operations;
const { CALLER, CALLVALUE, PUSH0, TIMESTAMP, NUMBER, PREVRANDAO } = operations;

/**
* Generate code for environment operations
Expand All @@ -26,6 +26,7 @@ export function generateEnvOp<S extends Stack>(
msg_data: PUSH0({ debug }), // Returns calldata offset (0)
block_timestamp: TIMESTAMP({ debug }),
block_number: NUMBER({ debug }),
block_prevrandao: PREVRANDAO({ debug }),
};

return pipe<S>()
Expand Down
1 change: 1 addition & 0 deletions packages/bugc/src/ir/analysis/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export class Validator {
"msg_value",
"block_number",
"block_timestamp",
"block_prevrandao",
];
if (!validOps.includes(inst.op)) {
this.error(`Invalid env operation '${inst.op}'`);
Expand Down
3 changes: 2 additions & 1 deletion packages/bugc/src/ir/spec/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,8 @@ export namespace Instruction {
| "msg_value"
| "msg_data"
| "block_number"
| "block_timestamp";
| "block_timestamp"
| "block_prevrandao";

dest: string;
}
Expand Down
1 change: 1 addition & 0 deletions packages/bugc/src/irgen/generate/expressions/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function* buildExpression(
case "expression:special:msg.data":
case "expression:special:block.timestamp":
case "expression:special:block.number":
case "expression:special:block.prevrandao":
return yield* buildSpecial(expr as Ast.Expression.Special);
case "expression:array":
return yield* buildArray(expr as Ast.Expression.Array, context);
Expand Down
3 changes: 3 additions & 0 deletions packages/bugc/src/irgen/generate/expressions/special.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export function* buildSpecial(expr: Ast.Expression.Special): Process<Ir.Value> {
case "expression:special:block.number":
op = "block_number";
break;
case "expression:special:block.prevrandao":
op = "block_prevrandao";
break;
default:
assertExhausted(expr);
}
Expand Down
6 changes: 6 additions & 0 deletions packages/bugc/src/irgen/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ describe("generateModule", () => {
code {
let num = block.number;
let time = block.timestamp;
let randao = block.prevrandao;
}
`;

Expand All @@ -430,8 +431,13 @@ describe("generateModule", () => {
(i) => i.kind === "env" && i.op === "block_timestamp",
);

const prevrandaoInst = entry.instructions.find(
(i) => i.kind === "env" && i.op === "block_prevrandao",
);

expect(numberInst).toBeDefined();
expect(timestampInst).toBeDefined();
expect(prevrandaoInst).toBeDefined();
});
});

Expand Down
21 changes: 14 additions & 7 deletions packages/bugc/src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,18 +535,25 @@ const msgExpression = located(
}),
);

// block.timestamp and block.number as special expressions
// block.timestamp, block.number and block.prevrandao as special
// expressions
const blockExpression = located(
P.seq(
Lang.keyword("block"),
Lang.dot,
P.alt(Lang.keyword("timestamp"), Lang.keyword("number")),
P.alt(
Lang.keyword("timestamp"),
Lang.keyword("number"),
Lang.keyword("prevrandao"),
),
).map(([_, __, property]) => {
const kind = property === "timestamp" ? "block.timestamp" : "block.number";
if (kind === "block.timestamp") {
return Ast.Expression.Special.blockTimestamp(PENDING_ID);
} else {
return Ast.Expression.Special.blockNumber(PENDING_ID);
switch (property) {
case "timestamp":
return Ast.Expression.Special.blockTimestamp(PENDING_ID);
case "number":
return Ast.Expression.Special.blockNumber(PENDING_ID);
default:
return Ast.Expression.Special.blockPrevrandao(PENDING_ID);
}
}),
);
Expand Down
3 changes: 3 additions & 0 deletions packages/bugc/src/typechecker/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,9 @@ export const expressionChecker: Pick<Visitor<Report, Context>, "expression"> = {
case "expression:special:block.number":
type = Type.Elementary.uint(256);
break;
case "expression:special:block.prevrandao":
type = Type.Elementary.uint(256);
break;
}

const nodeTypes = new Map(context.nodeTypes);
Expand Down
Loading