duly / test/near.test.mjs · raw
import test from 'node:test';import assert from 'node:assert/strict';import * as ed from '../web/vendor/ed25519.js';import {  b58decode, b58encode, b64encode, concat, decodeSignedDelegate, delegateHash, encodeDelegate,  encodeSignedDelegate, encodeTransaction, hex, outcomeEvents, outcomeFailure, toYocto, TGAS,} from '../web/lib/near.js';const seed = new Uint8Array(32).fill(7);const pub = await ed.getPublicKeyAsync(seed);const fc = { type: 'FunctionCall', method: 'register', args: new TextEncoder().encode('{}'), gas: 30n * TGAS, deposit: toYocto('0.01') };const delegate = { senderId: hex(pub), receiverId: 'dulypay.testnet', actions: [fc], nonce: 5n, maxBlockHeight: 1000n, publicKey: pub };test('base58 round trip, including leading zeros', () => {  for (const b of [[0, 0, 1], [255, 255], [0], [1, 2, 3, 4, 5]]) {    assert.deepEqual([...b58decode(b58encode(Uint8Array.from(b)))], b);  }  assert.throws(() => b58decode('0OIl'));});test('known account key encodes as NEAR does', () => {  // 32 bytes of 1 in base58, from the NEAR docs convention  assert.equal(b58encode(new Uint8Array(32).fill(1)), '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi');});test('signed delegate survives encode then strict decode', async () => {  const sig = await ed.signAsync(await delegateHash(delegate), seed);  const back = decodeSignedDelegate(encodeSignedDelegate(delegate, sig));  assert.equal(back.delegate.senderId, delegate.senderId);  assert.equal(back.delegate.actions[0].method, 'register');  assert.equal(back.delegate.actions[0].deposit, fc.deposit);  assert.equal(back.delegate.nonce, 5n);  assert.deepEqual([...back.signature], [...sig]);  assert.ok(await ed.verifyAsync(sig, await delegateHash(back.delegate), pub));});test('delegate hash uses the NEP-461 prefix, so it can never equal a transaction hash', async () => {  const body = encodeDelegate(delegate);  const plain = new Uint8Array(await crypto.subtle.digest('SHA-256', body));  assert.notDeepEqual([...plain], [...(await delegateHash(delegate))]);  const prefixed = concat(Uint8Array.of(0x6e, 0x01, 0x00, 0x40), body);  const manual = new Uint8Array(await crypto.subtle.digest('SHA-256', prefixed));  assert.deepEqual([...manual], [...(await delegateHash(delegate))]);});const sig64 = new Uint8Array(64);function encoded(actions) {  return encodeSignedDelegate({ ...delegate, actions }, sig64);}test('strict decoder refuses everything except one function call', () => {  assert.throws(() => decodeSignedDelegate(encoded([fc, fc])), /Exactly one action/);  assert.throws(() => decodeSignedDelegate(encoded([])), /Exactly one action/);  assert.throws(() => decodeSignedDelegate(encoded([{ type: 'Transfer', deposit: 5n }])), /Only function calls/);  assert.throws(() => decodeSignedDelegate(encoded([{ type: 'CreateAccount' }])), /Only function calls/);  assert.throws(() => decodeSignedDelegate(encoded([{ type: 'AddFunctionCallKey', publicKey: pub, allowance: 1n, receiverId: 'a', methods: [] }])), /Only function calls/);  assert.throws(() => decodeSignedDelegate(encoded([{ type: 'DeleteKey', publicKey: pub }])), /Only function calls/);  assert.throws(() => decodeSignedDelegate(encoded([{ type: 'Delegate', delegate, signature: sig64 }])), /Only function calls/);});test('strict decoder refuses trailing and truncated bytes', () => {  const good = encoded([fc]);  assert.throws(() => decodeSignedDelegate(concat(good, Uint8Array.of(0))), /Trailing/);  assert.throws(() => decodeSignedDelegate(good.slice(0, good.length - 1)), /Truncated/);  assert.throws(() => decodeSignedDelegate(new Uint8Array(0)), /Truncated/);});test('strict decoder refuses oversized arguments', () => {  const big = { ...fc, args: new Uint8Array(3000) };  assert.throws(() => decodeSignedDelegate(encoded([big])), /Too long/);});test('transaction encoding has the expected layout', () => {  const t = encodeTransaction({ signerId: 'a', publicKey: pub, nonce: 1n, receiverId: 'b', blockHash: new Uint8Array(32), actions: [{ type: 'Transfer', deposit: 1n }] });  // 4+1 signer, 1+32 key, 8 nonce, 4+1 receiver, 32 hash, 4 count, 1+16 transfer  assert.equal(t.length, 5 + 33 + 8 + 5 + 32 + 4 + 17);  assert.equal(t[5], 0);});test('failure and event extraction', () => {  const outcome = {    transaction_outcome: { outcome: { status: { SuccessReceiptId: 'x' } } },    receipts_outcome: [      { outcome: { status: { SuccessValue: '' }, logs: ['hello', 'EVENT_JSON:{"standard":"duly","event":"invoice_created","data":[{"id":"1-1"}]}', 'EVENT_JSON:{bad', 'EVENT_JSON:{"standard":"nep141","event":"ft_transfer","data":[]}'] } },      { outcome: { status: { Failure: { ActionError: { kind: { FunctionCallError: { ExecutionError: 'Smart contract panicked: Nope' } } } } }, logs: [] } },    ],  };  assert.deepEqual(outcomeEvents(outcome), [{ event: 'invoice_created', data: { id: '1-1' } }]);  assert.ok(outcomeFailure(outcome));});test('toYocto', () => {  assert.equal(toYocto('0.006'), 6_000_000_000_000_000_000_000n);  assert.equal(toYocto('1'), 10n ** 24n);  assert.ok(b64encode(Uint8Array.of(1, 2)).length > 0);});