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 9b71615..e01480d 100644 --- a/__tests__/utils.ts +++ b/__tests__/utils.ts @@ -46,10 +46,15 @@ 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 res = await defaultHandle(currentMemory, msg, env || defaultEnvironment); + const envWithRandomMod = env || defaultEnvironment; + // @ts-expect-error + envWithRandomMod.Module = { Id: module }; + const res = await defaultHandle(currentMemory, msg, envWithRandomMod); // save memory if (keepMemory) @@ -177,7 +182,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 +198,7 @@ expect.extend({ }, toBeJsonEncoded(actual: string, matcher: jest.AsymmetricMatcher) { let parsed: unknown; - + try { parsed = JSON.parse(actual); } catch (error: any) { @@ -226,7 +231,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..1723e32 100644 --- a/src/process.lua +++ b/src/process.lua @@ -38,11 +38,12 @@ local redeem = require ".supply.redeem" local delegation = require ".supply.delegation" local precision = require ".utils.precision" +local chance = require ".utils.chance" HandlersAdded = HandlersAdded or false -- add handlers for inputs -local function setup_handlers() +local function setup_handlers(msg) -- only add handlers once if HandlersAdded then return end @@ -361,11 +362,31 @@ 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 -- add handlers - setup_handlers() + setup_handlers(msg) -- eval handlers local co = coroutine.create(function() return pcall(Handlers.evaluate, msg, ao.env) end) 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..56eee80 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, ...) @@ -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 -- 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,