Skip to content

Commit

Permalink
test checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
TateB committed Oct 9, 2024
1 parent 8c1857c commit b964860
Show file tree
Hide file tree
Showing 12 changed files with 3,191 additions and 143 deletions.
79 changes: 79 additions & 0 deletions contracts/test/TestBytesArrayValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../universalResolver/BytesArrayValidator.sol";

contract TestBytesArrayValidator {
function testValidBytesArray() public pure {
// Create a valid bytes array
bytes[] memory validArray = new bytes[](5);
validArray[0] = "Hello";
validArray[1] = "World";
validArray[2] = "Two";
validArray[3] = "Three";
validArray[4] = "Four";
bytes memory encodedValidArray = abi.encode(validArray);

bool isValid = BytesArrayValidator.isValidBytesArray(encodedValidArray);
require(isValid, "Should be a valid bytes array");
}

function testInvalidBytesArray() public pure {
// Create an invalid bytes array (too short)
bytes memory invalidArray = new bytes(16);

bool isValid = BytesArrayValidator.isValidBytesArray(invalidArray);
require(!isValid, "Should be an invalid bytes array");
}

function testEmptyBytesArray() public pure {
// Create an empty bytes array
bytes[] memory emptyArray = new bytes[](0);
bytes memory encodedEmptyArray = abi.encode(emptyArray);

bool isValid = BytesArrayValidator.isValidBytesArray(encodedEmptyArray);
require(isValid, "Empty array should be valid");
}

function testEmptyItemInBytesArray() public pure {
// Create an empty bytes array
bytes[] memory emptyArray = new bytes[](1);
emptyArray[0] = "";
bytes memory encodedEmptyArray = abi.encode(emptyArray);

bool isValid = BytesArrayValidator.isValidBytesArray(encodedEmptyArray);
require(isValid, "Empty array should be valid");
}

function largeEmptyBytesArray() public pure {
bytes[] memory largeArray = new bytes[](1000);
bytes memory encodedLargeArray = abi.encode(largeArray);

bool isValid = BytesArrayValidator.isValidBytesArray(encodedLargeArray);
require(isValid, "Large array should be valid");
}

function testLargeBytesArray() public pure {
// Create a large bytes array
bytes[] memory largeArray = new bytes[](1000);
for (uint i = 0; i < 1000; i++) {
largeArray[i] = new bytes(100);
}
bytes memory encodedLargeArray = abi.encode(largeArray);

bool isValid = BytesArrayValidator.isValidBytesArray(encodedLargeArray);
require(isValid, "Large array should be valid");
}

function testInvalidOffsets() public pure {
// Create an invalid bytes array with incorrect offsets
bytes memory invalidOffsets = new bytes(128);
// Set an invalid offset
assembly {
mstore(add(invalidOffsets, 64), 0x20)
}

bool isValid = BytesArrayValidator.isValidBytesArray(invalidOffsets);
require(!isValid, "Array with invalid offsets should be invalid");
}
}
32 changes: 32 additions & 0 deletions contracts/test/TestUserCallbackFunctions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../universalResolver/ERC3668Caller.sol";
import "hardhat/console.sol";

contract TestUserCallbackFunctions is ERC3668Caller {
function testCreateUserCallbackFunctions() public view {
bytes4 internalCallbackFunction = bytes4(0x11111111);
bytes4 calldataRewriteFunction = bytes4(0x22222222);
bytes4 failureCallbackFunction = bytes4(0x33333333);
bytes4 validateResponseFunction = bytes4(0x44444444);

uint256 gasBefore = gasleft();
uint256 callbackFunctions = createUserCallbackFunctions(
internalCallbackFunction,
calldataRewriteFunction,
failureCallbackFunction,
validateResponseFunction
);
uint256 gasAfter = gasleft();
console.log("gas", gasBefore - gasAfter);

console.logBytes32(bytes32(callbackFunctions));

require(
callbackFunctions ==
0x0000000000000000000000000000000044444444333333332222222211111111,
"Callback functions should be correct"
);
}
}
50 changes: 50 additions & 0 deletions contracts/test/mocks/DummyAddrOffchainResolver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../../../contracts/resolvers/profiles/IAddrResolver.sol";
import "../../../contracts/resolvers/profiles/IExtendedResolver.sol";

error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);

contract DummyAddrOffchainResolver is IAddrResolver, ERC165 {
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(IAddrResolver).interfaceId ||
super.supportsInterface(interfaceId);
}

function addr(bytes32) external view returns (address payable) {
string[] memory urls = new string[](1);
urls[0] = "https://example.com/";

bytes memory data = abi.encode(address(this));

revert OffchainLookup(
address(this),
urls,
data,
this.addrCallback.selector,
data
);
}

function addrOnchain(bytes32) external view returns (address) {
return address(this);
}

function addrCallback(
bytes calldata response,
bytes calldata extraData
) external view returns (address) {
return abi.decode(response, (address));
}
}
36 changes: 28 additions & 8 deletions contracts/test/mocks/DummyOffchainResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,34 @@ contract DummyOffchainResolver is IExtendedResolver, ERC165 {
function resolveCallback(
bytes calldata response,
bytes calldata extraData
) external view returns (bytes memory) {
require(
keccak256(response) == keccak256(extraData),
"Response data error"
);
if (bytes4(extraData) == bytes4(keccak256("name(bytes32)"))) {
return abi.encode("offchain.test.eth");
) external pure returns (bytes memory) {
if (bytes4(extraData) == bytes4(keccak256("pubkey(bytes32)"))) {
revert();
}
if (bytes4(extraData) == bytes4(keccak256("contenthash(bytes32)"))) {
revert("Unsupported call");
}
if (bytes4(extraData) != bytes4(keccak256("multicall(bytes[])"))) {
return response;
}

bytes[] memory results = abi.decode(response, (bytes[]));
bytes[] memory calls = abi.decode(extraData[4:], (bytes[]));
for (uint256 i = 0; i < calls.length; i++) {
if (bytes4(calls[i]) == bytes4(keccak256("text(bytes32,string)"))) {
calls[i] = results[i];
} else if (bytes4(calls[i]) == bytes4(keccak256("addr(bytes32)"))) {
calls[i] = results[i];
} else if (bytes4(calls[i]) == bytes4(keccak256("name(bytes32)"))) {
return "";
} else if (
bytes4(calls[i]) == bytes4(keccak256("pubkey(bytes32)"))
) {
calls[i] = "";
} else {
revert("Unsupported call");
}
}
return abi.encode(address(this));
return abi.encode(calls);
}
}
50 changes: 20 additions & 30 deletions contracts/universalResolver/BytesArrayValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,49 @@ pragma solidity ^0.8.4;
library BytesArrayValidator {
function isValidBytesArray(bytes memory data) internal pure returns (bool) {
// The data must be at least 32 bytes long to contain the array length
if (data.length < 32) {
return false;
}
if (data.length < 32) return false;

uint256 N; // Number of elements in the bytes[] array
uint256 arrayLength;
assembly {
N := mload(add(data, 32)) // Read the array length from data[32..63]
arrayLength := mload(add(data, 64))
}

// Limit N to a reasonable size to prevent excessive computation
if (N > 1e6) {
return false;
}
// Limit length to a reasonable size to prevent excessive computation
if (arrayLength > 1e6) return false;

uint256 offsetsStart = 32; // The offsets start after the array length
uint256 offsetsEnd = offsetsStart + N * 32;
uint256 offsetsStart = 64;
uint256 offsetsEnd = offsetsStart + arrayLength * 32;

// The data must be long enough to include all offsets
if (data.length < offsetsEnd) {
return false;
if (arrayLength > 0) {
if (data.length < offsetsEnd) return false;
} else {
if (data.length != 64) return false;
}

// Loop through each offset and validate the corresponding data
for (uint256 i = 0; i < N; i++) {
for (uint256 i = 0; i < arrayLength; i++) {
uint256 offset_i;
assembly {
// Read the i-th offset from data[32 + i*32 .. 32 + (i+1)*32 - 1]
offset_i := mload(add(add(data, offsetsStart), mul(32, i)))
}

// Offsets should point beyond the header (array length and offsets)
if (offset_i < offsetsEnd) {
return false;
offset_i := mload(
add(add(data, 32), add(offsetsStart, mul(i, 32)))
)
}

// The offset plus 32 bytes must be within the data length to read the length of the element
if (offset_i + 32 > data.length) {
return false;
}
if (offsetsStart + offset_i + 32 > data.length) return false;

uint256 Li; // Length of the i-th byte array
assembly {
// Read the length of the element from data[32 + offset_i .. 32 + offset_i + 31]
Li := mload(add(add(data, 32), offset_i))
Li := mload(add(add(add(data, 32), offset_i), offsetsStart))
}

// Calculate the end position of the element's data
uint256 elementDataEnd = offset_i + 32 + Li;
uint256 elementDataEnd = offset_i +
(Li % 32 == 0 ? Li : Li + (32 - (Li % 32)));

// The element data must be within the bounds of the data array
if (elementDataEnd > data.length) {
return false;
}
if (elementDataEnd > data.length) return false;
}

// All checks passed; the data is a valid ABI-encoded bytes[]
Expand Down
1 change: 1 addition & 0 deletions contracts/universalResolver/ENSIP10ResolverFinder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ abstract contract ENSIP10ResolverFinder {
if (labelLength == 0) {
return (address(0), bytes32(0), offset);
}
// TODO: ensure name has enough length to avoid out-of-bounds
uint256 nextLabel = offset + labelLength + 1;
bytes32 labelHash;
if (
Expand Down
Loading

0 comments on commit b964860

Please sign in to comment.