Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[KGA-43][KGA-110][KGA-111][KGA-57] fix: check data.length and pendingWordLen #13

Merged
merged 1 commit into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions src/CairoLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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);
}

Expand All @@ -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);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
50 changes: 46 additions & 4 deletions test/CairoLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}