ox
Version:
Ethereum Standard Library
286 lines (254 loc) • 7.51 kB
text/typescript
import { MultisigConfig } from 'ox/tempo'
import { describe, expect, test } from 'vite-plus/test'
// Ground-truth vectors independently computed via `cast keccak` over the exact
// preimages defined by TIP-1061 / the Tempo reference implementation.
const owner1 = '0x1111111111111111111111111111111111111111'
const owner2 = '0x2222222222222222222222222222222222222222'
const singleOwnerConfig = {
threshold: 1,
owners: [{ owner: owner1, weight: 1 }],
} as const
describe('from', () => {
test('sorts owners ascending by address', () => {
const config = MultisigConfig.from({
threshold: 2,
owners: [
{ owner: owner2, weight: 1 },
{ owner: owner1, weight: 1 },
],
})
expect(config.owners.map((o) => o.owner)).toEqual([owner1, owner2])
})
test('asserts validity', () => {
expect(() =>
MultisigConfig.from({ threshold: 0, owners: [] }),
).toThrowError()
})
})
describe('getAddress', () => {
test('matches independent ground truth', () => {
expect(MultisigConfig.getAddress(singleOwnerConfig)).toMatchInlineSnapshot(
`"0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da"`,
)
})
test('matches independent ground truth (salt + weights)', () => {
expect(
MultisigConfig.getAddress({
salt: `0x${'42'.repeat(32)}`,
threshold: 2,
owners: [
{ owner: owner1, weight: 1 },
{ owner: owner2, weight: 2 },
],
}),
).toMatchInlineSnapshot(`"0x0773e28146400643e42cb28f6659b74e7c0b451d"`)
})
test('is stable across calls', () => {
expect(MultisigConfig.getAddress(singleOwnerConfig)).toBe(
MultisigConfig.getAddress(singleOwnerConfig),
)
})
test('differs for a different salt', () => {
expect(MultisigConfig.getAddress(singleOwnerConfig)).not.toBe(
MultisigConfig.getAddress({
...singleOwnerConfig,
salt: `0x${'42'.repeat(32)}`,
}),
)
})
test('address is chain-independent', () => {
// Derivation does not include chain ID; identical config → identical address.
const a = MultisigConfig.getAddress(singleOwnerConfig)
const b = MultisigConfig.getAddress(MultisigConfig.from(singleOwnerConfig))
expect(a).toBe(b)
})
test('throws on invalid config', () => {
expect(() =>
MultisigConfig.getAddress({
threshold: 5,
owners: singleOwnerConfig.owners,
}),
).toThrowError()
})
})
describe('getSignPayload', () => {
test('matches independent ground truth', () => {
expect(
MultisigConfig.getSignPayload({
payload: `0x${'de'.repeat(32)}`,
genesisConfig: singleOwnerConfig,
}),
).toMatchInlineSnapshot(
`"0x7df8cb9fef4fc2aeb271b617d1a4c6178b720ae1d7564f48a363069b7d77a079"`,
)
})
test('behavior: `genesisConfig` and `{ account }` produce identical digests', () => {
const account = MultisigConfig.getAddress(singleOwnerConfig)
const payload = `0x${'42'.repeat(32)}` as const
expect(
MultisigConfig.getSignPayload({
payload,
genesisConfig: singleOwnerConfig,
}),
).toBe(MultisigConfig.getSignPayload({ payload, account }))
})
})
describe('toTuple / fromTuple', () => {
test('round-trips', () => {
const config = MultisigConfig.from({
threshold: 3,
owners: [
{ owner: owner1, weight: 1 },
{ owner: owner2, weight: 2 },
],
})
const tuple = MultisigConfig.toTuple(config)
expect(MultisigConfig.fromTuple(tuple)).toEqual(config)
})
test('encodes each owner as `[owner, weight]`', () => {
const [, , owners] = MultisigConfig.toTuple(singleOwnerConfig)
expect(owners[0]).toEqual([owner1, '0x1'])
})
test('encodes salt as a full 32-byte string (first element)', () => {
const [salt] = MultisigConfig.toTuple(singleOwnerConfig)
expect(salt).toBe(MultisigConfig.zeroSalt)
})
test('round-trips a non-zero salt', () => {
const config = MultisigConfig.from({
...singleOwnerConfig,
salt: `0x${'42'.repeat(32)}`,
})
const tuple = MultisigConfig.toTuple(config)
expect(tuple[0]).toBe(`0x${'42'.repeat(32)}`)
expect(MultisigConfig.fromTuple(tuple)).toEqual(config)
})
})
describe('assert / validate', () => {
test('valid config', () => {
expect(MultisigConfig.validate(singleOwnerConfig)).toBe(true)
})
test('empty owners', () => {
expect(MultisigConfig.validate({ threshold: 1, owners: [] })).toBe(false)
})
test('accepts 255 owners', () => {
const owners = Array.from({ length: 255 }, (_, i) => ({
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
weight: 1,
}))
expect(
MultisigConfig.validate({
threshold: MultisigConfig.maxThreshold,
owners,
}),
).toBe(true)
})
test('too many owners', () => {
const owners = Array.from({ length: 256 }, (_, i) => ({
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
weight: 1,
}))
expect(MultisigConfig.validate({ threshold: 1, owners })).toBe(false)
})
test('zero threshold', () => {
expect(
MultisigConfig.validate({
threshold: 0,
owners: singleOwnerConfig.owners,
}),
).toBe(false)
})
test('threshold exceeds protocol maximum', () => {
expect(
MultisigConfig.validate({
threshold: MultisigConfig.maxThreshold + 1,
owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold + 1 }],
}),
).toBe(false)
})
test('fractional threshold', () => {
expect(
MultisigConfig.validate({
threshold: 1.5,
owners: [{ owner: owner1, weight: 2 }],
}),
).toBe(false)
})
test('threshold exceeds total weight', () => {
expect(
MultisigConfig.validate({
threshold: 2,
owners: singleOwnerConfig.owners,
}),
).toBe(false)
})
test('total weight exceeds u8 max', () => {
expect(
MultisigConfig.validate({
threshold: MultisigConfig.maxThreshold,
owners: [
{ owner: owner1, weight: 128 },
{ owner: owner2, weight: 128 },
],
}),
).toBe(false)
})
test('zero owner weight', () => {
expect(
MultisigConfig.validate({
threshold: 1,
owners: [{ owner: owner1, weight: 0 }],
}),
).toBe(false)
})
test('fractional owner weight', () => {
expect(
MultisigConfig.validate({
threshold: 1,
owners: [{ owner: owner1, weight: 1.5 }],
}),
).toBe(false)
})
test('zero owner address', () => {
expect(
MultisigConfig.validate({
threshold: 1,
owners: [
{
owner: '0x0000000000000000000000000000000000000000',
weight: 1,
},
],
}),
).toBe(false)
})
test('unsorted owners', () => {
expect(
MultisigConfig.validate({
threshold: 1,
owners: [
{ owner: owner2, weight: 1 },
{ owner: owner1, weight: 1 },
],
}),
).toBe(false)
})
test('duplicate owners', () => {
expect(
MultisigConfig.validate({
threshold: 1,
owners: [
{ owner: owner1, weight: 1 },
{ owner: owner1, weight: 1 },
],
}),
).toBe(false)
})
test('invalid salt size', () => {
expect(
MultisigConfig.validate({
...singleOwnerConfig,
salt: '0x42',
}),
).toBe(false)
})
})