mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-11 22:38:01 +00:00
109 lines
6.6 KiB
TypeScript
109 lines
6.6 KiB
TypeScript
// Checks a deployLocal.ts deployment on a running anvil as the store service will use it:
|
|
// the service key signs raw transactions over HTTP, and the deploy's real commitment ages apply.
|
|
import { randomBytes } from 'node:crypto'
|
|
import { readFileSync } from 'node:fs'
|
|
import hre from 'hardhat'
|
|
import { BaseError, ContractFunctionRevertedError, createWalletClient, decodeErrorResult, defineChain, encodeFunctionData, getAddress, http, labelhash, namehash, toHex, zeroHash } from 'viem'
|
|
import { privateKeyToAccount } from 'viem/accounts'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import { eip712Types, YEAR } from './fixtures/namesV2.js'
|
|
|
|
const deployment = JSON.parse(readFileSync(process.env.LOCAL_CHAIN_OUT || 'local-chain.json', 'utf8'))
|
|
|
|
// the PR's deploy scripts' settings, which deployLocal.ts copies
|
|
const DEPLOY_MIN_COMMITMENT_AGE = 60n
|
|
const DEPLOY_MAX_COMMITMENT_AGE = 86400n
|
|
const DEPLOY_MAX_LABEL_LENGTH = 63n
|
|
|
|
const connection = await hre.network.connect('localhost')
|
|
const publicClient = await connection.viem.getPublicClient()
|
|
const chain = defineChain({
|
|
id: deployment.chainId,
|
|
name: 'local',
|
|
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
|
rpcUrls: { default: { http: [deployment.rpcUrl] } },
|
|
})
|
|
const service = createWalletClient({ account: privateKeyToAccount(deployment.serviceKey), chain, transport: http(deployment.rpcUrl) })
|
|
const asService = { client: { public: publicClient, wallet: service } }
|
|
// a contract's simulate does not take the wallet's account
|
|
const fromService = { account: service.account.address }
|
|
const controller = await connection.viem.getContractAt('SimplexController', deployment.controller, asService)
|
|
const baseRegistrar = await connection.viem.getContractAt('BaseRegistrarImplementation', deployment.baseRegistrar, asService)
|
|
const sponsor = await connection.viem.getContractAt('SimplexSponsor', deployment.sponsor, asService)
|
|
const resolver = await connection.viem.getContractAt('SimplexResolver', deployment.resolver, asService)
|
|
// the sponsor passes the controller's and base registrar's reverts through, so decode with their errors too
|
|
const revertAbi = [...sponsor.abi, ...controller.abi, ...baseRegistrar.abi].filter((item) => item.type === 'error')
|
|
|
|
async function revertName(call: Promise<unknown>): Promise<string> {
|
|
try {
|
|
await call
|
|
} catch (e) {
|
|
const reverted = e instanceof BaseError ? e.walk((x) => x instanceof ContractFunctionRevertedError) : null
|
|
if (reverted instanceof ContractFunctionRevertedError && reverted.raw)
|
|
return decodeErrorResult({ abi: revertAbi, data: reverted.raw }).errorName
|
|
throw e
|
|
}
|
|
throw new Error('the call did not revert')
|
|
}
|
|
const mined = async (hash: `0x${string}`) =>
|
|
expect((await publicClient.waitForTransactionReceipt({ hash })).status).toBe('success')
|
|
|
|
describe('local deployment of PR #36 for the store service', () => {
|
|
it('is wired as the service config says', async () => {
|
|
expect(await publicClient.getChainId()).toBe(deployment.chainId)
|
|
expect(getAddress(await controller.read.sponsor())).toBe(deployment.sponsor)
|
|
expect(getAddress(await controller.read.defaultResolver())).toBe(deployment.resolver)
|
|
expect(getAddress(await controller.read.beneficiary())).toBe(deployment.guardian)
|
|
expect(getAddress(await sponsor.read.controller())).toBe(deployment.controller)
|
|
expect(await controller.read.minCommitmentAge()).toBe(DEPLOY_MIN_COMMITMENT_AGE)
|
|
expect(await controller.read.maxCommitmentAge()).toBe(DEPLOY_MAX_COMMITMENT_AGE)
|
|
expect(await baseRegistrar.read.maxLabelLength()).toBe(DEPLOY_MAX_LABEL_LENGTH)
|
|
expect(await baseRegistrar.read.controllers([deployment.controller])).toBe(true)
|
|
expect(service.account.address).toBe(deployment.registrar)
|
|
expect(await sponsor.read.registrarAllowance([deployment.registrar])).toBeGreaterThan(0n)
|
|
})
|
|
|
|
it('the service commits, waits minCommitmentAge, and registers with the owner intent', async () => {
|
|
const owner = privateKeyToAccount(toHex(randomBytes(32)))
|
|
const label = 'local' + randomBytes(4).toString('hex')
|
|
const node = namehash(`${label}.${deployment.tld}`)
|
|
const contact = 'https://smp8.simplex.im/a#Y8m3qT0vLw2'
|
|
const data = [encodeFunctionData({ abi: resolver.abi, functionName: 'setText', args: [node, 'simplex.contact', contact] })]
|
|
const reg = { label, owner: owner.address, duration: 2n * YEAR, secret: toHex(randomBytes(32)), resolver: deployment.resolver, data, reverseRecord: 0, referrer: zeroHash } as const
|
|
const tooLong = { ...reg, label: 'a'.repeat(deployment.maxLabelLength + 1), data: [] as `0x${string}`[] }
|
|
|
|
const commitment = await controller.read.makeCommitment([reg])
|
|
await mined(await controller.write.commit([commitment]))
|
|
await mined(await controller.write.commit([await controller.read.makeCommitment([tooLong])]))
|
|
|
|
// revealBefore of the first commitment; the second, mined in the same second or later, lapses no earlier
|
|
const deadline = (await controller.read.commitments([commitment])) + BigInt(deployment.maxCommitmentAge)
|
|
const sign = (r: typeof reg | typeof tooLong, nonce: bigint) =>
|
|
owner.signTypedData({
|
|
domain: { name: 'SimplexSponsor', version: '1', chainId: deployment.chainId, verifyingContract: deployment.sponsor },
|
|
types: eip712Types, primaryType: 'RegisterName',
|
|
message: { label: r.label, owner: r.owner, duration: r.duration, resolver: r.resolver, data: r.data, nonce, deadline },
|
|
})
|
|
const nonce = await sponsor.read.nonces([owner.address])
|
|
const signature = await sign(reg, nonce)
|
|
expect(await revertName(sponsor.simulate.registerWithCredit([reg, nonce, deadline, signature], fromService))).toBe('CommitmentTooNew')
|
|
|
|
await connection.networkHelpers.time.increase(deployment.minCommitmentAge + 1)
|
|
const allowanceBefore = await sponsor.read.registrarAllowance([deployment.registrar])
|
|
const price = await controller.read.rentPriceUSD([label, reg.duration])
|
|
await sponsor.simulate.registerWithCredit([reg, nonce, deadline, signature], fromService)
|
|
await mined(await sponsor.write.registerWithCredit([reg, nonce, deadline, signature]))
|
|
|
|
expect(getAddress(await baseRegistrar.read.ownerOf([BigInt(labelhash(label))]))).toBe(owner.address)
|
|
expect(await resolver.read.text([node, 'simplex.contact'])).toBe(contact)
|
|
expect(await sponsor.read.registrarAllowance([deployment.registrar])).toBe(allowanceBefore - price.base - price.premium)
|
|
expect(await sponsor.read.nonces([owner.address])).toBe(nonce + 1n)
|
|
|
|
const longNonce = await sponsor.read.nonces([owner.address])
|
|
expect(
|
|
await revertName(sponsor.simulate.registerWithCredit([tooLong, longNonce, deadline, await sign(tooLong, longNonce)], fromService)),
|
|
).toBe('LabelTooLong')
|
|
})
|
|
})
|