diff --git a/.gitignore b/.gitignore index 47a489f..eae8315 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ out # VIM *.swp +foundry.lock \ No newline at end of file diff --git a/README.md b/README.md index ab92c3b..d2ca647 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Additional FVM support can be found in the [filecoin-solidity library](https://g | Supported | Name | Address | | :-------: | :--- | :------ | -| ❌ | ResolveAddress | `0xfe00000000000000000000000000000000000001` | +| ✅ | ResolveAddress | `0xfe00000000000000000000000000000000000001` | | ❌ | LookupDelegatedAddress | `0xfe00000000000000000000000000000000000002` | | ✅ | CallActorByAddress | `0xfe00000000000000000000000000000000000003` | | ✅ | CallActorById | `0xfe00000000000000000000000000000000000005` | diff --git a/src/FVMAddress.sol b/src/FVMAddress.sol new file mode 100644 index 0000000..5ad8172 --- /dev/null +++ b/src/FVMAddress.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {RESOLVE_ADDRESS} from "./FVMPrecompiles.sol"; + +library FVMAddress { + function resolveAddress(bytes memory filAddress) internal view returns (bool success, uint64 actorId) { + bytes memory result; + (success, result) = address(RESOLVE_ADDRESS).staticcall(filAddress); + + if (success && result.length == 32) { + actorId = abi.decode(result, (uint64)); + } + } + + function toActorId(bytes memory filAddress) internal view returns (uint64 actorId) { + (bool success, uint64 id) = resolveAddress(filAddress); + require(success, "FVMAddress: Invalid address format"); + require(id != 0, "FVMAddress: Actor not found"); + return id; + } +} diff --git a/src/FVMPay.sol b/src/FVMPay.sol index bd2f9d4..191a9ed 100644 --- a/src/FVMPay.sol +++ b/src/FVMPay.sol @@ -24,11 +24,10 @@ library FVMPay { mstore(add(160, fmp), 192) // address offset mstore(add(214, fmp), or(0x040a0000000000000000000000000000000000000000, to)) // address mstore(add(192, fmp), 22) // address size - success := - and( - and(gt(returndatasize(), 31), eq(mload(fmp), EXIT_SUCCESS)), - delegatecall(gas(), CALL_ACTOR_BY_ADDRESS, fmp, 256, fmp, 256) - ) + success := and( + and(gt(returndatasize(), 31), eq(mload(fmp), EXIT_SUCCESS)), + delegatecall(gas(), CALL_ACTOR_BY_ADDRESS, fmp, 256, fmp, 256) + ) } } @@ -45,11 +44,10 @@ library FVMPay { mstore(add(96, fmp), EMPTY_CODEC) // codec mstore(add(128, fmp), 0) // params mstore(add(160, fmp), actorId) // actor ID - success := - and( - and(gt(returndatasize(), 31), eq(mload(fmp), EXIT_SUCCESS)), - delegatecall(gas(), CALL_ACTOR_BY_ID, fmp, 192, fmp, 192) - ) + success := and( + and(gt(returndatasize(), 31), eq(mload(fmp), EXIT_SUCCESS)), + delegatecall(gas(), CALL_ACTOR_BY_ID, fmp, 192, fmp, 192) + ) } } @@ -65,11 +63,10 @@ library FVMPay { mstore(add(96, fmp), EMPTY_CODEC) // codec mstore(add(128, fmp), 0) // params mstore(add(160, fmp), BURN_ACTOR_ID) // actor ID - success := - and( - and(gt(returndatasize(), 31), eq(mload(fmp), EXIT_SUCCESS)), - delegatecall(gas(), CALL_ACTOR_BY_ID, fmp, 192, fmp, 192) - ) + success := and( + and(gt(returndatasize(), 31), eq(mload(fmp), EXIT_SUCCESS)), + delegatecall(gas(), CALL_ACTOR_BY_ID, fmp, 192, fmp, 192) + ) } } } diff --git a/src/mocks/FVMResolveAddress.sol b/src/mocks/FVMResolveAddress.sol new file mode 100644 index 0000000..f067bc1 --- /dev/null +++ b/src/mocks/FVMResolveAddress.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +contract FVMResolveAddress { + mapping(bytes32 => uint64) public addressMocks; + mapping(bytes32 => bool) public addressExists; + + function mockResolveAddress(bytes memory filAddress, uint64 actorId) external { + bytes32 hash = keccak256(filAddress); + addressMocks[hash] = actorId; + addressExists[hash] = true; + } + + fallback() external { + bytes32 addressHash = keccak256(msg.data); + + if (addressExists[addressHash]) { + uint64 actorId = addressMocks[addressHash]; + assembly ("memory-safe") { + mstore(0, actorId) + return(0, 32) + } + } + + assembly ("memory-safe") { + return(0, 0) + } + } +} diff --git a/src/mocks/MockFVMTest.sol b/src/mocks/MockFVMTest.sol index b4331d6..5c4bb7e 100644 --- a/src/mocks/MockFVMTest.sol +++ b/src/mocks/MockFVMTest.sol @@ -3,18 +3,20 @@ pragma solidity ^0.8.30; import {Test} from "forge-std/Test.sol"; -import {CALL_ACTOR_BY_ADDRESS, CALL_ACTOR_BY_ID, GET_BEACON_RANDOMNESS} from "../FVMPrecompiles.sol"; +import {CALL_ACTOR_BY_ADDRESS, CALL_ACTOR_BY_ID, GET_BEACON_RANDOMNESS, RESOLVE_ADDRESS} from "../FVMPrecompiles.sol"; import {FVMCallActorByAddress} from "./FVMCallActorByAddress.sol"; import {FVMCallActorById} from "./FVMCallActorById.sol"; import {FVMGetBeaconRandomness} from "./FVMGetBeaconRandomness.sol"; +import {FVMResolveAddress} from "./FVMResolveAddress.sol"; -/// @notice Mocks the FVM precompiles for forge test contract MockFVMTest is Test { FVMGetBeaconRandomness public constant RANDOMNESS_PRECOMPILE = FVMGetBeaconRandomness(GET_BEACON_RANDOMNESS); + FVMResolveAddress public constant RESOLVE_ADDRESS_PRECOMPILE = FVMResolveAddress(RESOLVE_ADDRESS); function setUp() public virtual { vm.etch(CALL_ACTOR_BY_ADDRESS, address(new FVMCallActorByAddress()).code); vm.etch(CALL_ACTOR_BY_ID, address(new FVMCallActorById()).code); vm.etch(GET_BEACON_RANDOMNESS, address(new FVMGetBeaconRandomness()).code); + vm.etch(RESOLVE_ADDRESS, address(new FVMResolveAddress()).code); } } diff --git a/test/Address.t.sol b/test/Address.t.sol new file mode 100644 index 0000000..58e0aa5 --- /dev/null +++ b/test/Address.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {MockFVMTest} from "../src/mocks/MockFVMTest.sol"; +import {FVMAddress} from "../src/FVMAddress.sol"; + +contract AddressTest is MockFVMTest { + using FVMAddress for bytes; + + function testResolveAddressNotFound() public view { + bytes memory fakeAddress = hex"0102030405060708090a0b0c0d0e0f10"; + (bool success, uint64 actorId) = FVMAddress.resolveAddress(fakeAddress); + assertTrue(success); + assertEq(actorId, 0); + } + + function testResolveAddressMocked() public { + bytes memory testAddress = hex"01234567"; + uint64 expectedActorId = 12345; + + RESOLVE_ADDRESS_PRECOMPILE.mockResolveAddress(testAddress, expectedActorId); + + (bool success, uint64 actorId) = FVMAddress.resolveAddress(testAddress); + assertTrue(success); + assertEq(actorId, expectedActorId); + } + + function testToActorId() public { + bytes memory testAddress = hex"0123456789abcdef"; + uint64 expectedActorId = 99999; + + RESOLVE_ADDRESS_PRECOMPILE.mockResolveAddress(testAddress, expectedActorId); + + uint64 actorId = testAddress.toActorId(); + assertEq(actorId, expectedActorId); + } + + function testMultipleAddressResolutions() public { + bytes memory addr1 = hex"aabbccdd"; + bytes memory addr2 = hex"11223344"; + uint64 actorId1 = 1111; + uint64 actorId2 = 2222; + + RESOLVE_ADDRESS_PRECOMPILE.mockResolveAddress(addr1, actorId1); + RESOLVE_ADDRESS_PRECOMPILE.mockResolveAddress(addr2, actorId2); + + assertEq(addr1.toActorId(), actorId1); + assertEq(addr2.toActorId(), actorId2); + } + + function testDifferentAddressFormats() public { + bytes memory shortAddr = hex"01"; + bytes memory longAddr = hex"0123456789abcdef0123456789abcdef"; + uint64 shortActorId = 100; + uint64 longActorId = 200; + + RESOLVE_ADDRESS_PRECOMPILE.mockResolveAddress(shortAddr, shortActorId); + RESOLVE_ADDRESS_PRECOMPILE.mockResolveAddress(longAddr, longActorId); + + (bool success1, uint64 actorId1) = FVMAddress.resolveAddress(shortAddr); + (bool success2, uint64 actorId2) = FVMAddress.resolveAddress(longAddr); + + assertTrue(success1); + assertTrue(success2); + assertEq(actorId1, shortActorId); + assertEq(actorId2, longActorId); + } +}