diff --git a/src/utils/FunctionCodec.sol b/src/utils/FunctionCodec.sol new file mode 100644 index 0000000..47576a8 --- /dev/null +++ b/src/utils/FunctionCodec.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library FunctionCodec { + function encodeParams(address contractAddr, bytes4 selector) internal pure returns (bytes24) { + return bytes24(bytes20(contractAddr)) | bytes24(selector) >> 160; + } + + function decodeParams(bytes24 encoded) internal pure returns (address contractAddr, bytes4 selector) { + contractAddr = address(bytes20(encoded)); + selector = bytes4(encoded << 160); + } + + function encodeFunction(function () external returns(string memory) f) internal pure returns (bytes24) { + return encodeParams(f.address, f.selector); + } + + function decodeFunction( + address contractAddr, + bytes4 selector + ) + internal + pure + returns (function () external returns(string memory) f) + { + uint32 s = uint32(selector); + assembly { + f.address := contractAddr + f.selector := s + } + } + + function decodeFunction(bytes24 encoded) internal pure returns (function () external returns(string memory) f) { + (address contractAddr, bytes4 selector) = decodeParams(encoded); + return decodeFunction(contractAddr, selector); + } +} diff --git a/test/utils/FunctionCodec.t.sol b/test/utils/FunctionCodec.t.sol new file mode 100644 index 0000000..5b607bd --- /dev/null +++ b/test/utils/FunctionCodec.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { PRBTest } from "@prb/test/PRBTest.sol"; +import { console2 } from "forge-std/console2.sol"; +import { StdCheats } from "forge-std/StdCheats.sol"; + +import "src/utils/FunctionCodec.sol"; + +contract FunctionCodecTest is PRBTest, StdCheats { + function mockCallback() external pure returns(string memory) { + return "Hello, world!"; + } + + function testEncodeParams() public { + assertEq( + FunctionCodec.encodeParams(address(this), this.mockCallback.selector), + bytes24(abi.encodePacked(address(this), this.mockCallback.selector)) + ); + } + + function testDecodeParams() public { + bytes24 encoded = FunctionCodec.encodeParams(address(this), this.mockCallback.selector); + (address contractAddr, bytes4 selector) = FunctionCodec.decodeParams(encoded); + assertEq(contractAddr, address(this)); + assertEq(selector, this.mockCallback.selector); + } + + function testEncodeFunction() public { + bytes24 encoded = FunctionCodec.encodeFunction(this.mockCallback); + assertEq(encoded, bytes24(abi.encodePacked(address(this), this.mockCallback.selector))); + } + + function testDecodeFunction() public { + function () external returns(string memory) f = FunctionCodec.decodeFunction(address(this), this.mockCallback.selector); + assertEq(f(), this.mockCallback()); + } + + function testDecodeFunction2() public { + bytes24 encoded = FunctionCodec.encodeFunction(this.mockCallback); + function () external returns(string memory) f = FunctionCodec.decodeFunction(encoded); + assertEq(f(), this.mockCallback()); + } +}