synapsecns/sanguine

View on GitHub
packages/contracts-rfq/test/libs/ZapDataV1.t.sol

Summary

Maintainability
Test Coverage
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {ZapDataV1, ZapDataV1Harness} from "../harnesses/ZapDataV1Harness.sol";

import {Test} from "forge-std/Test.sol";

// solhint-disable func-name-mixedcase, ordering
contract ZapDataV1Test is Test {
    uint16 internal constant EXPECTED_VERSION = 1;

    ZapDataV1Harness internal harness;

    function setUp() public {
        harness = new ZapDataV1Harness();
    }

    function encodeZapData(
        uint16 version,
        uint16 amountPosition,
        address finalToken,
        address forwardTo,
        address target,
        bytes memory payload
    )
        public
        pure
        returns (bytes memory)
    {
        return abi.encodePacked(version, amountPosition, finalToken, forwardTo, target, payload);
    }

    function test_roundtrip_withAmount(
        address finalToken,
        address forwardTo,
        address target,
        uint256 amount,
        bytes memory prefix,
        bytes memory postfix
    )
        public
        view
    {
        vm.assume(prefix.length + 32 + postfix.length < type(uint16).max);
        vm.assume(target != address(0));

        // We don't know the amount at the time of encoding, so we provide a placeholder.
        uint16 amountPosition = uint16(prefix.length);
        bytes memory encodedPayload = abi.encodePacked(prefix, uint256(0), postfix);
        // We expect the correct amount to be substituted in the payload at the time of Zap.
        bytes memory finalPayload = abi.encodePacked(prefix, amount, postfix);

        bytes memory zapData = harness.encodeV1(amountPosition, finalToken, forwardTo, target, encodedPayload);

        harness.validateV1(zapData);
        assertEq(harness.version(zapData), 1);
        assertEq(harness.finalToken(zapData), finalToken);
        assertEq(harness.forwardTo(zapData), forwardTo);
        assertEq(harness.target(zapData), target);
        assertEq(harness.payload(zapData, amount), finalPayload);
        // Check against manually encoded ZapData.
        assertEq(
            zapData, encodeZapData(EXPECTED_VERSION, amountPosition, finalToken, forwardTo, target, encodedPayload)
        );
    }

    function test_roundtrip_noAmount(
        address finalToken,
        address forwardTo,
        address target,
        uint256 amount,
        bytes memory payload
    )
        public
        view
    {
        vm.assume(payload.length < type(uint16).max);
        vm.assume(target != address(0));

        uint16 amountPosition = type(uint16).max;
        bytes memory zapData = harness.encodeV1(amountPosition, finalToken, forwardTo, target, payload);

        harness.validateV1(zapData);
        assertEq(harness.version(zapData), 1);
        assertEq(harness.finalToken(zapData), finalToken);
        assertEq(harness.forwardTo(zapData), forwardTo);
        assertEq(harness.target(zapData), target);
        assertEq(harness.payload(zapData, amount), payload);
        // Check against manually encoded ZapData.
        assertEq(zapData, encodeZapData(EXPECTED_VERSION, amountPosition, finalToken, forwardTo, target, payload));
    }

    function test_encodeV1_revert_targetZeroAddress() public {
        vm.expectRevert(ZapDataV1.ZapDataV1__TargetZeroAddress.selector);
        harness.encodeV1(type(uint16).max, address(0), address(0), address(0), "");
    }

    function test_encodeDecodeV1_revert_invalidAmountPosition(
        address finalToken,
        address forwardTo,
        address target,
        uint16 amountPosition,
        uint256 amount,
        bytes memory payload
    )
        public
    {
        vm.assume(payload.length < type(uint16).max);
        vm.assume(target != address(0));
        // Make sure that (amountPosition + 32) is outside the bounds of the payload.
        uint16 incorrectMin = payload.length > 31 ? uint16(payload.length) - 31 : 0;
        uint16 incorrectMax = type(uint16).max - 1;
        amountPosition = uint16(bound(uint256(amountPosition), incorrectMin, incorrectMax));
        bytes memory invalidEncodedZapData =
            encodeZapData(uint16(1), amountPosition, finalToken, forwardTo, target, payload);

        vm.expectRevert(ZapDataV1.ZapDataV1__InvalidEncoding.selector);
        harness.encodeV1(amountPosition, finalToken, forwardTo, target, payload);

        // Validation should pass
        harness.validateV1(invalidEncodedZapData);
        harness.finalToken(invalidEncodedZapData);
        harness.forwardTo(invalidEncodedZapData);
        harness.target(invalidEncodedZapData);
        // But payload extraction should revert
        vm.expectRevert(ZapDataV1.ZapDataV1__InvalidEncoding.selector);
        harness.payload(invalidEncodedZapData, amount);
    }

    function test_validateV1_revert_unsupportedVersion_withAmount(
        uint16 version,
        address finalToken,
        address forwardTo,
        address target,
        bytes memory prefix,
        bytes memory postfix
    )
        public
    {
        vm.assume(version != 1);
        vm.assume(prefix.length + 32 + postfix.length < type(uint16).max);
        // We don't know the amount at the time of encoding, so we provide a placeholder.
        uint16 amountPosition = uint16(prefix.length);
        bytes memory encodedPayload = abi.encodePacked(prefix, uint256(0), postfix);

        bytes memory invalidEncodedZapData =
            encodeZapData(version, amountPosition, finalToken, forwardTo, target, encodedPayload);

        vm.expectRevert(abi.encodeWithSelector(ZapDataV1.ZapDataV1__UnsupportedVersion.selector, version));
        harness.validateV1(invalidEncodedZapData);
    }

    function test_validateV1_revert_unsupportedVersion_noAmount(
        uint16 version,
        address finalToken,
        address forwardTo,
        address target,
        bytes memory payload
    )
        public
    {
        vm.assume(version != 1);
        vm.assume(payload.length < type(uint16).max);

        uint16 amountPosition = type(uint16).max;
        bytes memory invalidEncodedZapData =
            encodeZapData(version, amountPosition, finalToken, forwardTo, target, payload);

        vm.expectRevert(abi.encodeWithSelector(ZapDataV1.ZapDataV1__UnsupportedVersion.selector, version));
        harness.validateV1(invalidEncodedZapData);
    }

    function test_validateV1_revert_invalidLength(bytes calldata fuzzData) public {
        bytes memory minimumValidZapData =
            encodeZapData(EXPECTED_VERSION, type(uint16).max, address(0), address(0), address(0), "");
        uint256 invalidLength = fuzzData.length % minimumValidZapData.length;
        bytes calldata invalidEncodedZapData = fuzzData[:invalidLength];

        vm.expectRevert(ZapDataV1.ZapDataV1__InvalidEncoding.selector);
        harness.validateV1(invalidEncodedZapData);
    }
}