|
| 1 | +import { expect } from "chai"; |
| 2 | +import { AbiCoder, decodeRlp, hexlify, zeroPadValue } from "ethers"; |
| 3 | +import { |
| 4 | + createMockReceiptProof, |
| 5 | + calculatePaymentId, |
| 6 | + PAYMENT_INITIATED_TOPIC, |
| 7 | +} from "../helpers/proofHelpers"; |
| 8 | + |
| 9 | +describe("proofHelpers", function () { |
| 10 | + describe("createMockReceiptProof", function () { |
| 11 | + const mockParams = { |
| 12 | + chainId: 1n, |
| 13 | + blockNumber: 100n, |
| 14 | + transactionIndex: 5, |
| 15 | + logIndex: 0, |
| 16 | + vault: "0x1234567890123456789012345678901234567890", |
| 17 | + payer: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", |
| 18 | + recipient: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", |
| 19 | + token: "0xcccccccccccccccccccccccccccccccccccccccc", |
| 20 | + amount: 1000000n, |
| 21 | + paymentId: "0x" + "00".repeat(32), |
| 22 | + }; |
| 23 | + |
| 24 | + it("should return valid proof structure", function () { |
| 25 | + const proof = createMockReceiptProof(mockParams); |
| 26 | + |
| 27 | + expect(proof.chainId).to.equal(mockParams.chainId); |
| 28 | + expect(proof.blockNumber).to.equal(mockParams.blockNumber); |
| 29 | + expect(proof.blockHeader).to.equal("0x"); |
| 30 | + expect(proof.ancestry).to.deep.equal([]); |
| 31 | + expect(proof.logIndex).to.equal(mockParams.logIndex); |
| 32 | + }); |
| 33 | + |
| 34 | + it("should encode transactionIndex as RLP", function () { |
| 35 | + const proof = createMockReceiptProof(mockParams); |
| 36 | + |
| 37 | + // transactionIndex = 5, which is 0x05, single byte < 0x80 |
| 38 | + expect(proof.transactionIndex).to.equal("0x05"); |
| 39 | + }); |
| 40 | + |
| 41 | + it("should encode transactionIndex 0 as 0x80", function () { |
| 42 | + const proof = createMockReceiptProof({ ...mockParams, transactionIndex: 0 }); |
| 43 | + expect(proof.transactionIndex).to.equal("0x80"); |
| 44 | + }); |
| 45 | + |
| 46 | + it("should produce RLP-decodable log", function () { |
| 47 | + const proof = createMockReceiptProof(mockParams); |
| 48 | + |
| 49 | + // Should be decodable without throwing |
| 50 | + const decoded = decodeRlp(proof.log); |
| 51 | + expect(Array.isArray(decoded)).to.be.true; |
| 52 | + expect(decoded.length).to.equal(3); // [address, topics[], data] |
| 53 | + }); |
| 54 | + |
| 55 | + it("should encode log address correctly", function () { |
| 56 | + const proof = createMockReceiptProof(mockParams); |
| 57 | + const decoded = decodeRlp(proof.log) as string[]; |
| 58 | + |
| 59 | + // First element is the vault address |
| 60 | + expect(hexlify(decoded[0]).toLowerCase()).to.equal(mockParams.vault.toLowerCase()); |
| 61 | + }); |
| 62 | + |
| 63 | + it("should include correct number of topics", function () { |
| 64 | + const proof = createMockReceiptProof(mockParams); |
| 65 | + const decoded = decodeRlp(proof.log) as unknown[]; |
| 66 | + |
| 67 | + // Topics: [event_sig, payer, recipient, token] |
| 68 | + expect((decoded[1] as unknown[]).length).to.equal(4); |
| 69 | + }); |
| 70 | + |
| 71 | + it("should encode topics and data payload correctly", function () { |
| 72 | + const proof = createMockReceiptProof(mockParams); |
| 73 | + const decoded = decodeRlp(proof.log) as unknown[]; |
| 74 | + const topics = decoded[1] as unknown[]; |
| 75 | + |
| 76 | + expect(hexlify(topics[0]).toLowerCase()).to.equal(PAYMENT_INITIATED_TOPIC.toLowerCase()); |
| 77 | + expect(hexlify(topics[1]).toLowerCase()).to.equal( |
| 78 | + zeroPadValue(mockParams.payer, 32).toLowerCase() |
| 79 | + ); |
| 80 | + expect(hexlify(topics[2]).toLowerCase()).to.equal( |
| 81 | + zeroPadValue(mockParams.recipient, 32).toLowerCase() |
| 82 | + ); |
| 83 | + expect(hexlify(topics[3]).toLowerCase()).to.equal( |
| 84 | + zeroPadValue(mockParams.token, 32).toLowerCase() |
| 85 | + ); |
| 86 | + |
| 87 | + const [amount, paymentId] = AbiCoder.defaultAbiCoder().decode( |
| 88 | + ["uint256", "bytes32"], |
| 89 | + decoded[2] |
| 90 | + ); |
| 91 | + expect(amount).to.equal(mockParams.amount); |
| 92 | + expect(paymentId).to.equal(mockParams.paymentId); |
| 93 | + }); |
| 94 | + }); |
| 95 | + |
| 96 | + describe("calculatePaymentId", function () { |
| 97 | + it("should return consistent payment ID", function () { |
| 98 | + const params = { |
| 99 | + chainId: 1n, |
| 100 | + vault: "0x1234567890123456789012345678901234567890", |
| 101 | + blockNumber: 100n, |
| 102 | + transactionIndex: 5, |
| 103 | + logIndex: 0, |
| 104 | + }; |
| 105 | + |
| 106 | + const id1 = calculatePaymentId(params); |
| 107 | + const id2 = calculatePaymentId(params); |
| 108 | + |
| 109 | + expect(id1).to.equal(id2); |
| 110 | + expect(id1).to.have.length(66); // 0x + 64 hex chars |
| 111 | + }); |
| 112 | + |
| 113 | + it("should return different IDs for different inputs", function () { |
| 114 | + const params1 = { |
| 115 | + chainId: 1n, |
| 116 | + vault: "0x1234567890123456789012345678901234567890", |
| 117 | + blockNumber: 100n, |
| 118 | + transactionIndex: 5, |
| 119 | + logIndex: 0, |
| 120 | + }; |
| 121 | + const params2 = { ...params1, blockNumber: 101n }; |
| 122 | + |
| 123 | + const id1 = calculatePaymentId(params1); |
| 124 | + const id2 = calculatePaymentId(params2); |
| 125 | + |
| 126 | + expect(id1).to.not.equal(id2); |
| 127 | + }); |
| 128 | + }); |
| 129 | + |
| 130 | + describe("PAYMENT_INITIATED_TOPIC", function () { |
| 131 | + it("should be a valid 32-byte keccak256 hash", function () { |
| 132 | + expect(PAYMENT_INITIATED_TOPIC).to.have.length(66); // 0x + 64 hex chars |
| 133 | + expect(PAYMENT_INITIATED_TOPIC).to.match(/^0x[a-f0-9]{64}$/i); |
| 134 | + }); |
| 135 | + }); |
| 136 | +}); |
0 commit comments