From 723b8187451be2319f04a291608a6858bfd089a8 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Sun, 12 Oct 2025 20:05:09 +0200 Subject: [PATCH 1/5] feat: randomize instead of once nonce --- __tests__/utils.ts | 11 ++-- src/process.lua | 18 +++++++ src/utils/chance.lua | 117 ++++++++++++++++++++++++++++++++++++++++ src/utils/handlers.lua | 4 +- src/utils/scheduler.lua | 2 +- 5 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 src/utils/chance.lua diff --git a/__tests__/utils.ts b/__tests__/utils.ts index 9b71615..b676b24 100644 --- a/__tests__/utils.ts +++ b/__tests__/utils.ts @@ -49,7 +49,10 @@ export async function setupProcess(defaultEnvironment: Environment, keepMemory = // handler function that automatically saves the state return async (msg, env) => { // call handle - const res = await defaultHandle(currentMemory, msg, env || defaultEnvironment); + const envWithRandomMod = env || defaultEnvironment; + // @ts-expect-error + envWithRandomMod.Module = { Id: generateArweaveAddress() }; + const res = await defaultHandle(currentMemory, msg, envWithRandomMod); // save memory if (keepMemory) @@ -177,7 +180,7 @@ expect.extend({ }, toBeFloatStringEncoded(actual: unknown) { const pass = typeof actual === "string" && actual.match(/^-?\d+(\.\d+)?$/) !== null; - + return { pass, message: () => `expected ${this.utils.printReceived(actual)} to be a ${this.utils.printExpected("string encoded float")}` @@ -193,7 +196,7 @@ expect.extend({ }, toBeJsonEncoded(actual: string, matcher: jest.AsymmetricMatcher) { let parsed: unknown; - + try { parsed = JSON.parse(actual); } catch (error: any) { @@ -226,7 +229,7 @@ expect.extend({ }) ]) }; - + if (this.isNot) { expect(actual).not.toEqual(expect.objectContaining(objTest)); } else { diff --git a/src/process.lua b/src/process.lua index ea2e548..5308ed4 100644 --- a/src/process.lua +++ b/src/process.lua @@ -38,6 +38,7 @@ local redeem = require ".supply.redeem" local delegation = require ".supply.delegation" local precision = require ".utils.precision" +local chance = require ".utils.chance" HandlersAdded = HandlersAdded or false @@ -361,6 +362,23 @@ function process.handle(msg, env) return ao.result() end + -- reseed random + chance.seed(tonumber(msg["Block-Height"] .. chance.stringToSeed(ao.env.Process.Owner .. ao.env.Module.Id .. ao.id))) + math.random = function(...) + local args = {...} + local n = #args + if n == 0 then + return chance.random() + end + if n == 1 then + return chance.integer(1, args[1]) + end + if n == 2 then + return chance.integer(args[1], args[2]) + end + return chance.random() + end + -- the controller is the process spawner Controller = Controller or ao.env.Process.Owner diff --git a/src/utils/chance.lua b/src/utils/chance.lua new file mode 100644 index 0000000..cfb1721 --- /dev/null +++ b/src/utils/chance.lua @@ -0,0 +1,117 @@ +-- Copyright (c) 2025 Forward Research +-- Code from the aos codebase: https://github.com/permaweb/aos + +--- The Chance module provides utilities for generating random numbers and values. Returns the chance table. +-- @module chance + +local N = 624 +local M = 397 +local MATRIX_A = 0x9908b0df +local UPPER_MASK = 0x80000000 +local LOWER_MASK = 0x7fffffff + +--- Initializes mt[N] with a seed +-- @lfunction init_genrand +-- @tparam {table} o The table to initialize +-- @tparam {number} s The seed +local function init_genrand(o, s) + o.mt[0] = s & 0xffffffff + for i = 1, N - 1 do + o.mt[i] = (1812433253 * (o.mt[i - 1] ~ (o.mt[i - 1] >> 30))) + i + -- See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. + -- In the previous versions, MSBs of the seed affect + -- only MSBs of the array mt[]. + -- 2002/01/09 modified by Makoto Matsumoto + o.mt[i] = o.mt[i] & 0xffffffff + -- for >32 bit machines + end + o.mti = N +end + +--- Generates a random number on [0,0xffffffff]-interval +-- @lfunction genrand_int32 +-- @tparam {table} o The table to generate the random number from +-- @treturn {number} The random number +local function genrand_int32(o) + local y + local mag01 = {} -- mag01[x] = x * MATRIX_A for x=0,1 + mag01[0] = 0x0 + mag01[1] = MATRIX_A + if o.mti >= N then -- generate N words at one time + if o.mti == N + 1 then -- if init_genrand() has not been called, + init_genrand(o, 5489) -- a default initial seed is used + end + for kk = 0, N - M - 1 do + y = (o.mt[kk] & UPPER_MASK) | (o.mt[kk + 1] & LOWER_MASK) + o.mt[kk] = o.mt[kk + M] ~ (y >> 1) ~ mag01[y & 0x1] + end + for kk = N - M, N - 2 do + y = (o.mt[kk] & UPPER_MASK) | (o.mt[kk + 1] & LOWER_MASK) + o.mt[kk] = o.mt[kk + (M - N)] ~ (y >> 1) ~ mag01[y & 0x1] + end + y = (o.mt[N - 1] & UPPER_MASK) | (o.mt[0] & LOWER_MASK) + o.mt[N - 1] = o.mt[M - 1] ~ (y >> 1) ~ mag01[y & 0x1] + + o.mti = 0 + end + + y = o.mt[o.mti] + o.mti = o.mti + 1 + + -- Tempering + y = y ~ (y >> 11) + y = y ~ ((y << 7) & 0x9d2c5680) + y = y ~ ((y << 15) & 0xefc60000) + y = y ~ (y >> 18) + + return y +end + +local MersenneTwister = {} +MersenneTwister.mt = {} +MersenneTwister.mti = N + 1 + + +--- The Random table +-- @table Random +-- @field seed The seed function +-- @field random The random function +-- @field integer The integer function +local Random = {} + +--- Sets a new random table given a seed. +-- @function seed +-- @tparam {number} seed The seed +function Random.seed(seed) + init_genrand(MersenneTwister, seed) +end + +--- Generates a random number on [0,1)-real-interval. +-- @function random +-- @treturn {number} The random number +function Random.random() + return genrand_int32(MersenneTwister) * (1.0 / 4294967296.0) +end + +--- Returns a random integer. The min and max are INCLUDED in the range. +-- The max integer in lua is math.maxinteger +-- The min is math.mininteger +-- @function Random.integer +-- @tparam {number} min The minimum value +-- @tparam {number} max The maximum value +-- @treturn {number} The random integer +function Random.integer(min, max) + assert(max >= min, "max must bigger than min") + return math.floor(Random.random() * (max - min + 1) + min) +end + +function Random.stringToSeed(s) + local seed = 0 + for i = 1, #s do + local char = string.byte(s, i) + seed = seed + char + end + return seed +end + +return Random diff --git a/src/utils/handlers.lua b/src/utils/handlers.lua index 7978f47..52b25b8 100644 --- a/src/utils/handlers.lua +++ b/src/utils/handlers.lua @@ -131,7 +131,7 @@ function handlers.receive(pattern, timeout) end handlers.advanced({ - name = "_once_" .. tostring(handlers.onceNonce), + name = "_once_" .. tostring(Timestamp) .. tostring(math.random(1e9)), position = "prepend", pattern = pattern, maxRuns = 1, @@ -162,7 +162,7 @@ function handlers.once(...) pattern = select(2, ...) handle = select(3, ...) else - name = "_once_" .. tostring(handlers.onceNonce) + name = "_once_" .. tostring(Timestamp) .. tostring(math.random(1e9)) handlers.onceNonce = handlers.onceNonce + 1 pattern = select(1, ...) handle = select(2, ...) diff --git a/src/utils/scheduler.lua b/src/utils/scheduler.lua index 83dcbba..ce0b4ac 100644 --- a/src/utils/scheduler.lua +++ b/src/utils/scheduler.lua @@ -51,7 +51,7 @@ function mod.schedule(...) -- wait for response Handlers.advanced({ - name = "_once_" .. tostring(Handlers.onceNonce), + name = "_once_" .. tostring(Timestamp) .. tostring(math.random(1e9)), position = "prepend", pattern = { From = msg.Target, From 83666a1d231df2f683f83c9fbc2a181a15f14cfe Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Mon, 13 Oct 2025 17:37:03 +0200 Subject: [PATCH 2/5] fix: handlers list --- src/utils/handlers.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/handlers.lua b/src/utils/handlers.lua index 52b25b8..56eee80 100644 --- a/src/utils/handlers.lua +++ b/src/utils/handlers.lua @@ -405,7 +405,7 @@ function handlers.advanced(config) if idx ~= nil and idx > 0 then -- found a handler to update - handlers[idx] = config + handlers.list[idx] = config else -- a handler with this name doesn't exist yet, so we add it -- From 9a8e56663052dcebde1735b288b1c7ae697ed184 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Mon, 13 Oct 2025 21:21:22 +0200 Subject: [PATCH 3/5] fix: seeding --- src/process.lua | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/process.lua b/src/process.lua index 5308ed4..eba39ca 100644 --- a/src/process.lua +++ b/src/process.lua @@ -43,7 +43,25 @@ local chance = require ".utils.chance" HandlersAdded = HandlersAdded or false -- add handlers for inputs -local function setup_handlers() +local function setup_handlers(msg) + -- reseed random for handlers.once() + -- reseed random + chance.seed(tonumber(msg["Block-Height"] .. chance.stringToSeed(ao.env.Process.Owner .. ao.env.Module.Id .. ao.id))) + math.random = function(...) + local args = {...} + local n = #args + if n == 0 then + return chance.random() + end + if n == 1 then + return chance.integer(1, args[1]) + end + if n == 2 then + return chance.integer(args[1], args[2]) + end + return chance.random() + end + -- only add handlers once if HandlersAdded then return end @@ -362,28 +380,11 @@ function process.handle(msg, env) return ao.result() end - -- reseed random - chance.seed(tonumber(msg["Block-Height"] .. chance.stringToSeed(ao.env.Process.Owner .. ao.env.Module.Id .. ao.id))) - math.random = function(...) - local args = {...} - local n = #args - if n == 0 then - return chance.random() - end - if n == 1 then - return chance.integer(1, args[1]) - end - if n == 2 then - return chance.integer(args[1], args[2]) - end - return chance.random() - end - -- the controller is the process spawner Controller = Controller or ao.env.Process.Owner -- add handlers - setup_handlers() + setup_handlers(msg) -- eval handlers local co = coroutine.create(function() return pcall(Handlers.evaluate, msg, ao.env) end) From fdbc85e1721fcb94fa85aa84e41fa2daa4733d87 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Mon, 13 Oct 2025 21:27:50 +0200 Subject: [PATCH 4/5] feat: move seeding --- src/process.lua | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/process.lua b/src/process.lua index eba39ca..1723e32 100644 --- a/src/process.lua +++ b/src/process.lua @@ -44,24 +44,6 @@ HandlersAdded = HandlersAdded or false -- add handlers for inputs local function setup_handlers(msg) - -- reseed random for handlers.once() - -- reseed random - chance.seed(tonumber(msg["Block-Height"] .. chance.stringToSeed(ao.env.Process.Owner .. ao.env.Module.Id .. ao.id))) - math.random = function(...) - local args = {...} - local n = #args - if n == 0 then - return chance.random() - end - if n == 1 then - return chance.integer(1, args[1]) - end - if n == 2 then - return chance.integer(args[1], args[2]) - end - return chance.random() - end - -- only add handlers once if HandlersAdded then return end @@ -380,6 +362,26 @@ function process.handle(msg, env) return ao.result() end + if not RandomSeeded then + chance.seed(tonumber(msg["Block-Height"] .. chance.stringToSeed(ao.env.Process.Owner .. ao.env.Module.Id .. ao.id))) + math.random = function(...) + local args = {...} + local n = #args + if n == 0 then + return chance.random() + end + if n == 1 then + return chance.integer(1, args[1]) + end + if n == 2 then + return chance.integer(args[1], args[2]) + end + return chance.random() + end + + RandomSeeded = true + end + -- the controller is the process spawner Controller = Controller or ao.env.Process.Owner From eec9ed9b2630e7eb1e780fae771063c64582783d Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Mon, 13 Oct 2025 21:50:31 +0200 Subject: [PATCH 5/5] chore: update tests --- __tests__/controller.test.ts | 1 + __tests__/liquidations.test.ts | 1 + __tests__/supply.test.ts | 1 + __tests__/token.test.ts | 1 + __tests__/utils.ts | 4 +++- 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/__tests__/controller.test.ts b/__tests__/controller.test.ts index fd32f0b..8c3198b 100644 --- a/__tests__/controller.test.ts +++ b/__tests__/controller.test.ts @@ -1476,6 +1476,7 @@ describe("Reserves tests", () => { beforeAll(async () => { const envWithReserves = { + ...env, Process: { ...env.Process, Tags: [ diff --git a/__tests__/liquidations.test.ts b/__tests__/liquidations.test.ts index dc9bb6a..cfd0dc3 100644 --- a/__tests__/liquidations.test.ts +++ b/__tests__/liquidations.test.ts @@ -577,6 +577,7 @@ describe("Position liquidation", () => { beforeEach(async () => { const envWithFriend = { + ...env, Process: { ...env.Process, Tags: env.Process.Tags.map((t) => { diff --git a/__tests__/supply.test.ts b/__tests__/supply.test.ts index 3259cc0..3ce7924 100644 --- a/__tests__/supply.test.ts +++ b/__tests__/supply.test.ts @@ -895,6 +895,7 @@ describe("AO delegation", () => { beforeEach(async () => { const envWithWAO = { + ...env, Process: { ...env.Process, Tags: [ diff --git a/__tests__/token.test.ts b/__tests__/token.test.ts index 1223138..52ee104 100644 --- a/__tests__/token.test.ts +++ b/__tests__/token.test.ts @@ -953,6 +953,7 @@ describe("Token standard functionalities", () => { }; const friendTicker = "TST"; const envWithFriend = { + ...env, Process: { ...env.Process, Tags: env.Process.Tags.map((t) => { diff --git a/__tests__/utils.ts b/__tests__/utils.ts index b676b24..e01480d 100644 --- a/__tests__/utils.ts +++ b/__tests__/utils.ts @@ -46,12 +46,14 @@ export async function setupProcess(defaultEnvironment: Environment, keepMemory = // current state of the process let currentMemory: ArrayBuffer | null = null; + const module = generateArweaveAddress() + // handler function that automatically saves the state return async (msg, env) => { // call handle const envWithRandomMod = env || defaultEnvironment; // @ts-expect-error - envWithRandomMod.Module = { Id: generateArweaveAddress() }; + envWithRandomMod.Module = { Id: module }; const res = await defaultHandle(currentMemory, msg, envWithRandomMod); // save memory