Skip to content

Commit c8d93d6

Browse files
authored
Added errAllow with mstore and mload (#78)
* Added errAllow with mstore and mload * Added fuzz test for errAllow equivalence
1 parent def6c99 commit c8d93d6

2 files changed

Lines changed: 268 additions & 1 deletion

File tree

src/helpers/HelperAssert.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,9 @@ abstract contract HelperAssert is HelperBase {
337337
// strippedDataPtr: memory position of where to copy the data
338338
// errorDataPtr: memory position of what to copy
339339
// dataLength: length of the data to copy
340-
mcopy(strippedDataPtr, errorDataPtr, dataLength)
340+
for { let i := 0 } lt(i, dataLength) { i := add(i, 32) } {
341+
mstore(add(strippedDataPtr, i), mload(add(errorDataPtr, i)))
342+
}
341343

342344
// now "strippedData" is the error message (string) without selector
343345
}

test/HelperAssert.t.sol

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,4 +1414,269 @@ contract TestHelperAssert is Test, HelperAssert, ErrAllowTestHelper {
14141414
require(!success3, "should fail");
14151415
assertFalse(_isErrorString(bytes4(emptyErrorData)), "empty error should not be Error(string) type");
14161416
}
1417+
1418+
/**
1419+
* @dev Alternative implementation of errAllow using mcopy opcode for testing equivalence.
1420+
*/
1421+
function errAllow_mcopy(
1422+
bytes memory errorData,
1423+
string[] memory allowedRequireErrorMessages,
1424+
string memory errorContext
1425+
) public {
1426+
// space for error message without selector (4 bytes)
1427+
bytes memory strippedData = new bytes(errorData.length - 4);
1428+
assembly {
1429+
// calculate the memory position of strippedData
1430+
let strippedDataPtr := add(strippedData, 32)
1431+
// calculate the memory position of errorData (4 bytes for selector)
1432+
let errorDataPtr := add(add(errorData, 32), 4)
1433+
// calculate the data length
1434+
let dataLength := sub(mload(errorData), 4)
1435+
// copy the data
1436+
// strippedDataPtr: memory position of where to copy the data
1437+
// errorDataPtr: memory position of what to copy
1438+
// dataLength: length of the data to copy
1439+
mcopy(strippedDataPtr, errorDataPtr, dataLength)
1440+
1441+
// now "strippedData" is the error message (string) without selector
1442+
}
1443+
1444+
// extract the string from the remaining data
1445+
string memory decodedString = abi.decode(strippedData, (string));
1446+
1447+
// compare with allowedRequireErrorMessages
1448+
bool allowed = false;
1449+
for (uint256 i = 0; i < allowedRequireErrorMessages.length; i++) {
1450+
if (keccak256(abi.encode(decodedString)) == keccak256(abi.encode(allowedRequireErrorMessages[i]))) {
1451+
allowed = true;
1452+
break;
1453+
}
1454+
}
1455+
1456+
t(allowed, errorContext);
1457+
}
1458+
1459+
/**
1460+
* Tests for errAllow vs errAllow_mcopy equivalence
1461+
*/
1462+
function test_errAllow_equivalence_short_message() public {
1463+
string[] memory allowedErrors = new string[](1);
1464+
allowedErrors[0] = "short";
1465+
1466+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), "short");
1467+
1468+
// Both should pass with allowed message
1469+
errAllow(errorData, allowedErrors, "errAllow test");
1470+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1471+
}
1472+
1473+
function test_errAllow_equivalence_short_message_fail() public {
1474+
string[] memory allowedErrors = new string[](1);
1475+
allowedErrors[0] = "expected";
1476+
1477+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), "different");
1478+
1479+
// Both should fail with disallowed message
1480+
vm.expectEmit(false, false, false, true);
1481+
emit AssertFail("errAllow test");
1482+
vm.expectRevert(PlatformTest.TestAssertFail.selector);
1483+
errAllow(errorData, allowedErrors, "errAllow test");
1484+
1485+
vm.expectEmit(false, false, false, true);
1486+
emit AssertFail("errAllow_mcopy test");
1487+
vm.expectRevert(PlatformTest.TestAssertFail.selector);
1488+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1489+
}
1490+
1491+
function test_errAllow_equivalence_32_byte_message() public {
1492+
// Exactly 32 bytes (one word)
1493+
string memory message = "12345678901234567890123456789012";
1494+
string[] memory allowedErrors = new string[](1);
1495+
allowedErrors[0] = message;
1496+
1497+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1498+
1499+
// Both should pass
1500+
errAllow(errorData, allowedErrors, "errAllow test");
1501+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1502+
}
1503+
1504+
function test_errAllow_equivalence_31_byte_message() public {
1505+
// Just under 32 bytes
1506+
string memory message = "1234567890123456789012345678901";
1507+
string[] memory allowedErrors = new string[](1);
1508+
allowedErrors[0] = message;
1509+
1510+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1511+
1512+
// Both should pass
1513+
errAllow(errorData, allowedErrors, "errAllow test");
1514+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1515+
}
1516+
1517+
function test_errAllow_equivalence_33_byte_message() public {
1518+
// Just over 32 bytes
1519+
string memory message = "123456789012345678901234567890123";
1520+
string[] memory allowedErrors = new string[](1);
1521+
allowedErrors[0] = message;
1522+
1523+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1524+
1525+
// Both should pass
1526+
errAllow(errorData, allowedErrors, "errAllow test");
1527+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1528+
}
1529+
1530+
function test_errAllow_equivalence_64_byte_message() public {
1531+
// Exactly two words
1532+
string memory message = "1234567890123456789012345678901212345678901234567890123456789012";
1533+
string[] memory allowedErrors = new string[](1);
1534+
allowedErrors[0] = message;
1535+
1536+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1537+
1538+
// Both should pass
1539+
errAllow(errorData, allowedErrors, "errAllow test");
1540+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1541+
}
1542+
1543+
function test_errAllow_equivalence_long_message() public {
1544+
// Very long message (over 100 bytes)
1545+
string memory message =
1546+
"This is a very long error message that exceeds multiple memory words to test proper copying behavior";
1547+
string[] memory allowedErrors = new string[](1);
1548+
allowedErrors[0] = message;
1549+
1550+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1551+
1552+
// Both should pass
1553+
errAllow(errorData, allowedErrors, "errAllow test");
1554+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1555+
}
1556+
1557+
function test_errAllow_equivalence_special_characters() public {
1558+
string memory message = "Error: Value out of range! @#$%^&*()";
1559+
string[] memory allowedErrors = new string[](1);
1560+
allowedErrors[0] = message;
1561+
1562+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1563+
1564+
// Both should pass
1565+
errAllow(errorData, allowedErrors, "errAllow test");
1566+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1567+
}
1568+
1569+
function test_errAllow_equivalence_unicode_characters() public {
1570+
string memory message = unicode"Error: Invalid value \u2713 \u2717";
1571+
string[] memory allowedErrors = new string[](1);
1572+
allowedErrors[0] = message;
1573+
1574+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1575+
1576+
// Both should pass
1577+
errAllow(errorData, allowedErrors, "errAllow test");
1578+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1579+
}
1580+
1581+
function test_errAllow_equivalence_empty_message() public {
1582+
string memory message = "";
1583+
string[] memory allowedErrors = new string[](1);
1584+
allowedErrors[0] = message;
1585+
1586+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1587+
1588+
// Both should pass
1589+
errAllow(errorData, allowedErrors, "errAllow test");
1590+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1591+
}
1592+
1593+
function test_errAllow_equivalence_single_character() public {
1594+
string memory message = "x";
1595+
string[] memory allowedErrors = new string[](1);
1596+
allowedErrors[0] = message;
1597+
1598+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1599+
1600+
// Both should pass
1601+
errAllow(errorData, allowedErrors, "errAllow test");
1602+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1603+
}
1604+
1605+
function test_errAllow_equivalence_multiple_allowed_messages() public {
1606+
string[] memory allowedErrors = new string[](3);
1607+
allowedErrors[0] = "first error";
1608+
allowedErrors[1] = "second error";
1609+
allowedErrors[2] = "third error";
1610+
1611+
// Test first message
1612+
bytes memory errorData1 = abi.encodeWithSelector(bytes4(0x08c379a0), "first error");
1613+
errAllow(errorData1, allowedErrors, "errAllow test 1");
1614+
errAllow_mcopy(errorData1, allowedErrors, "errAllow_mcopy test 1");
1615+
1616+
// Test middle message
1617+
bytes memory errorData2 = abi.encodeWithSelector(bytes4(0x08c379a0), "second error");
1618+
errAllow(errorData2, allowedErrors, "errAllow test 2");
1619+
errAllow_mcopy(errorData2, allowedErrors, "errAllow_mcopy test 2");
1620+
1621+
// Test last message
1622+
bytes memory errorData3 = abi.encodeWithSelector(bytes4(0x08c379a0), "third error");
1623+
errAllow(errorData3, allowedErrors, "errAllow test 3");
1624+
errAllow_mcopy(errorData3, allowedErrors, "errAllow_mcopy test 3");
1625+
}
1626+
1627+
function test_errAllow_equivalence_numbers_in_message() public {
1628+
string memory message = "Error code: 12345678901234567890";
1629+
string[] memory allowedErrors = new string[](1);
1630+
allowedErrors[0] = message;
1631+
1632+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), message);
1633+
1634+
// Both should pass
1635+
errAllow(errorData, allowedErrors, "errAllow test");
1636+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1637+
}
1638+
1639+
function test_errAllow_equivalence_real_dummy_error() public {
1640+
// Use actual dummy contract error
1641+
string[] memory allowedErrors = new string[](1);
1642+
allowedErrors[0] = "require failure message 1";
1643+
1644+
(bool success, bytes memory errorData) =
1645+
address(dummy).call(abi.encodeWithSignature("requireFailWithMessage()"));
1646+
require(!success, "should fail");
1647+
1648+
// Both should pass with real error data
1649+
errAllow(errorData, allowedErrors, "errAllow test");
1650+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1651+
}
1652+
1653+
function testFuzz_errAllow_equivalence(string[] memory allowedErrors, string memory actualMsg) public {
1654+
bytes memory errorData = abi.encodeWithSelector(bytes4(0x08c379a0), actualMsg);
1655+
1656+
// Check if actualMsg matches any in allowedErrors
1657+
bool shouldMatch = false;
1658+
for (uint256 i = 0; i < allowedErrors.length; i++) {
1659+
if (keccak256(abi.encode(allowedErrors[i])) == keccak256(abi.encode(actualMsg))) {
1660+
shouldMatch = true;
1661+
break;
1662+
}
1663+
}
1664+
1665+
if (shouldMatch) {
1666+
// Both should pass when actualMsg is in allowedErrors
1667+
errAllow(errorData, allowedErrors, "errAllow test");
1668+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1669+
} else {
1670+
// Both should fail when actualMsg is not in allowedErrors
1671+
vm.expectEmit(false, false, false, true);
1672+
emit AssertFail("errAllow test");
1673+
vm.expectRevert(PlatformTest.TestAssertFail.selector);
1674+
errAllow(errorData, allowedErrors, "errAllow test");
1675+
1676+
vm.expectEmit(false, false, false, true);
1677+
emit AssertFail("errAllow_mcopy test");
1678+
vm.expectRevert(PlatformTest.TestAssertFail.selector);
1679+
errAllow_mcopy(errorData, allowedErrors, "errAllow_mcopy test");
1680+
}
1681+
}
14171682
}

0 commit comments

Comments
 (0)