diff --git a/src/CairoLib.sol b/src/CairoLib.sol index ae5a8db..d4a8875 100644 --- a/src/CairoLib.sol +++ b/src/CairoLib.sol @@ -3,8 +3,8 @@ pragma solidity >=0.8.0 <0.9.0; library CairoLib { /// @dev The Cairo precompile contract's address. - address constant CAIRO_MULTICALL_PRECOMPILE= 0x0000000000000000000000000000000000075003; - address constant CAIRO_CALL_PRECOMPILE= 0x0000000000000000000000000000000000075004; + address constant CAIRO_MULTICALL_PRECOMPILE = 0x0000000000000000000000000000000000075003; + address constant CAIRO_CALL_PRECOMPILE = 0x0000000000000000000000000000000000075004; struct CairoCall { uint256 contractAddress; @@ -18,7 +18,10 @@ library CairoLib { /// @param functionSelector The function selector of the Cairo contract function to be called. /// @param data The input data for the Cairo contract function. /// @return The return data from the Cairo contract function. - function callCairo(uint256 contractAddress, uint256 functionSelector, uint256[] memory data) internal returns (bytes memory) { + function callCairo(uint256 contractAddress, uint256 functionSelector, uint256[] memory data) + internal + returns (bytes memory) + { bytes memory callData = abi.encode(contractAddress, functionSelector, data); (bool success, bytes memory result) = CAIRO_CALL_PRECOMPILE.call(callData); @@ -27,10 +30,7 @@ library CairoLib { return result; } - function callCairo(CairoCall memory call) - internal - returns (bytes memory) - { + function callCairo(CairoCall memory call) internal returns (bytes memory) { return callCairo(call.contractAddress, call.functionSelector, call.data); } @@ -45,7 +45,10 @@ library CairoLib { return callCairo(contractAddress, functionSelector, data); } - function callCairo(uint256 contractAddress, string memory functionName, uint256[] memory data) internal returns (bytes memory) { + function callCairo(uint256 contractAddress, string memory functionName, uint256[] memory data) + internal + returns (bytes memory) + { uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250; return callCairo(contractAddress, functionSelector, data); } @@ -56,7 +59,11 @@ library CairoLib { /// @param functionSelector The function selector of the Cairo contract function to be called. /// @param data The input data for the Cairo contract function. /// @return The return data from the Cairo contract function. - function staticcallCairo(uint256 contractAddress, uint256 functionSelector, uint256[] memory data) internal view returns (bytes memory) { + function staticcallCairo(uint256 contractAddress, uint256 functionSelector, uint256[] memory data) + internal + view + returns (bytes memory) + { bytes memory callData = abi.encode(contractAddress, functionSelector, data); (bool success, bytes memory result) = CAIRO_CALL_PRECOMPILE.staticcall(callData); @@ -84,15 +91,10 @@ library CairoLib { return staticcallCairo(contractAddress, functionSelector, data); } - function staticcallCairo(CairoCall memory call) - internal - view - returns (bytes memory) - { + function staticcallCairo(CairoCall memory call) internal view returns (bytes memory) { return staticcallCairo(call.contractAddress, call.functionSelector, call.data); } - /// @notice Performs a multicall to Cairo contracts deployed on Starknet. /// @dev Used with intent to modify the state of the Cairo contract. /// @param calls The array of CairoCall structs to be executed. @@ -136,6 +138,8 @@ library CairoLib { */ /// @param data The Cairo representation of the ByteArray serialized to bytes. function byteArrayToString(bytes memory data) internal pure returns (string memory) { + // It must be at least 96 bytes long (fullWordsLength + pendingWord + pendingWordLen) + // It can be more if fullWordsLength is greater than 0 and fullWords list is not empty require(data.length >= 96, "Invalid byte array length"); uint256 fullWordsLength; @@ -151,8 +155,10 @@ library CairoLib { pendingWord := mload(pendingWordPtr) pendingWordLen := mload(add(pendingWordPtr, 32)) } - - require(pendingWordLen <= 31, "Invalid pending word length"); + // Calculate the expected length of data + uint256 expectedLength = 96 + (fullWordsLength * 32); + require(data.length == expectedLength, "Data length does not match fullWordsLength"); + require(pendingWordLen < 31, "Invalid pending word length"); uint256 totalLength = fullWordsLength * 31 + pendingWordLen; bytes memory result = new bytes(totalLength); diff --git a/test/CairoLib.t.sol b/test/CairoLib.t.sol index 2aa34eb..1f1f7a3 100644 --- a/test/CairoLib.t.sol +++ b/test/CairoLib.t.sol @@ -57,19 +57,32 @@ contract ByteArrayConverterTest is Test { assertEq(result, "", "Conversion failed for empty string"); } - function testInvalidPendingWordLength() public { + function testInvalidPendingWordLength(uint256 pendingWordLen) public { + vm.assume(pendingWordLen > 30); bytes memory input = abi.encodePacked( uint256(0), uint256(0), - uint256(32) // Invalid pendingWordLen + uint256(pendingWordLen) // Invalid pendingWordLen ); vm.expectRevert("Invalid pending word length"); CairoLib.byteArrayToString(input); } - function testInvalidInputLength() public { - bytes memory input = new bytes(95); // Too short to be valid + function testInvalidPendingWordLength31Bytes() public { + bytes memory input = abi.encodePacked( + uint256(0), + uint256(0x4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e), // 31 byte + uint256(31) // Invalid pendingWordLen, because maximum 30 is allowed + ); + + vm.expectRevert("Invalid pending word length"); + CairoLib.byteArrayToString(input); + } + + function testInvalidInputLength(uint256 inputLenght) public { + vm.assume(inputLenght < 96); + bytes memory input = new bytes(inputLenght); // Too short to be valid vm.expectRevert("Invalid byte array length"); CairoLib.byteArrayToString(input); @@ -82,4 +95,33 @@ contract ByteArrayConverterTest is Test { string memory result = CairoLib.byteArrayToString(input); assertEq(result, "MyToken"); } + + function testInvalidFullWordsLength(uint256 fullWordsLength) public { + fullWordsLength = bound(fullWordsLength, 2, 200); + bytes memory data = abi.encodePacked( + uint256(fullWordsLength), // Incorrect fullWordsLength + uint256(0x48656c6c6f20576f726c642c20746869732069732061206c6f6e6765722073), // "Hello World, this is a longer s" + uint256(0x7472696e672e), // "tring." + uint256(6) // pendingWordLen + ); + + vm.expectRevert("Data length does not match fullWordsLength"); + CairoLib.byteArrayToString(data); + } + + function testInvalidMultipleFullWordsLength(uint256 fullWordsLength) public { + fullWordsLength = bound(fullWordsLength, 0, 200); + vm.assume(fullWordsLength != 3); + bytes memory data = abi.encodePacked( + uint256(fullWordsLength), + uint256(0x4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e), + uint256(0x73656374657475722061646970697363696e6720656c69742c207365642064), + uint256(0x6f20656975736d6f642074656d706f7220696e6369646964756e7420757420), + uint256(0x6c61626f726520657420646f6c6f7265206d61676e6120616c697175612e), + uint256(30) + ); + + vm.expectRevert("Data length does not match fullWordsLength"); + CairoLib.byteArrayToString(data); + } }