From 14637a88d11e11d94bf434e0325752745677d6a1 Mon Sep 17 00:00:00 2001 From: spengrah Date: Wed, 3 Jul 2024 23:58:39 -0500 Subject: [PATCH] compiling --- .gitmodules | 10 +- foundry.toml | 6 +- lib/farcaster-delegator | 1 + lib/forge-std | 2 +- lib/hats-module | 1 + lib/hats-protocol | 1 + script/{Counter.s.sol => Deploy.s.sol} | 41 +-- src/Counter.sol | 43 --- src/HatsFarcasterBundler.sol | 342 ++++++++++++++++++ ...unter.t.sol => HatsFarcasterBundler.t.sol} | 16 +- 10 files changed, 376 insertions(+), 87 deletions(-) create mode 160000 lib/farcaster-delegator create mode 160000 lib/hats-module create mode 160000 lib/hats-protocol rename script/{Counter.s.sol => Deploy.s.sol} (69%) delete mode 100644 src/Counter.sol create mode 100644 src/HatsFarcasterBundler.sol rename test/{Counter.t.sol => HatsFarcasterBundler.t.sol} (64%) diff --git a/.gitmodules b/.gitmodules index acff682..f51ff1e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,12 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std - +[submodule "lib/hats-protocol"] + path = lib/hats-protocol + url = https://github.com/hats-protocol/hats-protocol +[submodule "lib/farcaster-delegator"] + path = lib/farcaster-delegator + url = https://github.com/hats-protocol/farcaster-delegator +[submodule "lib/hats-module"] + path = lib/hats-module + url = https://github.com/hats-protocol/hats-module diff --git a/foundry.toml b/foundry.toml index d4726d6..03930a6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,10 +7,14 @@ optimizer_runs = 1_000_000 bytecode_hash = "none" gas_reports = [] auto_detect_solc = false -solc = "0.8.19" +solc = "0.8.26" remappings = [ "ds-test/=lib/forge-std/lib/ds-test/src/", "forge-std/=lib/forge-std/src/", + "hats-module/=lib/hats-module/src/", + "solady/=lib/hats-protocol/lib/solady/src", + "hats-protocol/=lib/hats-protocol/src", + "farcaster/=lib/farcaster-delegator/lib/farcaster/src", ] # Enable tests to read ir-optimized bytecode precompiled by profile.optimized fs_permissions = [{ access = "read", path = "./optimized-out" }] diff --git a/lib/farcaster-delegator b/lib/farcaster-delegator new file mode 160000 index 0000000..7fb94d2 --- /dev/null +++ b/lib/farcaster-delegator @@ -0,0 +1 @@ +Subproject commit 7fb94d224a1e3945c4266cf3dbc3b8b27189fb89 diff --git a/lib/forge-std b/lib/forge-std index 1cefc0e..07263d1 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1cefc0e4e3d2a1f604c654004c90bd6701b2b5e2 +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d diff --git a/lib/hats-module b/lib/hats-module new file mode 160000 index 0000000..e83bd72 --- /dev/null +++ b/lib/hats-module @@ -0,0 +1 @@ +Subproject commit e83bd72cb3eebdbeadabcb63e3c6f69ab61a5562 diff --git a/lib/hats-protocol b/lib/hats-protocol new file mode 160000 index 0000000..cccb71a --- /dev/null +++ b/lib/hats-protocol @@ -0,0 +1 @@ +Subproject commit cccb71a061cdb9095af7d11dfdb47026993d8c4e diff --git a/script/Counter.s.sol b/script/Deploy.s.sol similarity index 69% rename from script/Counter.s.sol rename to script/Deploy.s.sol index 1bce003..0ac2c6d 100644 --- a/script/Counter.s.sol +++ b/script/Deploy.s.sol @@ -1,22 +1,19 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; +pragma solidity ^0.8.19; import { Script, console2 } from "forge-std/Script.sol"; -import { Counter } from "../src/Counter.sol"; - -interface ImmutableCreate2Factory { - function create2(bytes32 salt, bytes calldata initializationCode) - external - payable - returns (address deploymentAddress); -} +import { HatsFarcasterBundler, IHats } from "../src/HatsFarcasterBundler.sol"; contract Deploy is Script { - Counter public counter; - bytes32 public SALT = bytes32(abi.encode("lets add some salt to these eggs")); + HatsFarcasterBundler public bundler; + bytes32 public SALT = bytes32(abi.encode(0x4a57)); // default values bool internal _verbose = true; + string internal _version = "0.1.0"; + IHats internal _hats = IHats(0x3bc1A0Ad72417f2d411118085256fC53CBdDd137); + HatsFarcasterBundler.Hat[] internal _hatTreeTemplate; + HatsFarcasterBundler.FarcasterContracts internal _farcasterContracts; /// @dev Override default values, if desired function prepare(bool verbose) public { @@ -31,7 +28,7 @@ contract Deploy is Script { function _log(string memory prefix) internal view { if (_verbose) { - console2.log(string.concat(prefix, "Counter:"), address(counter)); + console2.log(string.concat(prefix, "HatsFarcasterBundler:"), address(bundler)); } } @@ -47,7 +44,7 @@ contract Deploy is Script { * never differs regardless of where its being compiled * 2. The provided salt, `SALT` */ - counter = new Counter{ salt: SALT}(/* insert constructor args here */); + bundler = new HatsFarcasterBundler{ salt: SALT }(_version, _hats, _hatTreeTemplate, _farcasterContracts); vm.stopBroadcast(); @@ -55,24 +52,6 @@ contract Deploy is Script { } } -/// @dev Deploy pre-compiled ir-optimized bytecode to a non-deterministic address -contract DeployPrecompiled is Deploy { - /// @dev Update SALT and default values in Deploy contract - - function run() public override { - vm.startBroadcast(deployer()); - - bytes memory args = abi.encode( /* insert constructor args here */ ); - - /// @dev Load and deploy pre-compiled ir-optimized bytecode. - counter = Counter(deployCode("optimized-out/Counter.sol/Counter.json", args)); - - vm.stopBroadcast(); - - _log("Precompiled "); - } -} - /* FORGE CLI COMMANDS ## A. Simulate the deployment locally diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index a18659a..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -// import { console2 } from "forge-std/Test.sol"; // remove before deploy - -contract Counter { - /*////////////////////////////////////////////////////////////// - CUSTOM ERRORS - //////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////// - DATA MODELS - //////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////// - CONSANTS - //////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////// - MUTABLE STATE - //////////////////////////////////////////////////////////////*/ - uint256 public number; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////// - PUBLIC FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/HatsFarcasterBundler.sol b/src/HatsFarcasterBundler.sol new file mode 100644 index 0000000..0eed0e7 --- /dev/null +++ b/src/HatsFarcasterBundler.sol @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +// import { console2 } from "forge-std/Test.sol"; // comment out before deploy +import { IHats as HatsLike } from "../lib/hats-protocol/src/Interfaces/IHats.sol"; +import { HatsModuleFactory } from "../lib/hats-module/src/HatsModuleFactory.sol"; + +interface FarcasterDelegatorLike { + function prepareToReceive(uint256 _fid) external; +} + +interface IHats is HatsLike { + function lastTopHatId() external view returns (uint32); +} + +contract HatsFarcasterBundler { + /*////////////////////////////////////////////////////////////// + CUSTOM ERRORS + //////////////////////////////////////////////////////////////*/ + + error IncorrectHatIdPrediction(); + error InvalidArrayLength(); + + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /*////////////////////////////////////////////////////////////// + DATA MODELS + //////////////////////////////////////////////////////////////*/ + + /// @notice modified from Hats.sol for original definition + struct Hat { + address eligibility; + uint32 maxSupply; + uint32 supply; + uint16 lastHatId; + address toggle; + bool mutable_; + string details; + string imageURI; + } + + struct FarcasterContracts { + address _IdGateway; + address _idRegistry; + address _keyGateway; + address _keyRegistry; + address _signedKeyRequestValidator; + } + + struct HatWearers { + address[] wearers; + } + + /** + * @notice The arguments to pass to a batch mint call to mint a single hat to multiple wearers + * @dev The arrays must be the same length + * @param wearers The addresses to mint the hat to + */ + struct HatMintData { + uint256[] hatIds; + address[] wearers; + } + + /*////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////*/ + + /// @notice The Hats Protocol contract address + IHats public immutable HATS; + + /// @notice The mask for the owner hat id corresponding to the {hatTreeTemplate} structure + uint256 internal constant OWNER_HAT_MASK = 0x0000000000010001000000000000000000000000000000000000000000000000; + + /// @notice The mask for the caster hat id corresponding to the {hatTreeTemplate} structure + uint256 internal constant CASTER_HAT_MASK = 0x0000000000010001000100000000000000000000000000000000000000000000; + + /*////////////////////////////////////////////////////////////// + MUTABLE STATE + //////////////////////////////////////////////////////////////*/ + + /// @notice The semver version of this contract + string public version; + + /** + * @notice A simple hats tree template that takes the following format: + * [0] = x -> topHat + * [1] = x.1 -> autonomousAdminHat + * [2] = x.1.1 -> ownerHat + * [3] = x.1.1.1 -> casterHat + */ + Hat[] public hatTreeTemplate; + + /// @notice The Farcaster protocol contracts to use when deploying a HatsFarcasterDelegator instance + FarcasterContracts public farcasterContracts; + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor( + string memory _version, + IHats _hats, + Hat[] memory _hatTreeTemplate, + FarcasterContracts memory _farcasterContracts + ) { + version = _version; + HATS = _hats; + farcasterContracts = _farcasterContracts; + + hatTreeTemplate[0] = _hatTreeTemplate[0]; + hatTreeTemplate[1] = _hatTreeTemplate[1]; + hatTreeTemplate[2] = _hatTreeTemplate[2]; + hatTreeTemplate[3] = _hatTreeTemplate[3]; + } + + /*////////////////////////////////////////////////////////////// + PUBLIC FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Deploy a hats tree and the HatsFarcasterDelegator instance, and prepare the delegator instance to receive a + * fid + * @param _topHatWearer The address to transfer the top hat to + * @param _childHatWearers The addresses to mint the child hats to + * @param _factory The HatsModuleFactory to use to deploy the HatsFarcasterDelegator instance + * @param _hatsFarcasterDelegatorImplementation The implementation to use to deploy the HatsFarcasterDelegator + * instance + * @param _saltNonce The salt nonce to use for the HatsFarcasterDelegator instance + * @param _fid The fid to prepare the delegator instance to receive + */ + function deployTreeAndPrepareHatsFarcasterDelegator( + address _topHatWearer, + HatWearers[] memory _childHatWearers, + HatsModuleFactory _factory, + address _hatsFarcasterDelegatorImplementation, + uint256 _saltNonce, + uint256 _fid + ) external returns (uint256[] memory hatIds, address hatsFarcasterDelegatorInstance) { + // --- 1. deploy the HatsFarcasterDelegator instance ----------- + + // predict the owner and caster hat ids + (uint256 ownerHatId, uint256 casterHatId) = _predictOwnerAndCasterHatIds(); + + // create the instance + hatsFarcasterDelegatorInstance = _createHatsFarcasterDelegator( + _factory, _hatsFarcasterDelegatorImplementation, ownerHatId, casterHatId, _saltNonce + ); + + // --- 2. create the hats tree --------------------------------- + + // create the tree, with this contract temporarily wearing the top hat + hatIds = _createHatsTree(hatsFarcasterDelegatorInstance); + + // assert that the predicted caster hat id matches actual + if (hatIds[3] != casterHatId) revert IncorrectHatIdPrediction(); + + // --- 3. prepare the delegator instance to receive the fid ---- + + // authorize this contract to call prepareToReceive by minting it the ownerHat + HATS.mintHat(hatIds[2], address(this)); + + // call prepareToReceive on the delegator instance + FarcasterDelegatorLike(hatsFarcasterDelegatorInstance).prepareToReceive(_fid); + + // renounce the ownerHat + HATS.renounceHat(hatIds[2]); + + // --- 4. mint the hats ---------------------------------------- + + // assert that the hat wearers array length is 4 + if (_childHatWearers.length != 4) revert InvalidArrayLength(); + + // construct the batch mint hat data to mint the child hats + HatMintData[] memory hatMintDatas = new HatMintData[](3); + hatMintDatas[0] = _constructHatMintData(hatIds[1], _childHatWearers[1]); + hatMintDatas[1] = _constructHatMintData(hatIds[2], _childHatWearers[2]); + hatMintDatas[2] = _constructHatMintData(hatIds[3], _childHatWearers[3]); + + // do the mint + _mintHats(hatMintDatas); + + // --- 5. transfer the top hat to the desired top hat wearer --- + HATS.transferHat(hatIds[0], address(this), _topHatWearer); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Creates a new hats tree from the {hatTreeTemplate}, setting the HatsFarcasterDelegator instance as the + * casterHat's eligibility module + * @param _hatsFarcasterDelegatorInstance The address of the HatsFarcasterDelegator instance to set as the casterHat's + * eligibility module + * @return The hat ids of the created hats + */ + function _createHatsTree(address _hatsFarcasterDelegatorInstance) internal returns (uint256[] memory) { + // load the tree template into memory + Hat[] memory template = hatTreeTemplate; + + // create an array to store the hat ids + uint256[] memory hatIds = new uint256[](4); + + // create and mint the top hat to this contract + hatIds[0] = HATS.mintTopHat(address(this), template[0].details, template[0].imageURI); + + // create the autonomous admin hat + hatIds[1] = HATS.createHat( + hatIds[0], + template[1].details, + template[1].maxSupply, + template[1].eligibility, + template[1].toggle, + template[1].mutable_, + template[1].imageURI + ); + + // create the owner hat + hatIds[2] = HATS.createHat( + hatIds[1], + template[2].details, + template[2].maxSupply, + template[2].eligibility, + template[2].toggle, + template[2].mutable_, + template[2].imageURI + ); + + // create the caster hat, with the hats farcaster delegator instance as the eligibility module + hatIds[3] = HATS.createHat( + hatIds[2], + template[3].details, + template[3].maxSupply, + _hatsFarcasterDelegatorInstance, + template[3].toggle, + template[3].mutable_, + template[3].imageURI + ); + + return hatIds; + } + + /** + * @dev Mints the hats to the wearers in the {_hatMintDatas} array + * @param _hatMintDatas The hat mint data to pass to the batch mint function + */ + function _mintHats(HatMintData[] memory _hatMintDatas) internal { + for (uint256 i; i < _hatMintDatas.length; ++i) { + HATS.batchMintHats(_hatMintDatas[i].hatIds, _hatMintDatas[i].wearers); + } + } + + /** + * @dev Constructs the hat mint data to pass to the batch mint function + * @param _hatId The hat id to mint to each of the wearers + * @param _hatWearers The hat wearers to mint the hat to + * @return hatMintData The hat mint data + */ + function _constructHatMintData(uint256 _hatId, HatWearers memory _hatWearers) + internal + pure + returns (HatMintData memory hatMintData) + { + for (uint256 i; i < _hatWearers.wearers.length; ++i) { + hatMintData.hatIds[i] = _hatId; + } + + hatMintData.wearers = _hatWearers.wearers; + } + + /** + * @dev Creates a new HatsFarcasterDelegator instance + * @param _factory The HatsModuleFactory to use to deploy the HatsFarcasterDelegator instance + * @param _hatsFarcasterDelegatorImplementation The implementation to use to deploy the HatsFarcasterDelegator + * instance + * @param _ownerHat The hat id of the owner hat + * @param _casterHat The hat id of the caster hat + * @param _saltNonce The salt nonce to use for the HatsFarcasterDelegator instance + * @return The address of the created HatsFarcasterDelegator instance + */ + function _createHatsFarcasterDelegator( + HatsModuleFactory _factory, + address _hatsFarcasterDelegatorImplementation, + uint256 _ownerHat, + uint256 _casterHat, + uint256 _saltNonce + ) internal returns (address) { + return _factory.createHatsModule( + _hatsFarcasterDelegatorImplementation, + _casterHat, + _getHatsFarcasterDelegatorOtherImmutableArgs(_ownerHat), + abi.encode(), // no init data for this module + _saltNonce + ); + } + + // function _predictHatsFarcasterDelegatorAddress( + // HatsModuleFactory _factory, + // address _hatsFarcasterDelegatorImplementation, + // uint256 _casterHat, + // uint256 _ownerHat, + // uint256 _saltNonce + // ) internal view returns (address) { + // return _factory.getHatsModuleAddress( + // _hatsFarcasterDelegatorImplementation, + // _casterHat, + // _getHatsFarcasterDelegatorOtherImmutableArgs(_ownerHat), + // _saltNonce + // ); + // } + + /** + * @dev Constructs the other immutable args for the HatsFarcasterDelegator instance deployment + * @param _ownerHat The hat id of the owner hat + * @return The other immutable args + */ + function _getHatsFarcasterDelegatorOtherImmutableArgs(uint256 _ownerHat) internal view returns (bytes memory) { + return abi.encodePacked( + _ownerHat, + farcasterContracts._IdGateway, + farcasterContracts._idRegistry, + farcasterContracts._keyGateway, + farcasterContracts._keyRegistry, + farcasterContracts._signedKeyRequestValidator + ); + } + + /** + * @dev Predicts the hat ids of the owner and caster hats based on the next available top hat id and the + * {hatTreeTemplate} structure + * @return ownerHatId The hat id of the owner hat + * @return casterHatId The hat id of the caster hat + */ + function _predictOwnerAndCasterHatIds() internal view returns (uint256 ownerHatId, uint256 casterHatId) { + uint256 topHatId = (HATS.lastTopHatId() + 1) << 224; + + ownerHatId = topHatId | OWNER_HAT_MASK; + casterHatId = topHatId | CASTER_HAT_MASK; + } +} diff --git a/test/Counter.t.sol b/test/HatsFarcasterBundler.t.sol similarity index 64% rename from test/Counter.t.sol rename to test/HatsFarcasterBundler.t.sol index e190441..4e7f688 100644 --- a/test/Counter.t.sol +++ b/test/HatsFarcasterBundler.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; +pragma solidity ^0.8.21; import { Test, console2 } from "forge-std/Test.sol"; -import { Counter } from "../src/Counter.sol"; -import { Deploy, DeployPrecompiled } from "../script/Counter.s.sol"; +import { HatsFarcasterBundler } from "../src/HatsFarcasterBundler.sol"; +import { Deploy } from "../script/Deploy.s.sol"; -contract CounterTest is Deploy, Test { +contract HatsFarcasterBundlerTest is Deploy, Test { /// @dev Inherit from DeployPrecompiled instead of Deploy if working with pre-compiled contracts /// @dev variables inhereted from Deploy script - // Counter public counter; + // HatsFarcasterBundler public bundler; // bytes32 public SALT; uint256 public fork; @@ -25,8 +25,4 @@ contract CounterTest is Deploy, Test { } } -contract UnitTests is CounterTest { - function test_empty() public { - counter.setNumber(2); - } -} +contract UnitTests is HatsFarcasterBundlerTest { }