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
181 changes: 181 additions & 0 deletions packages/sdk-core/tests/resources/Powerup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { UInt128, UInt64 } from "@wireio/sdk-core/chain/Integer"
import { BNPrecision, PowerUpState, Resources } from "@wireio/sdk-core"
import { PowerUpStateResourceCPU } from "@wireio/sdk-core/resources/powerup/Cpu"
import { PowerUpStateResourceNET } from "@wireio/sdk-core/resources/powerup/Net"
import { createSampleUsage } from "../support/resources"

/** Matches `PowerUpStateResource` default block CPU limit (μs). */
const DEFAULT_BLOCK_CPU_LIMIT = 200_000
/** Matches `PowerUpStateResource` default block net limit (bytes). */
const DEFAULT_BLOCK_NET_LIMIT = 1_048_576_000
/**
* Day-window multiplier used by PowerUp `*_per_day` helpers:
* `limit * 2 * 60 * 60 * 24`.
*/
const POWERUP_DAY_WINDOW = 2 * 60 * 60 * 24

/** Shared PowerUp resource fields for CPU/NET fixtures. */
function powerUpResourceFields(overrides: Record<string, unknown> = {}) {
return {
version: 0,
weight: "100000000000000",
weight_ratio: "10000000000000",
assumed_stake_weight: "1",
initial_weight_ratio: "100000000000000",
target_weight_ratio: "10000000000000",
initial_timestamp: "2020-01-01T00:00:00",
target_timestamp: "2021-01-01T00:00:00",
exponent: "2",
decay_secs: 86_400,
min_price: "0.0000 SYS",
max_price: "1000.0000 SYS",
utilization: "0",
adjusted_utilization: "0",
utilization_timestamp: "2020-01-01T00:00:00",
...overrides
}
}

function createCpuState(overrides: Record<string, unknown> = {}) {
return PowerUpStateResourceCPU.from(powerUpResourceFields(overrides))
}

function createNetState(overrides: Record<string, unknown> = {}) {
return PowerUpStateResourceNET.from(powerUpResourceFields(overrides))
}

function createPowerUpState(
overrides: Record<string, unknown> = {}
): InstanceType<typeof PowerUpState> {
return PowerUpState.from({
version: 0,
net: powerUpResourceFields(),
cpu: powerUpResourceFields(),
powerup_days: 1,
min_powerup_fee: "0.0001 SYS",
...overrides
})
}

/** Mocked `Resources` parent that returns a fixed `powup.state` table row. */
function createPowerUpResources(state = createPowerUpState()) {
const getTableRows = jest.fn(async () => ({
rows: [state],
more: false
}))

const resources = new Resources({
api: {
v1: {
chain: {
get_table_rows: getTableRows
}
}
} as any
})

return { resources, getTableRows, state }
}

describe("PowerUpStateResourceCPU", () => {
test("us_per_day uses the default block CPU limit", () => {
expect(createCpuState().us_per_day()).toBe(
DEFAULT_BLOCK_CPU_LIMIT * POWERUP_DAY_WINDOW
)
})

test("us_per_day honors virtual_block_cpu_limit overrides", () => {
expect(
createCpuState().us_per_day({
virtual_block_cpu_limit: UInt64.from(100_000)
})
).toBe(100_000 * POWERUP_DAY_WINDOW)
})

test("ms_per_day and per_day derive from us_per_day", () => {
const cpu = createCpuState()
expect(cpu.ms_per_day()).toBe(cpu.us_per_day() / 1000)
expect(cpu.per_day()).toBe(cpu.us_per_day())
})

test("weight_to_us and us_to_weight round-trip with BNPrecision", () => {
const cpu = createCpuState()
const sample = UInt128.from(100_000_000)
const weight = BNPrecision.toNumber()
const us = cpu.weight_to_us(sample, weight)
expect(us).toBe(100_000_000)
expect(cpu.us_to_weight(sample, us)).toBe(weight)
})

test("frac_by_ms multiplies microseconds by 1000", () => {
const cpu = createCpuState()
const sample = createSampleUsage()
expect(cpu.frac_by_ms(sample, 1)).toBe(cpu.frac_by_us(sample, 1000))
})

test("price_per_us returns a finite non-negative fee", () => {
const price = createCpuState().price_per_us(createSampleUsage(), 1000)
expect(Number.isFinite(price)).toBe(true)
expect(price).toBeGreaterThanOrEqual(0)
})

test("allocated reflects the weight_ratio shift toward PowerUp", () => {
// 1 - (1e13 / 1e13 / 100) = 0.99
expect(createCpuState().allocated).toBeCloseTo(0.99)
})

test("symbol comes from min_price", () => {
expect(createCpuState().symbol.name).toBe("SYS")
})
})

describe("PowerUpStateResourceNET", () => {
test("bytes_per_day uses the default block net limit", () => {
expect(createNetState().bytes_per_day()).toBe(
DEFAULT_BLOCK_NET_LIMIT * POWERUP_DAY_WINDOW
)
})

test("kb_per_day and per_day derive from bytes_per_day", () => {
const net = createNetState()
expect(net.kb_per_day()).toBe(net.bytes_per_day() / 1000)
expect(net.per_day()).toBe(net.bytes_per_day())
})

test("weight_to_bytes and bytes_to_weight round-trip with BNPrecision", () => {
const net = createNetState()
const sample = UInt128.from(100_000_000)
const weight = BNPrecision.toNumber()
const bytes = net.weight_to_bytes(sample, weight)
expect(bytes).toBe(100_000_000)
expect(net.bytes_to_weight(sample, bytes)).toBe(weight)
})

test("frac_by_kb multiplies bytes by 1000", () => {
const net = createNetState()
const sample = createSampleUsage()
expect(net.frac_by_kb(sample, 1)).toBe(net.frac_by_bytes(sample, 1000))
})

test("price_per_byte returns a finite non-negative fee", () => {
const price = createNetState().price_per_byte(createSampleUsage(), 1000)
expect(Number.isFinite(price)).toBe(true)
expect(price).toBeGreaterThanOrEqual(0)
})
})

describe("PowerUpAPI", () => {
test("get_state reads sysio//powup.state and returns the first row", async () => {
const { resources, getTableRows, state } = createPowerUpResources()
const result = await resources.v1.powerup.get_state()

expect(result).toBe(state)
expect(result).toBeInstanceOf(PowerUpState)
expect(getTableRows).toHaveBeenCalledWith({
code: "sysio",
scope: "",
table: "powup.state",
type: PowerUpState
})
})
})
94 changes: 94 additions & 0 deletions packages/sdk-core/tests/resources/Ram.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Asset } from "@wireio/sdk-core/chain/Asset"
import { Int64 } from "@wireio/sdk-core/chain/Integer"
import { Resources, RAMState } from "@wireio/sdk-core"

/** Canonical RAM market row used by `sysio.system` bancor pricing tests. */
function createRamState(
overrides: Record<string, unknown> = {}
): InstanceType<typeof RAMState> {
return RAMState.from({
supply: "10000000000.0000 RAMCORE",
base: {
balance: "10000000000.0000 RAM",
weight: "0.50000000000000000"
},
quote: {
balance: "10000.0000 SYS",
weight: "0.50000000000000000"
},
...overrides
})
}

/** Mocked `Resources` parent that returns a fixed `rammarket` table row. */
function createRamResources(state = createRamState()) {
const getTableRows = jest.fn(async () => ({
rows: [state],
more: false
}))

const resources = new Resources({
api: {
v1: {
chain: {
get_table_rows: getTableRows
}
}
} as any
})

return { resources, getTableRows, state }
}

describe("RAMState", () => {
test("get_input ceil-divides quote * value by (base - value)", () => {
const state = createRamState()
// ceil((100 * 10) / (1000 - 10)) = ceil(1.0101...) = 2
expect(
Number(state.get_input(Int64.from(1000), Int64.from(100), Int64.from(10)))
).toBe(2)
})

test("price_per returns a SYS asset for the requested byte count", () => {
const price = createRamState().price_per(1024)
expect(price).toBeInstanceOf(Asset)
expect(price.symbol.name).toBe("SYS")
expect(price.units.value.gtn(0)).toBe(true)
})

test("price_per_kb prices one kilobyte as 1000 bytes", () => {
const state = createRamState()
expect(state.price_per_kb(1).equals(state.price_per(1000))).toBe(true)
})

test("price_per scales up for larger byte requests on a shallow market", () => {
const state = createRamState({
base: {
balance: "100000.0000 RAM",
weight: "0.50000000000000000"
},
quote: {
balance: "10000.0000 SYS",
weight: "0.50000000000000000"
}
})
expect(
state.price_per(20_000).units.value.gt(state.price_per(1_000).units.value)
).toBe(true)
})
})

describe("RAMAPI", () => {
test("get_state reads sysio/sysio/rammarket and returns the first row", async () => {
const { resources, getTableRows, state } = createRamResources()
const result = await resources.v1.ram.get_state()

expect(result).toBe(state)
expect(getTableRows).toHaveBeenCalledWith({
code: "sysio",
scope: "sysio",
table: "rammarket",
type: RAMState
})
})
})
93 changes: 93 additions & 0 deletions packages/sdk-core/tests/resources/Resources.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { UInt128 } from "@wireio/sdk-core/chain/Integer"
import { BNPrecision, Resources } from "@wireio/sdk-core"
import BN from "bn.js"

/** Account shape required by `Resources.getSampledUsage`. */
function createAccountObject(overrides: Record<string, unknown> = {}) {
return {
cpu_limit: { max: UInt128.from(200_000) },
net_limit: { max: UInt128.from(1_048_576) },
cpu_weight: UInt128.from(10_000),
net_weight: UInt128.from(20_000),
...overrides
}
}

describe("Resources", () => {
test("throws when neither url nor api is provided", () => {
expect(() => new Resources({} as any)).toThrow("Missing url or api client")
})

test("accepts an injected api client and default sample settings", () => {
const api = { v1: { chain: {} } } as any
const resources = new Resources({ api })

expect(resources.api).toBe(api)
expect(resources.sampleAccount).toBe("greymassfuel")
expect(resources.symbol).toBe("4,SYS")
expect(resources.v1.ram).toBeDefined()
expect(resources.v1.rex).toBeDefined()
expect(resources.v1.powerup).toBeDefined()
})

test("honors sampleAccount and symbol overrides", () => {
const resources = new Resources({
api: { v1: { chain: {} } } as any,
sampleAccount: "fuel.sample",
symbol: "4,WIRE"
})

expect(resources.sampleAccount).toBe("fuel.sample")
expect(resources.symbol).toBe("4,WIRE")
})

test("BNPrecision is 1e8", () => {
expect(BNPrecision.eq(new BN(100_000_000))).toBe(true)
})

test("getSampledUsage samples the configured account and ceil-divides weights", async () => {
const account = createAccountObject()
const getAccount = jest.fn(async () => account)
const resources = new Resources({
api: {
v1: {
chain: {
get_account: getAccount
}
}
} as any,
sampleAccount: "fuel.sample"
})

const sample = await resources.getSampledUsage()

expect(getAccount).toHaveBeenCalledWith("fuel.sample")
expect(sample.account).toBe(account)
expect(sample.cpu).toBeInstanceOf(UInt128)
expect(sample.net).toBeInstanceOf(UInt128)

// Mirror Resources.divCeil: (limit * BNPrecision) / weight, then
// subtract one when there is a remainder and the quotient is > 1.
const expectedCpu = divCeilSample(
account.cpu_limit.max.value,
account.cpu_weight.value
)
const expectedNet = divCeilSample(
account.net_limit.max.value,
account.net_weight.value
)

expect(Number(sample.cpu)).toBe(Number(expectedCpu))
expect(Number(sample.net)).toBe(Number(expectedNet))
})
})

/** Local copy of `Resources` sampling math for assertion expected values. */
function divCeilSample(limit: BN, weight: BN): BN {
const num = limit.mul(BNPrecision)
let quotient = num.div(weight)
if (num.mod(weight).gtn(0) && quotient.gtn(1)) {
quotient = quotient.subn(1)
}
return quotient
}
Loading
Loading