feat: add LOOKUP_DELEGATED_ADDRESS precompile wrapper#6
feat: add LOOKUP_DELEGATED_ADDRESS precompile wrapper#6pali101 wants to merge 5 commits intofilecoin-project:mainfrom
Conversation
Implement a library to wrap the LookupDelegatedAddress precompile
|
|
||
| import {LOOKUP_DELEGATED_ADDRESS} from "./FVMPrecompiles.sol"; | ||
|
|
||
| library FVMLookupDelegatedAddress { |
|
|
||
| // Update free memory pointer | ||
| // Round up to nearest 32-byte boundary | ||
| let newFmp := add(add(delegatedAddress, 0x20), and(add(returnSize, 0x1f), not(0x1f))) |
There was a problem hiding this comment.
can we assume delegated addresses are all the same length?
| uint256 actorIdFull = abi.decode(msg.data, (uint256)); | ||
|
|
||
| // Validate that actor ID fits in u64 (max u64 = 2^64 - 1) | ||
| require(actorIdFull <= type(uint64).max, "Invalid actor ID: exceeds max u64"); |
There was a problem hiding this comment.
Do these errors match the ones in builtin-actors?
| /// @notice Mock a delegated address lookup | ||
| /// @param actorId The actor ID | ||
| /// @param delegatedAddress The delegated address to return (empty bytes means no delegated address) | ||
| function mockLookupDelegatedAddress(uint64 actorId, bytes memory delegatedAddress) external { |
There was a problem hiding this comment.
also provide a function to mock with solidity address.
| using FVMLookupDelegatedAddress for bytes; | ||
|
|
||
| /// @notice Lookup the delegated address of an actor ID | ||
| function lookup(uint64 actorId) external view returns (bytes memory delegatedAddress) { |
There was a problem hiding this comment.
please deploy one of these to calibration and link it in the PR
| // Handle execution failure | ||
| if iszero(success) { | ||
| mstore(0, errorSelector) | ||
| revert(0, 4) |
There was a problem hiding this comment.
this won't work because the error would be at 28..32 after mstore. Instead, revert(28, 4).
| let success := staticcall(gas(), LOOKUP_DELEGATED_ADDRESS, fmp, 32, 0, 0) | ||
|
|
||
| // Handle execution failure | ||
| if iszero(success) { |
| /// @dev Validates that the address is 22 bytes and starts with 0x040a | ||
| /// @param delegatedAddress The delegated address (f410 format) | ||
| /// @return ethAddress The Ethereum-style address (last 20 bytes) | ||
| function toEthAddress(bytes memory delegatedAddress) internal pure returns (address ethAddress) { |
There was a problem hiding this comment.
move to separate library such as FVMAddress
| assertEq(result, expected, "Extracted address should match"); | ||
| } | ||
|
|
||
| function testToEthAddressInvalidLength() public { |
There was a problem hiding this comment.
move toEthAddress tests to separate test file
| // Shift right 240 bits (30 bytes) to get first 2 bytes | ||
| if iszero(eq(shr(240, firstWord), 0x040a)) { | ||
| mstore(0, errorSelector) | ||
| revert(0, 4) |
There was a problem hiding this comment.
I had to test this to be sure! It looks like because errorSelector is passed in as a Solidity bytes4, it actually gets left-aligned on the stack. That puts it at the very beginning of the memory word, making revert(0, 4) the correct offset here.
If we were using a raw Yul literal inside the assembly block, you'd be exactly right with revert(28, 4).
Quick test to confirm
revert(0, 4) correctly returns the selector, while revert(28, 4) returns zeros.
error PrecompileCallFailed();
// Reverts with 0xfd23ff64 (PrecompileCallFailed)
function rawStore() public pure returns (bytes32) {
bytes4 selector = PrecompileCallFailed.selector;
assembly ("memory-safe") {
mstore(0, selector)
revert(0,4)
}
}
// Reverts with 0x00000000
function rawStore28() public pure returns (bytes32) {
bytes4 selector = PrecompileCallFailed.selector;
assembly ("memory-safe") {
mstore(0, selector)
revert(28,4)
}
}There was a problem hiding this comment.
That's pretty annoying because there is a difference in codesize and likely gas. Compare hardcoding the selector
There was a problem hiding this comment.
I'll update the code to use the hardcoded selector (0x8eb60a41) and revert(28, 4). Just a heads up, I'm moving all this work to a new branch and will raise a fresh PR with all the comments addressed by tonight.
Summary
Implements the
LOOKUP_DELEGATED_ADDRESSprecompile wrapper (0xfe...02) to retrieve an actor's delegated address (f4) and extract the Ethereum-style address using optimized inline assembly.Key Changes
Library: Added
FVMLookupDelegatedAddresswith:lookupDelegatedAddress(uint64) → bytes: Retrieves the raw delegated address. Handles the "success with no data" case for actors without a delegated address.lookupDelegatedAddressStrict(uint64) → bytes: Reverts withNoDelegatedAddress()if the lookup returns empty.toEthAddress(bytes) → address: Validates the f410 format (length 22, prefix 0x040a) and extracts the last 20 bytes as a standard Ethereum address.Mocks:
src/mocks/FVMLookupDelegatedAddress.solwhich mirrors FVM behavior, includingMockFVMTest.solto etch the precompile and exposeLOOKUP_ADDRfor low-level testing.Tests: Added
LookupDelegatedAddress.t.solwith coverage, including:Closes #2