From a56a3a9c942ea49fd653482f57ab82af97498fb5 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:32:49 -0400 Subject: [PATCH 001/119] add getHatEligibilityModule to IHats interface --- contracts/interfaces/hats/IHats.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/interfaces/hats/IHats.sol b/contracts/interfaces/hats/IHats.sol index c460c46d..6d486704 100644 --- a/contracts/interfaces/hats/IHats.sol +++ b/contracts/interfaces/hats/IHats.sol @@ -39,4 +39,5 @@ interface IHats { ) external returns (bool success); function transferHat(uint256 _hatId, address _from, address _to) external; + function getHatEligibilityModule(uint256 _hatId) external view returns (address eligibility); } From 21502f9c5d7c568f3a00d8cc65760cb67d5f7ff9 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:33:18 -0400 Subject: [PATCH 002/119] Add HatsElectionEligibility interface --- .../hats/IHatsElectionEligibility.sol | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 contracts/interfaces/hats/IHatsElectionEligibility.sol diff --git a/contracts/interfaces/hats/IHatsElectionEligibility.sol b/contracts/interfaces/hats/IHatsElectionEligibility.sol new file mode 100644 index 00000000..3b0c7c67 --- /dev/null +++ b/contracts/interfaces/hats/IHatsElectionEligibility.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13; + +interface IHatsElectionEligibility { + event ElectionOpened(uint128 nextTermEnd); + event ElectionCompleted(uint128 termEnd, address[] winners); + event NewTermStarted(uint128 termEnd); + event Recalled(uint128 termEnd, address[] accounts); + + /// @notice Returns the first second after the current term ends. + /// @dev Also serves as the id for the current term. + function currentTermEnd() external view returns (uint128); + + /// @notice Returns the first second after the next term ends. + /// @dev Also serves as the id for the next term. + function nextTermEnd() external view returns (uint128); + + /// @notice Returns the election status (open or closed) for a given term end. + /// @param termEnd The term end timestamp to query. + function electionStatus( + uint128 termEnd + ) external view returns (bool isElectionOpen); + + /// @notice Returns whether a candidate was elected in a given term. + /// @param termEnd The term end timestamp to query. + /// @param candidate The address of the candidate. + function electionResults( + uint128 termEnd, + address candidate + ) external view returns (bool elected); + + /// @notice Returns the BALLOT_BOX_HAT constant. + function BALLOT_BOX_HAT() external pure returns (uint256); + + /// @notice Returns the ADMIN_HAT constant. + function ADMIN_HAT() external pure returns (uint256); + + /** + * @notice Submit the results of an election for a specified term. + * @dev Only callable by the wearer(s) of the BALLOT_BOX_HAT. + * @param _termEnd The id of the term for which the election results are being submitted. + * @param _winners The addresses of the winners of the election. + */ + function elect(uint128 _termEnd, address[] calldata _winners) external; + + /** + * @notice Submit the results of a recall election for a specified term. + * @dev Only callable by the wearer(s) of the BALLOT_BOX_HAT. + * @param _termEnd The id of the term for which the recall results are being submitted. + * @param _recallees The addresses to be recalled. + */ + function recall(uint128 _termEnd, address[] calldata _recallees) external; + + /** + * @notice Set the next term and open the election for it. + * @dev Only callable by the wearer(s) of the ADMIN_HAT. + * @param _newTermEnd The id of the term that will be opened. + */ + function setNextTerm(uint128 _newTermEnd) external; + + /** + * @notice Start the next term, updating the current term. + * @dev Can be called by anyone, but will revert if conditions are not met. + */ + function startNextTerm() external; + + /** + * @notice Determine the eligibility and standing of a wearer for a hat. + * @param _wearer The address of the hat wearer. + * @param _hatId The ID of the hat. + * @return eligible True if the wearer is eligible for the hat. + * @return standing True if the wearer is in good standing. + */ + function getWearerStatus( + address _wearer, + uint256 _hatId + ) external view returns (bool eligible, bool standing); +} From e69292e2889ce09817d832c573a5e27cbded82a9 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:34:37 -0400 Subject: [PATCH 003/119] wip DecentAutonomousAdminHat contract --- contracts/DecentAutonomousAdminHat.sol | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 contracts/DecentAutonomousAdminHat.sol diff --git a/contracts/DecentAutonomousAdminHat.sol b/contracts/DecentAutonomousAdminHat.sol new file mode 100644 index 00000000..8e375630 --- /dev/null +++ b/contracts/DecentAutonomousAdminHat.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.19; +import "./interfaces/hats/IHats.sol"; +import "./interfaces/hats/IHatsElectionEligibility.sol"; + +contract DecentAutonomousAdminHat { + string public constant NAME = "DecentAutonomousAdminHat"; + + uint256 public hatId; + + struct TriggerStartArgs { + address userHatSmartAccountAddress; + IHats userHatPropocol; + uint256 userHatId; + } + + // ////////////////////////////////////////////////////////////// + // Constructor + // ////////////////////////////////////////////////////////////// + constructor() {} + + // ////////////////////////////////////////////////////////////// + // Initializer + // ////////////////////////////////////////////////////////////// + + /** + * + * @param _initData encoded initialization parameters: `uint256 hatId` + */ + + function _setup(bytes calldata _initData) internal { + (hatId) = abi.decode(_initData, (uint256)); + } + + // ////////////////////////////////////////////////////////////// + // Public Functions + // ////////////////////////////////////////////////////////////// + function triggerStartNextTerm(TriggerStartArgs calldata args) public { + // ? should we use `checkHatWearerStatus` here? + // ? should we use `isAdminOfHat` here? + // ? should we use `isEligible` here? + + address hatsEE = args.userHatPropocol.getHatEligibilityModule( + args.userHatId + ); + + IHatsElectionEligibility(hatsEE).startNextTerm(); + } + + // ////////////////////////////////////////////////////////////// + // Internal Functions + // ////////////////////////////////////////////////////////////// +} From 9e66612791035f746e344fce1e3ea551f90e1f5e Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:36:08 -0400 Subject: [PATCH 004/119] create AA Hat --- contracts/DecentAutonomousAdminHat.sol | 123 ++++++++++++++++++++----- 1 file changed, 102 insertions(+), 21 deletions(-) diff --git a/contracts/DecentAutonomousAdminHat.sol b/contracts/DecentAutonomousAdminHat.sol index 8e375630..407b1a4c 100644 --- a/contracts/DecentAutonomousAdminHat.sol +++ b/contracts/DecentAutonomousAdminHat.sol @@ -2,52 +2,133 @@ pragma solidity =0.8.19; import "./interfaces/hats/IHats.sol"; import "./interfaces/hats/IHatsElectionEligibility.sol"; +import "./interfaces/sablier/ISablierV2LockupLinear.sol"; contract DecentAutonomousAdminHat { string public constant NAME = "DecentAutonomousAdminHat"; - uint256 public hatId; + uint256 public adminHatId; + struct SablierStreamInfo { + uint256 streamId; + ISablierV2LockupLinear sablierV2LockupLinear; + } struct TriggerStartArgs { address userHatSmartAccountAddress; - IHats userHatPropocol; + address currentWearer; + IHats userHatProtocol; uint256 userHatId; + address nominatedWearer; + SablierStreamInfo[] sablierStreamInfo; } // ////////////////////////////////////////////////////////////// // Constructor // ////////////////////////////////////////////////////////////// - constructor() {} - - // ////////////////////////////////////////////////////////////// - // Initializer - // ////////////////////////////////////////////////////////////// - - /** - * - * @param _initData encoded initialization parameters: `uint256 hatId` - */ - - function _setup(bytes calldata _initData) internal { - (hatId) = abi.decode(_initData, (uint256)); + constructor(uint256 _adminHatId) { + adminHatId =_adminHatId; } // ////////////////////////////////////////////////////////////// // Public Functions // ////////////////////////////////////////////////////////////// function triggerStartNextTerm(TriggerStartArgs calldata args) public { - // ? should we use `checkHatWearerStatus` here? - // ? should we use `isAdminOfHat` here? - // ? should we use `isEligible` here? + require( + args.userHatProtocol.isWearerOfHat( + args.currentWearer, + args.userHatId + ), + "Not current wearer" + ); + + address hatsEligibilityModuleAddress = args + .userHatProtocol + .getHatEligibilityModule(args.userHatId); + + IHatsElectionEligibility hatsElectionModule = IHatsElectionEligibility( + hatsEligibilityModuleAddress + ); + + hatsElectionModule.startNextTerm(); + + // transfer user hat to self + args.userHatProtocol.transferHat( + args.userHatId, + args.currentWearer, + address(this) + ); - address hatsEE = args.userHatPropocol.getHatEligibilityModule( - args.userHatId + // for each withdrawable stream, withdraw funds to current wearer of hat + _flushUnclaimedFunds( + _getStreamsWithUnclaimedFunds(args.sablierStreamInfo), + args.currentWearer ); - IHatsElectionEligibility(hatsEE).startNextTerm(); + // transfer hat to nominated wearer + args.userHatProtocol.transferHat( + args.userHatId, + address(this), + args.nominatedWearer + ); } // ////////////////////////////////////////////////////////////// // Internal Functions // ////////////////////////////////////////////////////////////// + + /** + * @dev Withdraws unclaimed funds from Sablier streams. + * @param _sablierStreamInfo SablierStreamInfo array + */ + function _flushUnclaimedFunds( + SablierStreamInfo[] memory _sablierStreamInfo, + address withdrawTo + ) internal { + for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { + _sablierStreamInfo[i].sablierV2LockupLinear.withdrawMax( + _sablierStreamInfo[i].streamId, + withdrawTo + ); + } + } + + /** + * @dev Returns an array of Sablier stream ids that have unclaimed funds. + * @param _sablierStreamInfo SablierStreamInfo array + * @return streamsWithUnclaimedFunds An array of SablierStreamInfo that have unclaimed funds + */ + function _getStreamsWithUnclaimedFunds( + SablierStreamInfo[] memory _sablierStreamInfo + ) internal view returns (SablierStreamInfo[] memory) { + uint256 streamsWithUnclaimedFundsCount = 0; + + for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { + uint128 withdrawableAmount = _sablierStreamInfo[i] + .sablierV2LockupLinear + .withdrawableAmountOf(_sablierStreamInfo[i].streamId); + + if (withdrawableAmount > 0) { + streamsWithUnclaimedFundsCount++; + } + } + + SablierStreamInfo[] + memory streamsWithUnclaimedFunds = new SablierStreamInfo[]( + streamsWithUnclaimedFundsCount + ); + uint256 index = 0; + + for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { + uint128 withdrawableAmount = _sablierStreamInfo[i] + .sablierV2LockupLinear + .withdrawableAmountOf(_sablierStreamInfo[i].streamId); + + if (withdrawableAmount > 0) { + streamsWithUnclaimedFunds[index] = _sablierStreamInfo[i]; + index++; + } + } + + return streamsWithUnclaimedFunds; + } } From 44324411ed700fafbd98a597d635e95ba41383cc Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 03:45:00 -0400 Subject: [PATCH 005/119] remove unused param --- contracts/DecentAutonomousAdminHat.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/DecentAutonomousAdminHat.sol b/contracts/DecentAutonomousAdminHat.sol index 407b1a4c..05901467 100644 --- a/contracts/DecentAutonomousAdminHat.sol +++ b/contracts/DecentAutonomousAdminHat.sol @@ -14,7 +14,6 @@ contract DecentAutonomousAdminHat { ISablierV2LockupLinear sablierV2LockupLinear; } struct TriggerStartArgs { - address userHatSmartAccountAddress; address currentWearer; IHats userHatProtocol; uint256 userHatId; @@ -26,7 +25,7 @@ contract DecentAutonomousAdminHat { // Constructor // ////////////////////////////////////////////////////////////// constructor(uint256 _adminHatId) { - adminHatId =_adminHatId; + adminHatId = _adminHatId; } // ////////////////////////////////////////////////////////////// @@ -40,7 +39,6 @@ contract DecentAutonomousAdminHat { ), "Not current wearer" ); - address hatsEligibilityModuleAddress = args .userHatProtocol .getHatEligibilityModule(args.userHatId); From 5830d1f68f35b5e17cfac81dde6081a0fee5c908 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 03:46:11 -0400 Subject: [PATCH 006/119] update interfaces with needed methods --- contracts/interfaces/hats/IHats.sol | 10 +++++- .../sablier/ISablierV2LockupLinear.sol | 31 +++++++++++++++++++ contracts/mocks/MockHats.sol | 9 ++++++ contracts/mocks/MockSablierV2LockupLinear.sol | 13 ++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/hats/IHats.sol b/contracts/interfaces/hats/IHats.sol index 6d486704..3a3743ea 100644 --- a/contracts/interfaces/hats/IHats.sol +++ b/contracts/interfaces/hats/IHats.sol @@ -39,5 +39,13 @@ interface IHats { ) external returns (bool success); function transferHat(uint256 _hatId, address _from, address _to) external; - function getHatEligibilityModule(uint256 _hatId) external view returns (address eligibility); + + function getHatEligibilityModule( + uint256 _hatId + ) external view returns (address eligibility); + + function isWearerOfHat( + address _user, + uint256 _hatId + ) external view returns (bool isWearer); } diff --git a/contracts/interfaces/sablier/ISablierV2LockupLinear.sol b/contracts/interfaces/sablier/ISablierV2LockupLinear.sol index 0aa6cac9..4bf0c724 100644 --- a/contracts/interfaces/sablier/ISablierV2LockupLinear.sol +++ b/contracts/interfaces/sablier/ISablierV2LockupLinear.sol @@ -8,4 +8,35 @@ interface ISablierV2LockupLinear { function createWithTimestamps( LockupLinear.CreateWithTimestamps calldata params ) external returns (uint256 streamId); + + /// @notice Emitted when assets are withdrawn from a stream. + /// @param streamId The ID of the stream. + /// @param to The address that has received the withdrawn assets. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param amount The amount of assets withdrawn, denoted in units of the asset's decimals. + event WithdrawFromLockupStream(uint256 indexed streamId, address indexed to, IERC20 indexed asset, uint128 amount); + + /// @notice Withdraws the maximum withdrawable amount from the stream to the provided address `to`. + /// + /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. + /// + /// Notes: + /// - Refer to the notes in {withdraw}. + /// + /// Requirements: + /// - Refer to the requirements in {withdraw}. + /// + /// @param streamId The ID of the stream to withdraw from. + /// @param to The address receiving the withdrawn assets. + /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. + function withdrawMax( + uint256 streamId, + address to + ) external returns (uint128 withdrawnAmount); + + /// @notice Calculates the amount that the recipient can withdraw from the stream, denoted in units of the asset's + /// decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function withdrawableAmountOf(uint256 streamId) external view returns (uint128 withdrawableAmount); } diff --git a/contracts/mocks/MockHats.sol b/contracts/mocks/MockHats.sol index f5ac2ece..1cf74405 100644 --- a/contracts/mocks/MockHats.sol +++ b/contracts/mocks/MockHats.sol @@ -33,4 +33,13 @@ contract MockHats is IHats { } function transferHat(uint256, address, address) external {} + + function getHatEligibilityModule( + uint256 _hatId + ) external view returns (address eligibility) {} + + function isWearerOfHat( + address _user, + uint256 _hatId + ) external view returns (bool isWearer) {} } diff --git a/contracts/mocks/MockSablierV2LockupLinear.sol b/contracts/mocks/MockSablierV2LockupLinear.sol index 7d737bb4..8511d364 100644 --- a/contracts/mocks/MockSablierV2LockupLinear.sol +++ b/contracts/mocks/MockSablierV2LockupLinear.sol @@ -156,4 +156,17 @@ contract MockSablierV2LockupLinear is ISablierV2LockupLinear { stream.recipient = recipient; } + + function withdrawMax( + uint256 streamId, + address to + ) external override returns (uint128 withdrawnAmount) { + withdrawnAmount = withdrawableAmountOf(streamId); + require(withdrawnAmount > 0, "No withdrawable amount"); + + Stream storage stream = streams[streamId]; + stream.totalAmount -= withdrawnAmount; + IERC20(stream.asset).transfer(to, withdrawnAmount); + emit WithdrawFromLockupStream(streamId, to, IERC20(stream.asset), withdrawnAmount); + } } From 5f8828591c61c1f3ab3145c52bb53e7f3684860a Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 03:47:11 -0400 Subject: [PATCH 007/119] add tests for Wearer requirement --- contracts/mocks/MockHatsAdmin.sol | 77 +++++++++++++ .../mocks/MockHatsElectionEligibility.sol | 41 +++++++ test/DecentAutonomousAdminHat.test.ts | 108 ++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 contracts/mocks/MockHatsAdmin.sol create mode 100644 contracts/mocks/MockHatsElectionEligibility.sol create mode 100644 test/DecentAutonomousAdminHat.test.ts diff --git a/contracts/mocks/MockHatsAdmin.sol b/contracts/mocks/MockHatsAdmin.sol new file mode 100644 index 00000000..5b7fef2c --- /dev/null +++ b/contracts/mocks/MockHatsAdmin.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.19; + +import "../interfaces/hats/IHats.sol"; + +contract MockHatsAutoAdmin is IHats { + uint256 hatId = 0; + mapping(uint256 => address) public wearer; + mapping(uint256 => address) public eligibility; + + event HatCreated(uint256 hatId); + + function mintTopHat( + address _target, + string memory _details, + string memory _imageURI + ) external pure returns (uint256 topHatId) { + // Silence unused variable warnings + _target; + _details; + _imageURI; + return 0; + } + + function createHat( + uint256 _admin, + string calldata _details, + uint32 _maxSupply, + address _eligibility, + address _toggle, + bool _mutable, + string calldata _imageURI + ) external returns (uint256 newHatId) { + // Silence unused variable warnings + _admin; + _details; + _maxSupply; + _toggle; + _mutable; + _imageURI; + hatId++; + eligibility[hatId] = _eligibility; + emit HatCreated(hatId); + return hatId; + } + + function mintHat( + uint256 _hatId, + address _wearer + ) external returns (bool success) { + wearer[_hatId] = _wearer; + return true; + } + + function isWearerOfHat( + address _wearer, + uint256 _hatId + ) external view override returns (bool) { + return _wearer == wearer[_hatId]; + } + + function getHatEligibilityModule( + uint256 _hatId + ) external view override returns (address) { + return eligibility[_hatId]; + } + + function transferHat( + uint256 _hatId, + address from, + address to + ) external override { + // Silence unused variable warnings + from; + wearer[_hatId] = to; + } +} diff --git a/contracts/mocks/MockHatsElectionEligibility.sol b/contracts/mocks/MockHatsElectionEligibility.sol new file mode 100644 index 00000000..d9d8daab --- /dev/null +++ b/contracts/mocks/MockHatsElectionEligibility.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +contract MockHatsElectionEligibility { + uint128 private _currentTermEnd; + uint128 private _nextTermEnd; + mapping(uint128 => bool) private _electionStatus; + + event ElectionOpened(uint128 nextTermEnd); + event ElectionCompleted(uint128 termEnd, address[] winners); + event NewTermStarted(uint128 termEnd); + + // Mock function to simulate starting the next term + function startNextTerm() external { + _currentTermEnd = _nextTermEnd; + _nextTermEnd = 0; + + emit NewTermStarted(_currentTermEnd); + } + + function currentTermEnd() external view returns (uint128) { + return _currentTermEnd; + } + + function electionStatus(uint128 termEnd) external view returns (bool) { + return _electionStatus[termEnd]; + } + + // Functions to set the mock data for testing + function setCurrentTermEnd(uint128 termEnd) external { + _currentTermEnd = termEnd; + } + + function setNextTermEnd(uint128 termEnd) external { + _nextTermEnd = termEnd; + } + + function setElectionStatus(uint128 termEnd, bool status) external { + _electionStatus[termEnd] = status; + } +} diff --git a/test/DecentAutonomousAdminHat.test.ts b/test/DecentAutonomousAdminHat.test.ts new file mode 100644 index 00000000..a84934b9 --- /dev/null +++ b/test/DecentAutonomousAdminHat.test.ts @@ -0,0 +1,108 @@ +import { + DecentAutonomousAdminHat, + DecentAutonomousAdminHat__factory, + MockHatsAutoAdmin, + MockHatsAutoAdmin__factory, + MockHatsElectionEligibility, + MockHatsElectionEligibility__factory, +} from "../typechain-types" +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" +import { expect } from "chai" +import hre from "hardhat" + +describe.only("DecentAutonomousAdminHat", function () { + // Signer accounts + let deployer: SignerWithAddress + let currentWearer: SignerWithAddress + let randomUser: SignerWithAddress + let adminHatWearer: SignerWithAddress + let nominatedWearer: SignerWithAddress + + // Contract instances + let hatsProtocol: MockHatsAutoAdmin + let hatsElectionModule: MockHatsElectionEligibility + let adminHat: DecentAutonomousAdminHat + + // Variables + let userHatId: bigint + + beforeEach(async function () { + // Get signers + ;[deployer, adminHatWearer, currentWearer, nominatedWearer, randomUser] = + await hre.ethers.getSigners() + + // Deploy MockHatsAutoAdmin (Mock Hats Protocol) + hatsProtocol = await new MockHatsAutoAdmin__factory(deployer).deploy() + + // Deploy MockHatsElectionEligibility (Eligibility Module) + hatsElectionModule = await new MockHatsElectionEligibility__factory(deployer).deploy() + + // Create Admin Hat + const createAdminTx = await hatsProtocol.createHat( + await hatsProtocol.getAddress(), // Admin address (self-administered) + "Details", // Hat details + 100, // Max supply + hre.ethers.ZeroAddress, // Eligibility module (none) + hre.ethers.ZeroAddress, // Toggle module (none) + true, // Is mutable + "imageURI" // Image URI + ) + const createAdminTxReceipt = await createAdminTx.wait() + const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0] + + // Deploy DecentAutonomousAdminHat contract with the admin hat ID + adminHat = await new DecentAutonomousAdminHat__factory(deployer).deploy(adminHatId) + + // Mint the admin hat to adminHatWearer + await hatsProtocol.mintHat(adminHatId, await adminHatWearer.getAddress()) + + // Create User Hat under the admin hat + const createUserTx = await hatsProtocol.createHat( + await adminHat.getAddress(), // Admin address (adminHat contract) + "Details", // Hat details + 100, // Max supply + await hatsElectionModule.getAddress(), // Eligibility module (election module) + hre.ethers.ZeroAddress, // Toggle module (none) + false, // Is mutable + "imageURI" // Image URI + ) + + const createUserTxReceipt = await createUserTx.wait() + userHatId = createUserTxReceipt?.toJSON().logs[0].args[0] + + // Mint the user hat to currentWearer + await hatsProtocol.mintHat(userHatId, await currentWearer.getAddress()) + }) + + describe("triggerStartNextTerm", function () { + it("should correctly validate current wearer and transfer", async function () { + const args = { + currentWearer: currentWearer.address, + userHatProtocol: await hatsProtocol.getAddress(), + userHatId: userHatId, + nominatedWearer: nominatedWearer.address, + sablierStreamInfo: [], // No Sablier stream info for this test + } + + // Call triggerStartNextTerm on the adminHat contract + await adminHat.triggerStartNextTerm(args) + + // Verify the hat is now worn by the nominated wearer + expect(await hatsProtocol.isWearerOfHat(nominatedWearer.address, userHatId)).to.be.true + }) + it("should correctly invalidate random address as current wearer", async function () { + const args = { + currentWearer: randomUser.address, + userHatProtocol: await hatsProtocol.getAddress(), + userHatId: userHatId, + nominatedWearer: nominatedWearer.address, + sablierStreamInfo: [], // No Sablier stream info for this test + } + + // Verify the hat is now worn by the current wearer + await expect(adminHat.connect(randomUser).triggerStartNextTerm(args)).to.be.revertedWith( + "Not current wearer" + ) + }) + }) +}) From 92456962eb09a1c5493af6d7613d6ebeb80fe8d9 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 03:47:19 -0400 Subject: [PATCH 008/119] add deployment file --- deploy/core/018_deploy_DecentAutonomousAdminHat.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 deploy/core/018_deploy_DecentAutonomousAdminHat.ts diff --git a/deploy/core/018_deploy_DecentAutonomousAdminHat.ts b/deploy/core/018_deploy_DecentAutonomousAdminHat.ts new file mode 100644 index 00000000..b2862578 --- /dev/null +++ b/deploy/core/018_deploy_DecentAutonomousAdminHat.ts @@ -0,0 +1,9 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + await deployNonUpgradeable(hre, "DecentAutonomousAdminHat"); +}; + +export default func; From 77e81dbf524d1d2949383b075206a31af8723aa9 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 04:07:35 -0400 Subject: [PATCH 009/119] drop hat off name --- ...DecentAutonomousAdminHat.sol => DecentAutonomousAdmin.sol} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename contracts/{DecentAutonomousAdminHat.sol => DecentAutonomousAdmin.sol} (97%) diff --git a/contracts/DecentAutonomousAdminHat.sol b/contracts/DecentAutonomousAdmin.sol similarity index 97% rename from contracts/DecentAutonomousAdminHat.sol rename to contracts/DecentAutonomousAdmin.sol index 05901467..31760c3b 100644 --- a/contracts/DecentAutonomousAdminHat.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -4,8 +4,8 @@ import "./interfaces/hats/IHats.sol"; import "./interfaces/hats/IHatsElectionEligibility.sol"; import "./interfaces/sablier/ISablierV2LockupLinear.sol"; -contract DecentAutonomousAdminHat { - string public constant NAME = "DecentAutonomousAdminHat"; +contract DecentAutonomousAdmin { + string public constant NAME = "DecentAutonomousAdmin"; uint256 public adminHatId; From 85b82178f43077c5d47939545ae13efcc8a51ac2 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 04:45:58 -0400 Subject: [PATCH 010/119] add Autonomous Admin as wearer of admin hat - break out create method into seperate function --- contracts/DecentHats_0_1_0.sol | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index c00dadaa..9f79455a 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -9,6 +9,7 @@ import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; import {IHats} from "./interfaces/hats/IHats.sol"; import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; +import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; contract DecentHats_0_1_0 { string public constant NAME = "DecentHats_0_1_0"; @@ -205,6 +206,30 @@ contract DecentHats_0_1_0 { } } + function createAdminHatAndAccount( + IHats hatsProtocol, + uint256 adminHatId, + Hat calldata hat, + address topHatAccount, + IERC6551Registry registry, + address hatsAccountImplementation, + bytes32 salt + ) internal returns (uint256 hatId, address accountAddress) { + hatId = createHat(hatsProtocol, adminHatId, hat, topHatAccount); + + accountAddress = createAccount( + registry, + hatsAccountImplementation, + salt, + address(hatsProtocol), + hatId + ); + + // Set the Autonomous Admin as the wearer of the admin hat + DecentAutonomousAdmin adminHat = new DecentAutonomousAdmin(adminHatId); + hatsProtocol.mintHat(hatId, address(adminHat)); + } + function createAndDeclareTree(CreateTreeParams calldata params) public { bytes32 salt = getSalt(); @@ -219,7 +244,7 @@ contract DecentHats_0_1_0 { updateKeyValuePairs(params.keyValuePairs, topHatId); - (uint256 adminHatId, ) = createHatAndAccountAndMintAndStreams( + (uint256 adminHatId, ) = createAdminHatAndAccount( params.hatsProtocol, topHatId, params.adminHat, From 8666ac5957115692f705437b4d617cfd465cbabf Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 04:46:38 -0400 Subject: [PATCH 011/119] remove only --- test/DecentAutonomousAdminHat.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DecentAutonomousAdminHat.test.ts b/test/DecentAutonomousAdminHat.test.ts index a84934b9..5d8c7b45 100644 --- a/test/DecentAutonomousAdminHat.test.ts +++ b/test/DecentAutonomousAdminHat.test.ts @@ -10,7 +10,7 @@ import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" import { expect } from "chai" import hre from "hardhat" -describe.only("DecentAutonomousAdminHat", function () { +describe("DecentAutonomousAdminHat", function () { // Signer accounts let deployer: SignerWithAddress let currentWearer: SignerWithAddress From db60757cc32d5859e67e278584934eee9a5e320b Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 05:17:18 -0400 Subject: [PATCH 012/119] fix deploy name --- deploy/core/018_deploy_DecentAutonomousAdminHat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/core/018_deploy_DecentAutonomousAdminHat.ts b/deploy/core/018_deploy_DecentAutonomousAdminHat.ts index b2862578..f1adb696 100644 --- a/deploy/core/018_deploy_DecentAutonomousAdminHat.ts +++ b/deploy/core/018_deploy_DecentAutonomousAdminHat.ts @@ -3,7 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types"; import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentAutonomousAdminHat"); + await deployNonUpgradeable(hre, "DecentAutonomousAdmin"); }; export default func; From 6b2d609b9184e86917e085effbc57d82fddc6577 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 25 Sep 2024 23:21:36 -0400 Subject: [PATCH 013/119] pass eligibility address as param --- contracts/DecentHats_0_1_0.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index 9f79455a..bfbf2e74 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -29,6 +29,7 @@ contract DecentHats_0_1_0 { uint32 maxSupply; string details; string imageURI; + address eligibility; bool isMutable; address wearer; SablierStreamParams[] sablierParams; // Optional Sablier stream parameters @@ -93,7 +94,7 @@ contract DecentHats_0_1_0 { _hat.details, _hat.maxSupply, topHatAccount, - topHatAccount, + _hat.eligibility, _hat.isMutable, _hat.imageURI ); From b53a417ebea667cb249c8d3d34b6c730f5c58ed0 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 01:50:54 -0400 Subject: [PATCH 014/119] update with proxy pattern --- contracts/DecentAutonomousAdmin.sol | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 31760c3b..d6aef8be 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -6,7 +6,7 @@ import "./interfaces/sablier/ISablierV2LockupLinear.sol"; contract DecentAutonomousAdmin { string public constant NAME = "DecentAutonomousAdmin"; - + string public version_; uint256 public adminHatId; struct SablierStreamInfo { @@ -24,7 +24,14 @@ contract DecentAutonomousAdmin { // ////////////////////////////////////////////////////////////// // Constructor // ////////////////////////////////////////////////////////////// - constructor(uint256 _adminHatId) { + constructor(string memory _version) { + version_ = _version; + } + + // ////////////////////////////////////////////////////////////// + // Initializer + // ////////////////////////////////////////////////////////////// + function setUp(uint256 _adminHatId) public { adminHatId = _adminHatId; } From 4170355359500f09b88ba7932205a493f82b57f1 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 01:51:10 -0400 Subject: [PATCH 015/119] update with new props to deploy proxy --- contracts/DecentHats_0_1_0.sol | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index bfbf2e74..b8ed2a75 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -10,6 +10,7 @@ import {IHats} from "./interfaces/hats/IHats.sol"; import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; +import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; contract DecentHats_0_1_0 { string public constant NAME = "DecentHats_0_1_0"; @@ -38,6 +39,8 @@ contract DecentHats_0_1_0 { struct CreateTreeParams { IHats hatsProtocol; address hatsAccountImplementation; + ModuleProxyFactory moduleProxyFactory; + address decentAutonomousAdminMasterCopy; IERC6551Registry registry; address keyValuePairs; string topHatDetails; @@ -214,7 +217,9 @@ contract DecentHats_0_1_0 { address topHatAccount, IERC6551Registry registry, address hatsAccountImplementation, - bytes32 salt + bytes32 salt, + ModuleProxyFactory moduleProxyFactory, + address decentAutonomousAdminMasterCopy ) internal returns (uint256 hatId, address accountAddress) { hatId = createHat(hatsProtocol, adminHatId, hat, topHatAccount); @@ -226,9 +231,21 @@ contract DecentHats_0_1_0 { hatId ); - // Set the Autonomous Admin as the wearer of the admin hat - DecentAutonomousAdmin adminHat = new DecentAutonomousAdmin(adminHatId); - hatsProtocol.mintHat(hatId, address(adminHat)); + bytes memory initializer = abi.encodeWithSignature( + "setUp(uint256)", + adminHatId + ); + uint256 saltNonce = uint256( + keccak256(abi.encodePacked(block.timestamp, initializer)) + ); + hatsProtocol.mintHat( + hatId, + moduleProxyFactory.deployModule( + decentAutonomousAdminMasterCopy, + initializer, + saltNonce + ) + ); } function createAndDeclareTree(CreateTreeParams calldata params) public { @@ -252,7 +269,9 @@ contract DecentHats_0_1_0 { topHatAccount, params.registry, params.hatsAccountImplementation, - salt + salt, + params.moduleProxyFactory, + params.decentAutonomousAdminMasterCopy ); for (uint256 i = 0; i < params.hats.length; ) { From 5094eef96731721cf2c471270423a557b4ee5b7f Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 01:52:02 -0400 Subject: [PATCH 016/119] rename and update tests to support proxy pattern --- ....test.ts => DecentAutonomousAdmin.test.ts} | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) rename test/{DecentAutonomousAdminHat.test.ts => DecentAutonomousAdmin.test.ts} (75%) diff --git a/test/DecentAutonomousAdminHat.test.ts b/test/DecentAutonomousAdmin.test.ts similarity index 75% rename from test/DecentAutonomousAdminHat.test.ts rename to test/DecentAutonomousAdmin.test.ts index 5d8c7b45..c4f9e45f 100644 --- a/test/DecentAutonomousAdminHat.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -1,36 +1,40 @@ +import { ModuleProxyFactory } from "../typechain-types/@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory" import { - DecentAutonomousAdminHat, - DecentAutonomousAdminHat__factory, + DecentAutonomousAdmin, + DecentAutonomousAdmin__factory, MockHatsAutoAdmin, MockHatsAutoAdmin__factory, MockHatsElectionEligibility, MockHatsElectionEligibility__factory, + ModuleProxyFactory__factory, } from "../typechain-types" import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" import { expect } from "chai" import hre from "hardhat" -describe("DecentAutonomousAdminHat", function () { +describe.only("DecentAutonomousAdminHat", function () { // Signer accounts let deployer: SignerWithAddress let currentWearer: SignerWithAddress let randomUser: SignerWithAddress - let adminHatWearer: SignerWithAddress let nominatedWearer: SignerWithAddress // Contract instances let hatsProtocol: MockHatsAutoAdmin let hatsElectionModule: MockHatsElectionEligibility - let adminHat: DecentAutonomousAdminHat + let adminHat: DecentAutonomousAdmin + let adminHatMasterCopy: DecentAutonomousAdmin + let moduleProxyFactory: ModuleProxyFactory // Variables let userHatId: bigint beforeEach(async function () { // Get signers - ;[deployer, adminHatWearer, currentWearer, nominatedWearer, randomUser] = + ;[deployer, currentWearer, nominatedWearer, randomUser] = await hre.ethers.getSigners() + moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() // Deploy MockHatsAutoAdmin (Mock Hats Protocol) hatsProtocol = await new MockHatsAutoAdmin__factory(deployer).deploy() @@ -51,14 +55,23 @@ describe("DecentAutonomousAdminHat", function () { const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0] // Deploy DecentAutonomousAdminHat contract with the admin hat ID - adminHat = await new DecentAutonomousAdminHat__factory(deployer).deploy(adminHatId) + adminHatMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy("TEST") + const proxyDeployTx = await moduleProxyFactory.deployModule( + await adminHatMasterCopy.getAddress(), + adminHatMasterCopy.interface.encodeFunctionData("setUp", [adminHatId]), + 1n + ) + const proxyDeployTxReceipt = await proxyDeployTx.wait() + const proxyAddress = await proxyDeployTxReceipt?.toJSON().logs[0].args[0] + + adminHat = DecentAutonomousAdmin__factory.connect(proxyAddress, deployer) // Mint the admin hat to adminHatWearer - await hatsProtocol.mintHat(adminHatId, await adminHatWearer.getAddress()) + await hatsProtocol.mintHat(adminHatId, proxyAddress) // Create User Hat under the admin hat const createUserTx = await hatsProtocol.createHat( - await adminHat.getAddress(), // Admin address (adminHat contract) + proxyAddress, // Admin address (adminHat contract) "Details", // Hat details 100, // Max supply await hatsElectionModule.getAddress(), // Eligibility module (election module) From ca71da099ba29d9be4cf60ff60dedd1b787eed14 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 02:16:19 -0400 Subject: [PATCH 017/119] remove only --- test/DecentAutonomousAdmin.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index c4f9e45f..68fc2ec3 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -12,7 +12,7 @@ import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" import { expect } from "chai" import hre from "hardhat" -describe.only("DecentAutonomousAdminHat", function () { +describe("DecentAutonomousAdminHat", function () { // Signer accounts let deployer: SignerWithAddress let currentWearer: SignerWithAddress From 94d0cf47d5c83f7625ebf5ca33d7bf69fa720f3d Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 02:16:34 -0400 Subject: [PATCH 018/119] update DecentHats tests to support changes to contract --- test/DecentHats_0_1_0.test.ts | 736 +++++++++++++++++----------------- 1 file changed, 358 insertions(+), 378 deletions(-) diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index afaa5c95..f1741567 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -15,23 +15,22 @@ import { MockSablierV2LockupLinear, MockERC20__factory, MockERC20, -} from "../typechain-types"; + DecentAutonomousAdmin__factory, + ModuleProxyFactory__factory, +} from "../typechain-types" -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers, solidityPackedKeccak256 } from "ethers"; -import hre from "hardhat"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" +import { expect } from "chai" +import { ethers, solidityPackedKeccak256 } from "ethers" +import hre from "hardhat" -import { - getGnosisSafeL2Singleton, - getGnosisSafeProxyFactory, -} from "./GlobalSafeDeployments.test"; +import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from "./GlobalSafeDeployments.test" import { buildSafeTransaction, buildSignatureBytes, predictGnosisSafeAddress, safeSignTypedData, -} from "./helpers"; +} from "./helpers" const executeSafeTransaction = async ({ safe, @@ -39,20 +38,20 @@ const executeSafeTransaction = async ({ transactionData, signers, }: { - safe: GnosisSafeL2; - to: string; - transactionData: string; - signers: SignerWithAddress[]; + safe: GnosisSafeL2 + to: string + transactionData: string + signers: SignerWithAddress[] }) => { const safeTx = buildSafeTransaction({ to, data: transactionData, nonce: await safe.nonce(), - }); + }) const sigs = await Promise.all( signers.map(async (signer) => await safeSignTypedData(signer, safe, safeTx)) - ); + ) const tx = await safe.execTransaction( safeTx.to, @@ -65,59 +64,64 @@ const executeSafeTransaction = async ({ safeTx.gasToken, safeTx.refundReceiver, buildSignatureBytes(sigs) - ); + ) + + return tx +} - return tx; -}; +describe.only("DecentHats_0_1_0", () => { + let dao: SignerWithAddress -describe("DecentHats_0_1_0", () => { - let dao: SignerWithAddress; + let mockHats: MockHats + let mockHatsAddress: string - let mockHats: MockHats; - let mockHatsAddress: string; + let keyValuePairs: KeyValuePairs + let gnosisSafe: GnosisSafeL2 - let keyValuePairs: KeyValuePairs; - let gnosisSafe: GnosisSafeL2; + let decentHats: DecentHats_0_1_0 + let decentHatsAddress: string - let decentHats: DecentHats_0_1_0; - let decentHatsAddress: string; + let gnosisSafeAddress: string + let erc6551Registry: ERC6551Registry - let gnosisSafeAddress: string; - let erc6551Registry: ERC6551Registry; + let adminHatMasterCopyAddress: string + let moduleProxyFactoryAddress: string - let mockHatsAccountImplementation: MockHatsAccount; - let mockHatsAccountImplementationAddress: string; + let mockHatsAccountImplementation: MockHatsAccount + let mockHatsAccountImplementationAddress: string - let mockSablier: MockSablierV2LockupLinear; - let mockSablierAddress: string; + let mockSablier: MockSablierV2LockupLinear + let mockSablierAddress: string - let mockERC20: MockERC20; - let mockERC20Address: string; + let mockERC20: MockERC20 + let mockERC20Address: string beforeEach(async () => { - const signers = await hre.ethers.getSigners(); - const [deployer] = signers; - [, dao] = signers; - - mockHats = await new MockHats__factory(deployer).deploy(); - mockHatsAddress = await mockHats.getAddress(); - keyValuePairs = await new KeyValuePairs__factory(deployer).deploy(); - erc6551Registry = await new ERC6551Registry__factory(deployer).deploy(); - mockHatsAccountImplementation = await new MockHatsAccount__factory( - deployer - ).deploy(); - mockHatsAccountImplementationAddress = - await mockHatsAccountImplementation.getAddress(); - decentHats = await new DecentHats_0_1_0__factory(deployer).deploy(); - decentHatsAddress = await decentHats.getAddress(); - - const gnosisSafeProxyFactory = getGnosisSafeProxyFactory(); - const gnosisSafeL2Singleton = getGnosisSafeL2Singleton(); - const gnosisSafeL2SingletonAddress = - await gnosisSafeL2Singleton.getAddress(); - - const createGnosisSetupCalldata = - GnosisSafeL2__factory.createInterface().encodeFunctionData("setup", [ + const signers = await hre.ethers.getSigners() + const [deployer] = signers + ;[, dao] = signers + + mockHats = await new MockHats__factory(deployer).deploy() + mockHatsAddress = await mockHats.getAddress() + keyValuePairs = await new KeyValuePairs__factory(deployer).deploy() + erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() + mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy() + mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress() + decentHats = await new DecentHats_0_1_0__factory(deployer).deploy() + decentHatsAddress = await decentHats.getAddress() + + const moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() + moduleProxyFactoryAddress = await moduleProxyFactory.getAddress() + const adminHatMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy("TEST") + adminHatMasterCopyAddress = await adminHatMasterCopy.getAddress() + + const gnosisSafeProxyFactory = getGnosisSafeProxyFactory() + const gnosisSafeL2Singleton = getGnosisSafeL2Singleton() + const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress() + + const createGnosisSetupCalldata = GnosisSafeL2__factory.createInterface().encodeFunctionData( + "setup", + [ [dao.address], 1, hre.ethers.ZeroAddress, @@ -126,207 +130,193 @@ describe("DecentHats_0_1_0", () => { hre.ethers.ZeroAddress, 0, hre.ethers.ZeroAddress, - ]); + ] + ) - const saltNum = BigInt( - `0x${Buffer.from(hre.ethers.randomBytes(32)).toString("hex")}` - ); + const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString("hex")}`) const predictedGnosisSafeAddress = await predictGnosisSafeAddress( createGnosisSetupCalldata, saltNum, gnosisSafeL2SingletonAddress, gnosisSafeProxyFactory - ); - gnosisSafeAddress = predictedGnosisSafeAddress; + ) + gnosisSafeAddress = predictedGnosisSafeAddress await gnosisSafeProxyFactory.createProxyWithNonce( gnosisSafeL2SingletonAddress, createGnosisSetupCalldata, saltNum - ); + ) - gnosisSafe = GnosisSafeL2__factory.connect( - predictedGnosisSafeAddress, - deployer - ); + gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer) // Deploy MockSablierV2LockupLinear - mockSablier = await new MockSablierV2LockupLinear__factory( - deployer - ).deploy(); - mockSablierAddress = await mockSablier.getAddress(); + mockSablier = await new MockSablierV2LockupLinear__factory(deployer).deploy() + mockSablierAddress = await mockSablier.getAddress() - mockERC20 = await new MockERC20__factory(deployer).deploy( - "MockERC20", - "MCK" - ); - mockERC20Address = await mockERC20.getAddress(); + mockERC20 = await new MockERC20__factory(deployer).deploy("MockERC20", "MCK") + mockERC20Address = await mockERC20.getAddress() - await mockERC20.mint(gnosisSafeAddress, ethers.parseEther("1000000")); - }); + await mockERC20.mint(gnosisSafeAddress, ethers.parseEther("1000000")) + }) describe("DecentHats as a Module", () => { - let enableModuleTx: ethers.ContractTransactionResponse; + let enableModuleTx: ethers.ContractTransactionResponse beforeEach(async () => { enableModuleTx = await executeSafeTransaction({ safe: gnosisSafe, to: gnosisSafeAddress, - transactionData: - GnosisSafeL2__factory.createInterface().encodeFunctionData( - "enableModule", - [decentHatsAddress] - ), + transactionData: GnosisSafeL2__factory.createInterface().encodeFunctionData( + "enableModule", + [decentHatsAddress] + ), signers: [dao], - }); - }); + }) + }) it("Emits an ExecutionSuccess event", async () => { - await expect(enableModuleTx).to.emit(gnosisSafe, "ExecutionSuccess"); - }); + await expect(enableModuleTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an EnabledModule event", async () => { - await expect(enableModuleTx) - .to.emit(gnosisSafe, "EnabledModule") - .withArgs(decentHatsAddress); - }); + await expect(enableModuleTx).to.emit(gnosisSafe, "EnabledModule").withArgs(decentHatsAddress) + }) describe("Creating a new Top Hat and Tree", () => { - let createAndDeclareTreeTx: ethers.ContractTransactionResponse; + let createAndDeclareTreeTx: ethers.ContractTransactionResponse beforeEach(async () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + moduleProxyFactory: moduleProxyFactoryAddress, + decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + eligibility: ethers.ZeroAddress, + sablierParams: [], + }, + hats: [ + { maxSupply: 1, details: "", imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, + eligibility: ethers.ZeroAddress, sablierParams: [], }, - hats: [ - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - ], - }, - ] - ), + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + eligibility: ethers.ZeroAddress, + sablierParams: [], + }, + ], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx).to.emit( - gnosisSafe, - "ExecutionSuccess" - ); - }); + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an ExecutionFromModuleSuccess event", async () => { await expect(createAndDeclareTreeTx) .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress); - }); + .withArgs(decentHatsAddress) + }) it("Emits some hatsTreeId ValueUpdated events", async () => { await expect(createAndDeclareTreeTx) .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "0"); - }); + .withArgs(gnosisSafeAddress, "topHatId", "0") + }) describe("Multiple calls", () => { - let createAndDeclareTreeTx2: ethers.ContractTransactionResponse; + let createAndDeclareTreeTx2: ethers.ContractTransactionResponse beforeEach(async () => { createAndDeclareTreeTx2 = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - hats: [], + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + moduleProxyFactory: moduleProxyFactoryAddress, + decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + eligibility: ethers.ZeroAddress, }, - ] - ), + hats: [], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx2).to.emit( - gnosisSafe, - "ExecutionSuccess" - ); - }); + await expect(createAndDeclareTreeTx2).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an ExecutionFromModuleSuccess event", async () => { await expect(createAndDeclareTreeTx2) .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress); - }); + .withArgs(decentHatsAddress) + }) it("Creates Top Hats with sequential IDs", async () => { await expect(createAndDeclareTreeTx2) .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "4"); - }); - }); + .withArgs(gnosisSafeAddress, "topHatId", "4") + }) + }) describe("Creating Hats Accounts", () => { - let salt: string; + let salt: string beforeEach(async () => { salt = solidityPackedKeccak256( ["string", "uint256", "address"], ["DecentHats_0_1_0", await hre.getChainId(), decentHatsAddress] - ); - }); + ) + }) const getHatAccount = async (hatId: bigint) => { const hatAccountAddress = await erc6551Registry.account( @@ -335,283 +325,273 @@ describe("DecentHats_0_1_0", () => { await hre.getChainId(), mockHatsAddress, hatId - ); + ) const hatAccount = MockHatsAccount__factory.connect( hatAccountAddress, hre.ethers.provider - ); + ) - return hatAccount; - }; + return hatAccount + } it("Generates the correct Addresses for the current Hats", async () => { - const currentCount = await mockHats.count(); + const currentCount = await mockHats.count() for (let i = 0n; i < currentCount; i++) { - const topHatAccount = await getHatAccount(i); - expect(await topHatAccount.tokenId()).eq(i); - expect(await topHatAccount.tokenImplementation()).eq( - mockHatsAddress - ); + const topHatAccount = await getHatAccount(i) + expect(await topHatAccount.tokenId()).eq(i) + expect(await topHatAccount.tokenImplementation()).eq(mockHatsAddress) } - }); - }); - }); + }) + }) + }) describe("Creating a new Top Hat and Tree with Sablier Streams", () => { - let createAndDeclareTreeTx: ethers.ContractTransactionResponse; - let currentBlockTimestamp: number; + let createAndDeclareTreeTx: ethers.ContractTransactionResponse + let currentBlockTimestamp: number beforeEach(async () => { - currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))! - .timestamp; + currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + moduleProxyFactory: moduleProxyFactoryAddress, + decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + eligibility: ethers.ZeroAddress, + }, + hats: [ + { maxSupply: 1, details: "", imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, + eligibility: ethers.ZeroAddress, + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("100"), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: 0, + end: currentBlockTimestamp + 2592000, // 30 days from now + }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + }, + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + eligibility: ethers.ZeroAddress, sablierParams: [], }, - hats: [ - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [ - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("100"), - asset: mockERC20Address, - cancelable: true, - transferable: false, - timestamps: { - start: currentBlockTimestamp, - cliff: 0, - end: currentBlockTimestamp + 2592000, // 30 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, - }, - ], - }, - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - ], - }, - ] - ), + ], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx).to.emit( - gnosisSafe, - "ExecutionSuccess" - ); - }); + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an ExecutionFromModuleSuccess event", async () => { await expect(createAndDeclareTreeTx) .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress); - }); + .withArgs(decentHatsAddress) + }) it("Emits some hatsTreeId ValueUpdated events", async () => { await expect(createAndDeclareTreeTx) .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "0"); - }); + .withArgs(gnosisSafeAddress, "topHatId", "0") + }) it("Creates a Sablier stream for the hat with stream parameters", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(1); + ) + expect(streamCreatedEvents.length).to.equal(1) - const event = streamCreatedEvents[0]; - expect(event.args.sender).to.equal(gnosisSafeAddress); - expect(event.args.recipient).to.not.equal(ethers.ZeroAddress); - expect(event.args.totalAmount).to.equal(ethers.parseEther("100")); - }); + const event = streamCreatedEvents[0] + expect(event.args.sender).to.equal(gnosisSafeAddress) + expect(event.args.recipient).to.not.equal(ethers.ZeroAddress) + expect(event.args.totalAmount).to.equal(ethers.parseEther("100")) + }) it("Does not create a Sablier stream for hats without stream parameters", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(1); // Only one stream should be created - }); + ) + expect(streamCreatedEvents.length).to.equal(1) // Only one stream should be created + }) it("Creates a Sablier stream with correct timestamps", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(1); + ) + expect(streamCreatedEvents.length).to.equal(1) - const streamId = streamCreatedEvents[0].args.streamId; - const stream = await mockSablier.getStream(streamId); + const streamId = streamCreatedEvents[0].args.streamId + const stream = await mockSablier.getStream(streamId) - expect(stream.startTime).to.equal(currentBlockTimestamp); - expect(stream.endTime).to.equal(currentBlockTimestamp + 2592000); - }); - }); + expect(stream.startTime).to.equal(currentBlockTimestamp) + expect(stream.endTime).to.equal(currentBlockTimestamp + 2592000) + }) + }) describe("Creating a new Top Hat and Tree with Multiple Sablier Streams per Hat", () => { - let currentBlockTimestamp: number; + let currentBlockTimestamp: number beforeEach(async () => { - currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))! - .timestamp; + currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + moduleProxyFactory: moduleProxyFactoryAddress, + decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + eligibility: ethers.ZeroAddress, + }, + hats: [ + { maxSupply: 1, details: "", imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - sablierParams: [], - }, - hats: [ - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [ - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("100"), - asset: mockERC20Address, - cancelable: true, - transferable: false, - timestamps: { - start: currentBlockTimestamp, - cliff: currentBlockTimestamp + 86400, // 1 day cliff - end: currentBlockTimestamp + 2592000, // 30 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, + eligibility: ethers.ZeroAddress, + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("100"), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: currentBlockTimestamp + 86400, // 1 day cliff + end: currentBlockTimestamp + 2592000, // 30 days from now }, - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("50"), - asset: mockERC20Address, - cancelable: false, - transferable: true, - timestamps: { - start: currentBlockTimestamp, - cliff: 0, // No cliff - end: currentBlockTimestamp + 1296000, // 15 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("50"), + asset: mockERC20Address, + cancelable: false, + transferable: true, + timestamps: { + start: currentBlockTimestamp, + cliff: 0, // No cliff + end: currentBlockTimestamp + 1296000, // 15 days from now }, - ], - }, - ], - }, - ] - ), + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + }, + ], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Creates multiple Sablier streams for a single hat", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(2); + ) + expect(streamCreatedEvents.length).to.equal(2) - const event1 = streamCreatedEvents[0]; - expect(event1.args.sender).to.equal(gnosisSafeAddress); - expect(event1.args.recipient).to.not.equal(ethers.ZeroAddress); - expect(event1.args.totalAmount).to.equal(ethers.parseEther("100")); + const event1 = streamCreatedEvents[0] + expect(event1.args.sender).to.equal(gnosisSafeAddress) + expect(event1.args.recipient).to.not.equal(ethers.ZeroAddress) + expect(event1.args.totalAmount).to.equal(ethers.parseEther("100")) - const event2 = streamCreatedEvents[1]; - expect(event2.args.sender).to.equal(gnosisSafeAddress); - expect(event2.args.recipient).to.equal(event1.args.recipient); - expect(event2.args.totalAmount).to.equal(ethers.parseEther("50")); - }); + const event2 = streamCreatedEvents[1] + expect(event2.args.sender).to.equal(gnosisSafeAddress) + expect(event2.args.recipient).to.equal(event1.args.recipient) + expect(event2.args.totalAmount).to.equal(ethers.parseEther("50")) + }) it("Creates streams with correct parameters", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - - const stream1 = await mockSablier.getStream( - streamCreatedEvents[0].args.streamId - ); - expect(stream1.cancelable).to.be.true; - expect(stream1.transferable).to.be.false; - expect(stream1.endTime - stream1.startTime).to.equal(2592000); - - const stream2 = await mockSablier.getStream( - streamCreatedEvents[1].args.streamId - ); - expect(stream2.cancelable).to.be.false; - expect(stream2.transferable).to.be.true; - expect(stream2.endTime - stream2.startTime).to.equal(1296000); - }); + ) + + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) + expect(stream1.cancelable).to.be.true + expect(stream1.transferable).to.be.false + expect(stream1.endTime - stream1.startTime).to.equal(2592000) + + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) + expect(stream2.cancelable).to.be.false + expect(stream2.transferable).to.be.true + expect(stream2.endTime - stream2.startTime).to.equal(1296000) + }) it("Creates streams with correct timestamps", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - - const stream1 = await mockSablier.getStream( - streamCreatedEvents[0].args.streamId - ); - expect(stream1.startTime).to.equal(currentBlockTimestamp); - expect(stream1.endTime).to.equal(currentBlockTimestamp + 2592000); - - const stream2 = await mockSablier.getStream( - streamCreatedEvents[1].args.streamId - ); - expect(stream2.startTime).to.equal(currentBlockTimestamp); - expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000); - }); - }); - }); -}); + ) + + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) + expect(stream1.startTime).to.equal(currentBlockTimestamp) + expect(stream1.endTime).to.equal(currentBlockTimestamp + 2592000) + + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) + expect(stream2.startTime).to.equal(currentBlockTimestamp) + expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000) + }) + }) + }) +}) From 62917d55fa20657bd89a4ec8212b67e69ae70861 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 02:17:08 -0400 Subject: [PATCH 019/119] update deploy with constructor arg --- ...utonomousAdminHat.ts => 018_deploy_DecentAutonomousAdmin.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename deploy/core/{018_deploy_DecentAutonomousAdminHat.ts => 018_deploy_DecentAutonomousAdmin.ts} (80%) diff --git a/deploy/core/018_deploy_DecentAutonomousAdminHat.ts b/deploy/core/018_deploy_DecentAutonomousAdmin.ts similarity index 80% rename from deploy/core/018_deploy_DecentAutonomousAdminHat.ts rename to deploy/core/018_deploy_DecentAutonomousAdmin.ts index f1adb696..cbdb509d 100644 --- a/deploy/core/018_deploy_DecentAutonomousAdminHat.ts +++ b/deploy/core/018_deploy_DecentAutonomousAdmin.ts @@ -3,7 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types"; import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentAutonomousAdmin"); + await deployNonUpgradeable(hre, "DecentAutonomousAdmin", ["0.1.0"]); }; export default func; From 3c067933cb5514e959a7764d2c14c4a93103c6b5 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:57:08 -0400 Subject: [PATCH 020/119] use existing salt --- contracts/DecentHats_0_1_0.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index b8ed2a75..ac7eed3d 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -235,9 +235,7 @@ contract DecentHats_0_1_0 { "setUp(uint256)", adminHatId ); - uint256 saltNonce = uint256( - keccak256(abi.encodePacked(block.timestamp, initializer)) - ); + uint256 saltNonce = uint256(salt); hatsProtocol.mintHat( hatId, moduleProxyFactory.deployModule( From bda7d794b6eb0f7754b217c9aa8d8c1f1171646b Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:57:35 -0400 Subject: [PATCH 021/119] remove only --- test/DecentHats_0_1_0.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index f1741567..6de8c37d 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -69,7 +69,7 @@ const executeSafeTransaction = async ({ return tx } -describe.only("DecentHats_0_1_0", () => { +describe("DecentHats_0_1_0", () => { let dao: SignerWithAddress let mockHats: MockHats From 6ff9499096cc310b8d15b000b59ad9dae886760f Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:59:24 -0400 Subject: [PATCH 022/119] don't add new variables --- contracts/DecentHats_0_1_0.sol | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index ac7eed3d..6c568f43 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -231,17 +231,12 @@ contract DecentHats_0_1_0 { hatId ); - bytes memory initializer = abi.encodeWithSignature( - "setUp(uint256)", - adminHatId - ); - uint256 saltNonce = uint256(salt); hatsProtocol.mintHat( hatId, moduleProxyFactory.deployModule( decentAutonomousAdminMasterCopy, - initializer, - saltNonce + abi.encodeWithSignature("setUp(uint256)", adminHatId), + uint256(salt) ) ); } From ce2087c2c6e46560d04bfd133a9fb33f0b6bd8db Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:47:09 -0400 Subject: [PATCH 023/119] add interface for HatModuleFactory --- contracts/interfaces/IHatModuleFactory.sol | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 contracts/interfaces/IHatModuleFactory.sol diff --git a/contracts/interfaces/IHatModuleFactory.sol b/contracts/interfaces/IHatModuleFactory.sol new file mode 100644 index 00000000..46c74ac0 --- /dev/null +++ b/contracts/interfaces/IHatModuleFactory.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + + +interface IHatsModuleFactory { + function createHatsModule( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + bytes calldata _initData, + uint256 _saltNonce + ) external returns (address _instance); + + function getHatsModuleAddress( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + uint256 _saltNonce + ) external view returns (address); +} From 04c2639fa8aff480b3ac5cbc33aebdf4749d49d9 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 1 Oct 2024 02:20:22 -0400 Subject: [PATCH 024/119] update to add electionModuleAddress to termed roles --- contracts/DecentHats_0_1_0.sol | 66 +++++++++++++++++++++++++++-- contracts/interfaces/hats/IHats.sol | 2 + contracts/mocks/MockHats.sol | 2 + contracts/mocks/MockHatsAdmin.sol | 4 ++ 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index 6c568f43..8e6bfcde 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -11,6 +11,8 @@ import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinea import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; +import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; +import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; contract DecentHats_0_1_0 { string public constant NAME = "DecentHats_0_1_0"; @@ -26,14 +28,20 @@ contract DecentHats_0_1_0 { LockupLinear.Broker broker; } + struct TermedParams { + uint128 firstTermEnd; + address[] nominatedWearers; + } + struct Hat { uint32 maxSupply; string details; string imageURI; - address eligibility; bool isMutable; address wearer; + bool isTermed; SablierStreamParams[] sablierParams; // Optional Sablier stream parameters + TermedParams termedParams; // Optional termed parameters } struct CreateTreeParams { @@ -47,6 +55,8 @@ contract DecentHats_0_1_0 { string topHatImageURI; Hat adminHat; Hat[] hats; + IHatsModuleFactory hatsModuleFactory; + address hatsElectionEligibilityImplementation; } function getSalt() internal view returns (bytes32 salt) { @@ -96,8 +106,9 @@ contract DecentHats_0_1_0 { adminHatId, _hat.details, _hat.maxSupply, + // ? @todo should be this be dead HATS address? + topHatAccount, topHatAccount, - _hat.eligibility, _hat.isMutable, _hat.imageURI ); @@ -150,10 +161,27 @@ contract DecentHats_0_1_0 { address topHatAccount, IERC6551Registry registry, address hatsAccountImplementation, - bytes32 salt + bytes32 salt, + IHatsModuleFactory hatsModuleFactory, + address hatsElectionEligibilityImplementation, + uint256 topHatId ) internal returns (uint256 hatId, address accountAddress) { hatId = createHat(hatsProtocol, adminHatId, hat, topHatAccount); + if (hat.isTermed) { + // Create election module and set as eligiblity, elect, and start next term + createElectionModuleAndExecuteFirstTerm( + hatsProtocol, + hatsModuleFactory, + hatsElectionEligibilityImplementation, + hatId, + hat.termedParams.firstTermEnd, + hat.termedParams.nominatedWearers, + abi.encodePacked(topHatId), + uint256(salt) + ); + } + accountAddress = createAccount( registry, hatsAccountImplementation, @@ -241,6 +269,33 @@ contract DecentHats_0_1_0 { ); } + function createElectionModuleAndExecuteFirstTerm( + IHats hatsProtocol, + IHatsModuleFactory hatsModuleFactory, + address hatsElectionEligibilityImplementation, + uint256 hatId, + uint128 firstTermEnd, + address[] memory nominatedWearer, + bytes memory otherImmutableArgs, + uint256 saltNonce + ) internal returns (address) { + address electionModuleAddress = hatsModuleFactory.createHatsModule( + hatsElectionEligibilityImplementation, + hatId, + otherImmutableArgs, + abi.encode(firstTermEnd), + saltNonce + ); + hatsProtocol.changeHatEligibility(hatId, electionModuleAddress); + + IHatsElectionEligibility(electionModuleAddress).elect( + firstTermEnd, + nominatedWearer + ); + IHatsElectionEligibility(electionModuleAddress).startNextTerm(); + return electionModuleAddress; + } + function createAndDeclareTree(CreateTreeParams calldata params) public { bytes32 salt = getSalt(); @@ -275,7 +330,10 @@ contract DecentHats_0_1_0 { topHatAccount, params.registry, params.hatsAccountImplementation, - salt + salt, + params.hatsModuleFactory, + params.hatsElectionEligibilityImplementation, + topHatId ); unchecked { diff --git a/contracts/interfaces/hats/IHats.sol b/contracts/interfaces/hats/IHats.sol index 3a3743ea..0f4218b7 100644 --- a/contracts/interfaces/hats/IHats.sol +++ b/contracts/interfaces/hats/IHats.sol @@ -48,4 +48,6 @@ interface IHats { address _user, uint256 _hatId ) external view returns (bool isWearer); + + function changeHatEligibility(uint256 _hatId, address _newEligibility) external; } diff --git a/contracts/mocks/MockHats.sol b/contracts/mocks/MockHats.sol index 1cf74405..17859685 100644 --- a/contracts/mocks/MockHats.sol +++ b/contracts/mocks/MockHats.sol @@ -42,4 +42,6 @@ contract MockHats is IHats { address _user, uint256 _hatId ) external view returns (bool isWearer) {} + + function changeHatEligibility(uint256, address) external {} } diff --git a/contracts/mocks/MockHatsAdmin.sol b/contracts/mocks/MockHatsAdmin.sol index 5b7fef2c..914c0d30 100644 --- a/contracts/mocks/MockHatsAdmin.sol +++ b/contracts/mocks/MockHatsAdmin.sol @@ -74,4 +74,8 @@ contract MockHatsAutoAdmin is IHats { from; wearer[_hatId] = to; } + + function changeHatEligibility(uint256 _hatId, address _newEligibility) external override { + eligibility[_hatId] = _newEligibility; + } } From f379a19ee8d3d5a2cdedef8ddd7abb689cb65881 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:02:50 -0400 Subject: [PATCH 025/119] update test with new params --- contracts/mocks/MockHatsModuleFactory.sol | 36 ++++++++++++ test/DecentHats_0_1_0.test.ts | 72 ++++++++++++++++++++--- 2 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 contracts/mocks/MockHatsModuleFactory.sol diff --git a/contracts/mocks/MockHatsModuleFactory.sol b/contracts/mocks/MockHatsModuleFactory.sol new file mode 100644 index 00000000..2bfb92ba --- /dev/null +++ b/contracts/mocks/MockHatsModuleFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IHatsModuleFactory} from "../interfaces/IHatModuleFactory.sol"; + +contract MockHatsModuleFactory is IHatsModuleFactory { + function createHatsModule( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + bytes calldata _initData, + uint256 _saltNonce + ) external pure returns (address _instance) { + // Silence unused variable warnings + _implementation; + _hatId; + _otherImmutableArgs; + _initData; + _saltNonce; + return address(0); + } + + function getHatsModuleAddress( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + uint256 _saltNonce + ) external pure returns (address) { + // Silence unused variable warnings + _implementation; + _hatId; + _otherImmutableArgs; + _saltNonce; + return address(0); + } +} diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index 6de8c37d..b99292cb 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -17,6 +17,7 @@ import { MockERC20, DecentAutonomousAdmin__factory, ModuleProxyFactory__factory, + MockHatsElectionEligibility__factory, } from "../typechain-types" import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" @@ -96,6 +97,9 @@ describe("DecentHats_0_1_0", () => { let mockERC20: MockERC20 let mockERC20Address: string + let mockHatsElectionEligibilityImplementationAddress: string + let mockHatsModuleFactoryAddress: string + beforeEach(async () => { const signers = await hre.ethers.getSigners() const [deployer] = signers @@ -103,6 +107,12 @@ describe("DecentHats_0_1_0", () => { mockHats = await new MockHats__factory(deployer).deploy() mockHatsAddress = await mockHats.getAddress() + const mockHatsElectionEligibilityImplementation = + await new MockHatsElectionEligibility__factory(deployer).deploy() + mockHatsElectionEligibilityImplementationAddress = + await mockHatsElectionEligibilityImplementation.getAddress() + const mockHatsModuleFactory = await new ModuleProxyFactory__factory(deployer).deploy() + mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress() keyValuePairs = await new KeyValuePairs__factory(deployer).deploy() erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy() @@ -209,8 +219,12 @@ describe("DecentHats_0_1_0", () => { imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - eligibility: ethers.ZeroAddress, sablierParams: [], + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, hats: [ { @@ -219,8 +233,12 @@ describe("DecentHats_0_1_0", () => { imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - eligibility: ethers.ZeroAddress, sablierParams: [], + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, { maxSupply: 1, @@ -228,10 +246,16 @@ describe("DecentHats_0_1_0", () => { imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - eligibility: ethers.ZeroAddress, sablierParams: [], + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, ], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -281,9 +305,15 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - eligibility: ethers.ZeroAddress, + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, hats: [], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -376,7 +406,11 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - eligibility: ethers.ZeroAddress, + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, hats: [ { @@ -385,7 +419,6 @@ describe("DecentHats_0_1_0", () => { imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - eligibility: ethers.ZeroAddress, sablierParams: [ { sablier: mockSablierAddress, @@ -402,6 +435,11 @@ describe("DecentHats_0_1_0", () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, { maxSupply: 1, @@ -409,10 +447,16 @@ describe("DecentHats_0_1_0", () => { imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - eligibility: ethers.ZeroAddress, sablierParams: [], + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, ], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -497,7 +541,11 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - eligibility: ethers.ZeroAddress, + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, hats: [ { @@ -506,7 +554,6 @@ describe("DecentHats_0_1_0", () => { imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - eligibility: ethers.ZeroAddress, sablierParams: [ { sablier: mockSablierAddress, @@ -537,8 +584,15 @@ describe("DecentHats_0_1_0", () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], + isTermed: false, + termedParams: { + firstTermEnd: 0, + nominatedWearers: [], + }, }, ], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, ] ), From 79fd8db0c36f01ad73806d8fddbd28f3bce1907c Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:25:32 -0400 Subject: [PATCH 026/119] rename param --- contracts/DecentHats_0_1_0.sol | 10 +++++----- test/DecentHats_0_1_0.test.ts | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index 8e6bfcde..06ce94fe 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -29,7 +29,7 @@ contract DecentHats_0_1_0 { } struct TermedParams { - uint128 firstTermEnd; + uint128 termEndTs; address[] nominatedWearers; } @@ -175,7 +175,7 @@ contract DecentHats_0_1_0 { hatsModuleFactory, hatsElectionEligibilityImplementation, hatId, - hat.termedParams.firstTermEnd, + hat.termedParams.termEndTs, hat.termedParams.nominatedWearers, abi.encodePacked(topHatId), uint256(salt) @@ -274,7 +274,7 @@ contract DecentHats_0_1_0 { IHatsModuleFactory hatsModuleFactory, address hatsElectionEligibilityImplementation, uint256 hatId, - uint128 firstTermEnd, + uint128 termEndTs, address[] memory nominatedWearer, bytes memory otherImmutableArgs, uint256 saltNonce @@ -283,13 +283,13 @@ contract DecentHats_0_1_0 { hatsElectionEligibilityImplementation, hatId, otherImmutableArgs, - abi.encode(firstTermEnd), + abi.encode(termEndTs), saltNonce ); hatsProtocol.changeHatEligibility(hatId, electionModuleAddress); IHatsElectionEligibility(electionModuleAddress).elect( - firstTermEnd, + termEndTs, nominatedWearer ); IHatsElectionEligibility(electionModuleAddress).startNextTerm(); diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index b99292cb..5023075c 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -222,7 +222,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -236,7 +236,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -249,7 +249,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -307,7 +307,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -408,7 +408,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -437,7 +437,7 @@ describe("DecentHats_0_1_0", () => { ], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -450,7 +450,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -543,7 +543,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, @@ -586,7 +586,7 @@ describe("DecentHats_0_1_0", () => { ], isTermed: false, termedParams: { - firstTermEnd: 0, + termEndTs: 0, nominatedWearers: [], }, }, From 7fe694788b878da0c37b3d82b677fe79eb61672a Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:55:47 -0400 Subject: [PATCH 027/119] revert use of moduleProxyfactory to deploy DecentAutoAdmin --- contracts/DecentAutonomousAdmin.sol | 8 +------- contracts/DecentHats_0_1_0.sol | 17 +++-------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index d6aef8be..f228f346 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -24,14 +24,8 @@ contract DecentAutonomousAdmin { // ////////////////////////////////////////////////////////////// // Constructor // ////////////////////////////////////////////////////////////// - constructor(string memory _version) { + constructor(string memory _version, uint256 _adminHatId) { version_ = _version; - } - - // ////////////////////////////////////////////////////////////// - // Initializer - // ////////////////////////////////////////////////////////////// - function setUp(uint256 _adminHatId) public { adminHatId = _adminHatId; } diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index 06ce94fe..be3684ad 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -10,7 +10,6 @@ import {IHats} from "./interfaces/hats/IHats.sol"; import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; -import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; @@ -47,8 +46,6 @@ contract DecentHats_0_1_0 { struct CreateTreeParams { IHats hatsProtocol; address hatsAccountImplementation; - ModuleProxyFactory moduleProxyFactory; - address decentAutonomousAdminMasterCopy; IERC6551Registry registry; address keyValuePairs; string topHatDetails; @@ -245,9 +242,7 @@ contract DecentHats_0_1_0 { address topHatAccount, IERC6551Registry registry, address hatsAccountImplementation, - bytes32 salt, - ModuleProxyFactory moduleProxyFactory, - address decentAutonomousAdminMasterCopy + bytes32 salt ) internal returns (uint256 hatId, address accountAddress) { hatId = createHat(hatsProtocol, adminHatId, hat, topHatAccount); @@ -261,11 +256,7 @@ contract DecentHats_0_1_0 { hatsProtocol.mintHat( hatId, - moduleProxyFactory.deployModule( - decentAutonomousAdminMasterCopy, - abi.encodeWithSignature("setUp(uint256)", adminHatId), - uint256(salt) - ) + address(new DecentAutonomousAdmin("0_0_1", adminHatId)) ); } @@ -317,9 +308,7 @@ contract DecentHats_0_1_0 { topHatAccount, params.registry, params.hatsAccountImplementation, - salt, - params.moduleProxyFactory, - params.decentAutonomousAdminMasterCopy + salt ); for (uint256 i = 0; i < params.hats.length; ) { From 5cdba24670ba02978ab6c70382bcbfe3de98b1ae Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 3 Oct 2024 23:50:16 -0400 Subject: [PATCH 028/119] rename termEndTs -> termEndDateTs --- contracts/DecentHats_0_1_0.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index be3684ad..ced33ddc 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -28,7 +28,7 @@ contract DecentHats_0_1_0 { } struct TermedParams { - uint128 termEndTs; + uint128 termEndDateTs; address[] nominatedWearers; } @@ -172,7 +172,7 @@ contract DecentHats_0_1_0 { hatsModuleFactory, hatsElectionEligibilityImplementation, hatId, - hat.termedParams.termEndTs, + hat.termedParams.termEndDateTs, hat.termedParams.nominatedWearers, abi.encodePacked(topHatId), uint256(salt) @@ -265,7 +265,7 @@ contract DecentHats_0_1_0 { IHatsModuleFactory hatsModuleFactory, address hatsElectionEligibilityImplementation, uint256 hatId, - uint128 termEndTs, + uint128 termEndDateTs, address[] memory nominatedWearer, bytes memory otherImmutableArgs, uint256 saltNonce @@ -274,13 +274,13 @@ contract DecentHats_0_1_0 { hatsElectionEligibilityImplementation, hatId, otherImmutableArgs, - abi.encode(termEndTs), + abi.encode(termEndDateTs), saltNonce ); hatsProtocol.changeHatEligibility(hatId, electionModuleAddress); IHatsElectionEligibility(electionModuleAddress).elect( - termEndTs, + termEndDateTs, nominatedWearer ); IHatsElectionEligibility(electionModuleAddress).startNextTerm(); From 69641f5f860f754000cb536a5ebffc4a8f9fba0f Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 3 Oct 2024 23:50:21 -0400 Subject: [PATCH 029/119] Update tests to support changes --- test/DecentAutonomousAdmin.test.ts | 20 +++--------- test/DecentHats_0_1_0.test.ts | 51 +++++++++++------------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index 68fc2ec3..7dcadc64 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -31,8 +31,7 @@ describe("DecentAutonomousAdminHat", function () { beforeEach(async function () { // Get signers - ;[deployer, currentWearer, nominatedWearer, randomUser] = - await hre.ethers.getSigners() + ;[deployer, currentWearer, nominatedWearer, randomUser] = await hre.ethers.getSigners() moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() // Deploy MockHatsAutoAdmin (Mock Hats Protocol) @@ -55,23 +54,14 @@ describe("DecentAutonomousAdminHat", function () { const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0] // Deploy DecentAutonomousAdminHat contract with the admin hat ID - adminHatMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy("TEST") - const proxyDeployTx = await moduleProxyFactory.deployModule( - await adminHatMasterCopy.getAddress(), - adminHatMasterCopy.interface.encodeFunctionData("setUp", [adminHatId]), - 1n - ) - const proxyDeployTxReceipt = await proxyDeployTx.wait() - const proxyAddress = await proxyDeployTxReceipt?.toJSON().logs[0].args[0] - - adminHat = DecentAutonomousAdmin__factory.connect(proxyAddress, deployer) - + adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy("TEST", adminHatId) + const adminHatAddress = await adminHat.getAddress() // Mint the admin hat to adminHatWearer - await hatsProtocol.mintHat(adminHatId, proxyAddress) + await hatsProtocol.mintHat(adminHatId, adminHatAddress) // Create User Hat under the admin hat const createUserTx = await hatsProtocol.createHat( - proxyAddress, // Admin address (adminHat contract) + adminHatAddress, // Admin address (adminHat contract) "Details", // Hat details 100, // Max supply await hatsElectionModule.getAddress(), // Eligibility module (election module) diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index 5023075c..e93af2f8 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -15,8 +15,7 @@ import { MockSablierV2LockupLinear, MockERC20__factory, MockERC20, - DecentAutonomousAdmin__factory, - ModuleProxyFactory__factory, + MockHatsModuleFactory__factory, MockHatsElectionEligibility__factory, } from "../typechain-types" @@ -85,9 +84,6 @@ describe("DecentHats_0_1_0", () => { let gnosisSafeAddress: string let erc6551Registry: ERC6551Registry - let adminHatMasterCopyAddress: string - let moduleProxyFactoryAddress: string - let mockHatsAccountImplementation: MockHatsAccount let mockHatsAccountImplementationAddress: string @@ -111,7 +107,7 @@ describe("DecentHats_0_1_0", () => { await new MockHatsElectionEligibility__factory(deployer).deploy() mockHatsElectionEligibilityImplementationAddress = await mockHatsElectionEligibilityImplementation.getAddress() - const mockHatsModuleFactory = await new ModuleProxyFactory__factory(deployer).deploy() + const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy() mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress() keyValuePairs = await new KeyValuePairs__factory(deployer).deploy() erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() @@ -120,11 +116,6 @@ describe("DecentHats_0_1_0", () => { decentHats = await new DecentHats_0_1_0__factory(deployer).deploy() decentHatsAddress = await decentHats.getAddress() - const moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() - moduleProxyFactoryAddress = await moduleProxyFactory.getAddress() - const adminHatMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy("TEST") - adminHatMasterCopyAddress = await adminHatMasterCopy.getAddress() - const gnosisSafeProxyFactory = getGnosisSafeProxyFactory() const gnosisSafeL2Singleton = getGnosisSafeL2Singleton() const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress() @@ -209,8 +200,6 @@ describe("DecentHats_0_1_0", () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - moduleProxyFactory: moduleProxyFactoryAddress, - decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, topHatDetails: "", topHatImageURI: "", adminHat: { @@ -222,7 +211,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, @@ -236,7 +225,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, @@ -249,13 +238,14 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -294,8 +284,6 @@ describe("DecentHats_0_1_0", () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - moduleProxyFactory: moduleProxyFactoryAddress, - decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, topHatDetails: "", topHatImageURI: "", adminHat: { @@ -307,13 +295,14 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, hats: [], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -395,8 +384,6 @@ describe("DecentHats_0_1_0", () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - moduleProxyFactory: moduleProxyFactoryAddress, - decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, topHatDetails: "", topHatImageURI: "", adminHat: { @@ -408,7 +395,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, @@ -437,7 +424,7 @@ describe("DecentHats_0_1_0", () => { ], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, @@ -450,13 +437,14 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -530,8 +518,6 @@ describe("DecentHats_0_1_0", () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - moduleProxyFactory: moduleProxyFactoryAddress, - decentAutonomousAdminMasterCopy: adminHatMasterCopyAddress, topHatDetails: "", topHatImageURI: "", adminHat: { @@ -543,7 +529,7 @@ describe("DecentHats_0_1_0", () => { sablierParams: [], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, @@ -586,13 +572,14 @@ describe("DecentHats_0_1_0", () => { ], isTermed: false, termedParams: { - termEndTs: 0, + termEndDateTs: 0, nominatedWearers: [], }, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, }, ] ), From bf0fa435ef3e1f44515b81aee7e5ff80f68b4250 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:52:42 -0400 Subject: [PATCH 030/119] revert DecentHats_0_1_0 contract --- contracts/DecentHats_0_1_0.sol | 93 ++-------------------------------- 1 file changed, 4 insertions(+), 89 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index ced33ddc..5d29a42e 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -9,9 +9,6 @@ import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; import {IHats} from "./interfaces/hats/IHats.sol"; import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; -import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; -import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; -import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; contract DecentHats_0_1_0 { string public constant NAME = "DecentHats_0_1_0"; @@ -27,20 +24,13 @@ contract DecentHats_0_1_0 { LockupLinear.Broker broker; } - struct TermedParams { - uint128 termEndDateTs; - address[] nominatedWearers; - } - struct Hat { uint32 maxSupply; string details; string imageURI; bool isMutable; address wearer; - bool isTermed; SablierStreamParams[] sablierParams; // Optional Sablier stream parameters - TermedParams termedParams; // Optional termed parameters } struct CreateTreeParams { @@ -52,8 +42,6 @@ contract DecentHats_0_1_0 { string topHatImageURI; Hat adminHat; Hat[] hats; - IHatsModuleFactory hatsModuleFactory; - address hatsElectionEligibilityImplementation; } function getSalt() internal view returns (bytes32 salt) { @@ -103,7 +91,6 @@ contract DecentHats_0_1_0 { adminHatId, _hat.details, _hat.maxSupply, - // ? @todo should be this be dead HATS address? topHatAccount, topHatAccount, _hat.isMutable, @@ -158,27 +145,10 @@ contract DecentHats_0_1_0 { address topHatAccount, IERC6551Registry registry, address hatsAccountImplementation, - bytes32 salt, - IHatsModuleFactory hatsModuleFactory, - address hatsElectionEligibilityImplementation, - uint256 topHatId + bytes32 salt ) internal returns (uint256 hatId, address accountAddress) { hatId = createHat(hatsProtocol, adminHatId, hat, topHatAccount); - if (hat.isTermed) { - // Create election module and set as eligiblity, elect, and start next term - createElectionModuleAndExecuteFirstTerm( - hatsProtocol, - hatsModuleFactory, - hatsElectionEligibilityImplementation, - hatId, - hat.termedParams.termEndDateTs, - hat.termedParams.nominatedWearers, - abi.encodePacked(topHatId), - uint256(salt) - ); - } - accountAddress = createAccount( registry, hatsAccountImplementation, @@ -235,58 +205,6 @@ contract DecentHats_0_1_0 { } } - function createAdminHatAndAccount( - IHats hatsProtocol, - uint256 adminHatId, - Hat calldata hat, - address topHatAccount, - IERC6551Registry registry, - address hatsAccountImplementation, - bytes32 salt - ) internal returns (uint256 hatId, address accountAddress) { - hatId = createHat(hatsProtocol, adminHatId, hat, topHatAccount); - - accountAddress = createAccount( - registry, - hatsAccountImplementation, - salt, - address(hatsProtocol), - hatId - ); - - hatsProtocol.mintHat( - hatId, - address(new DecentAutonomousAdmin("0_0_1", adminHatId)) - ); - } - - function createElectionModuleAndExecuteFirstTerm( - IHats hatsProtocol, - IHatsModuleFactory hatsModuleFactory, - address hatsElectionEligibilityImplementation, - uint256 hatId, - uint128 termEndDateTs, - address[] memory nominatedWearer, - bytes memory otherImmutableArgs, - uint256 saltNonce - ) internal returns (address) { - address electionModuleAddress = hatsModuleFactory.createHatsModule( - hatsElectionEligibilityImplementation, - hatId, - otherImmutableArgs, - abi.encode(termEndDateTs), - saltNonce - ); - hatsProtocol.changeHatEligibility(hatId, electionModuleAddress); - - IHatsElectionEligibility(electionModuleAddress).elect( - termEndDateTs, - nominatedWearer - ); - IHatsElectionEligibility(electionModuleAddress).startNextTerm(); - return electionModuleAddress; - } - function createAndDeclareTree(CreateTreeParams calldata params) public { bytes32 salt = getSalt(); @@ -301,7 +219,7 @@ contract DecentHats_0_1_0 { updateKeyValuePairs(params.keyValuePairs, topHatId); - (uint256 adminHatId, ) = createAdminHatAndAccount( + (uint256 adminHatId, ) = createHatAndAccountAndMintAndStreams( params.hatsProtocol, topHatId, params.adminHat, @@ -319,10 +237,7 @@ contract DecentHats_0_1_0 { topHatAccount, params.registry, params.hatsAccountImplementation, - salt, - params.hatsModuleFactory, - params.hatsElectionEligibilityImplementation, - topHatId + salt ); unchecked { @@ -332,4 +247,4 @@ contract DecentHats_0_1_0 { params.hatsProtocol.transferHat(topHatId, address(this), msg.sender); } -} +} \ No newline at end of file From b4722373748bb0797d7d8925d19b0d4e43fe05b3 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:53:27 -0400 Subject: [PATCH 031/119] remove versioning from constructor --- contracts/DecentAutonomousAdmin.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index f228f346..ce23043a 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -6,7 +6,8 @@ import "./interfaces/sablier/ISablierV2LockupLinear.sol"; contract DecentAutonomousAdmin { string public constant NAME = "DecentAutonomousAdmin"; - string public version_; + string public version_ = "0.1.0"; + uint256 public adminHatId; struct SablierStreamInfo { @@ -24,8 +25,7 @@ contract DecentAutonomousAdmin { // ////////////////////////////////////////////////////////////// // Constructor // ////////////////////////////////////////////////////////////// - constructor(string memory _version, uint256 _adminHatId) { - version_ = _version; + constructor(uint256 _adminHatId) { adminHatId = _adminHatId; } From 6c83f8c5a16ab6291d9dde769183b51a4dadbd7a Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:56:39 -0400 Subject: [PATCH 032/119] remove unneeded deploy file --- deploy/core/018_deploy_DecentAutonomousAdmin.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 deploy/core/018_deploy_DecentAutonomousAdmin.ts diff --git a/deploy/core/018_deploy_DecentAutonomousAdmin.ts b/deploy/core/018_deploy_DecentAutonomousAdmin.ts deleted file mode 100644 index cbdb509d..00000000 --- a/deploy/core/018_deploy_DecentAutonomousAdmin.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; -import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; - -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentAutonomousAdmin", ["0.1.0"]); -}; - -export default func; From be65d3bc941778acb1d705b553e84f8d4cd7fc1b Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:59:06 -0400 Subject: [PATCH 033/119] add DecentHats_0_2_0 contract and deployment script --- contracts/DecentHats_0_2_0.sol | 355 +++++++++++++++++++++ deploy/core/018_deploy_DecentHats_0_2_0.ts | 9 + 2 files changed, 364 insertions(+) create mode 100644 contracts/DecentHats_0_2_0.sol create mode 100644 deploy/core/018_deploy_DecentHats_0_2_0.ts diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol new file mode 100644 index 00000000..3d353fd4 --- /dev/null +++ b/contracts/DecentHats_0_2_0.sol @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.19; + +import {Enum} from "@gnosis.pm/safe-contracts/contracts/common/Enum.sol"; +import {IAvatar} from "@gnosis.pm/zodiac/contracts/interfaces/IAvatar.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; +import {IHats} from "./interfaces/hats/IHats.sol"; +import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; +import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; +import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; +import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; +import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; + +contract DecentHats_0_2_0 { + string public constant NAME = "DecentHats_0_2_0"; + + struct SablierStreamParams { + ISablierV2LockupLinear sablier; + address sender; + uint128 totalAmount; + address asset; + bool cancelable; + bool transferable; + LockupLinear.Timestamps timestamps; + LockupLinear.Broker broker; + } + + struct TermedParams { + uint128 termEndDateTs; + address[] nominatedWearers; + } + + struct Hat { + uint32 maxSupply; + string details; + string imageURI; + bool isMutable; + address wearer; + bool isTermed; + SablierStreamParams[] sablierParams; // Optional Sablier stream parameters + TermedParams[] termedParams; // Optional termed parameters + } + + struct CreateTreeParams { + IHats hatsProtocol; + address hatsAccountImplementation; + IERC6551Registry registry; + address keyValuePairs; + string topHatDetails; + string topHatImageURI; + Hat adminHat; + Hat[] hats; + IHatsModuleFactory hatsModuleFactory; + address hatsElectionEligibilityImplementation; + } + + /* ///////////////////////////////////////////////////////////////////////////// + EXTERNAL FUNCTIONS + ///////////////////////////////////////////////////////////////////////////// */ + function createAndDeclareTree(CreateTreeParams calldata params) public { + bytes32 salt = _getSalt(); + + (uint256 topHatId, address topHatAccount) = _createTopHatAndAccount( + params.hatsProtocol, + params.topHatDetails, + params.topHatImageURI, + params.registry, + params.hatsAccountImplementation, + salt + ); + + _updateKeyValuePairs(params.keyValuePairs, topHatId); + + (uint256 adminHatId, ) = _createAdminHatAndAccount( + params.hatsProtocol, + topHatId, + params.adminHat, + topHatAccount, + params.registry, + params.hatsAccountImplementation, + salt + ); + + for (uint256 i = 0; i < params.hats.length; ) { + (uint256 hatId, ) = _createHatAndAccountAndMintAndStreams( + params.hatsProtocol, + params.registry, + topHatAccount, + params.hatsAccountImplementation, + adminHatId, + params.hats[i], + salt + ); + + if (params.hats[i].isTermed) { + // Create election module and set as eligiblity, elect, and start next term + _createElectionModuleAndExecuteFirstTerm( + params.hatsProtocol, + params.hatsModuleFactory, + params.hatsElectionEligibilityImplementation, + hatId, + topHatId, + params.hats[i].termedParams[0], + uint256(keccak256(abi.encode(salt, hatId))) + ); + } + + unchecked { + ++i; + } + } + + params.hatsProtocol.transferHat(topHatId, address(this), msg.sender); + } + + /* ///////////////////////////////////////////////////////////////////////////// + INTERAL FUNCTIONS + ///////////////////////////////////////////////////////////////////////////// */ + + function _getSalt() internal view returns (bytes32 salt) { + uint256 chainId; + assembly { + chainId := chainid() + } + + bytes memory concatenatedSaltInput = abi.encodePacked( + NAME, + chainId, + address(this) + ); + + salt = keccak256(concatenatedSaltInput); + } + + function _updateKeyValuePairs( + address _keyValuePairs, + uint256 topHatId + ) internal { + string[] memory keys = new string[](1); + string[] memory values = new string[](1); + keys[0] = "topHatId"; + values[0] = Strings.toString(topHatId); + + IAvatar(msg.sender).execTransactionFromModule( + _keyValuePairs, + 0, + abi.encodeWithSignature( + "updateValues(string[],string[])", + keys, + values + ), + Enum.Operation.Call + ); + } + + function _createHat( + IHats _hatsProtocol, + uint256 adminHatId, + Hat memory _hat, + address topHatAccount + ) internal returns (uint256) { + return + _hatsProtocol.createHat( + adminHatId, + _hat.details, + _hat.maxSupply, + topHatAccount, + topHatAccount, + _hat.isMutable, + _hat.imageURI + ); + } + + function _createAccount( + IERC6551Registry _registry, + address _hatsAccountImplementation, + bytes32 salt, + address protocolAddress, + uint256 hatId + ) internal returns (address) { + return + _registry.createAccount( + _hatsAccountImplementation, + salt, + block.chainid, + protocolAddress, + hatId + ); + } + + function _createTopHatAndAccount( + IHats _hatsProtocol, + string memory _topHatDetails, + string memory _topHatImageURI, + IERC6551Registry _registry, + address _hatsAccountImplementation, + bytes32 salt + ) internal returns (uint256 topHatId, address topHatAccount) { + topHatId = _hatsProtocol.mintTopHat( + address(this), + _topHatDetails, + _topHatImageURI + ); + + topHatAccount = _createAccount( + _registry, + _hatsAccountImplementation, + salt, + address(_hatsProtocol), + topHatId + ); + } + + function _createHatAndAccountAndMintAndStreams( + IHats hatsProtocol, + IERC6551Registry registry, + address topHatAccount, + address hatsAccountImplementation, + uint256 adminHatId, + Hat calldata hat, + bytes32 salt + ) internal returns (uint256 hatId, address accountAddress) { + hatId = _createHat(hatsProtocol, adminHatId, hat, topHatAccount); + + accountAddress = _createAccount( + registry, + hatsAccountImplementation, + salt, + address(hatsProtocol), + hatId + ); + if (hat.wearer != address(0)) { + hatsProtocol.mintHat(hatId, hat.wearer); + } + + for (uint256 i = 0; i < hat.sablierParams.length; ) { + SablierStreamParams memory sablierParams = hat.sablierParams[i]; + + // Approve tokens for Sablier + IAvatar(msg.sender).execTransactionFromModule( + sablierParams.asset, + 0, + abi.encodeWithSignature( + "approve(address,uint256)", + address(sablierParams.sablier), + sablierParams.totalAmount + ), + Enum.Operation.Call + ); + + LockupLinear.CreateWithTimestamps memory params = LockupLinear + .CreateWithTimestamps({ + sender: sablierParams.sender, + recipient: accountAddress, + totalAmount: sablierParams.totalAmount, + asset: IERC20(sablierParams.asset), + cancelable: sablierParams.cancelable, + transferable: sablierParams.transferable, + timestamps: sablierParams.timestamps, + broker: sablierParams.broker + }); + + // Proxy the Sablier call through IAvatar + IAvatar(msg.sender).execTransactionFromModule( + address(sablierParams.sablier), + 0, + abi.encodeWithSignature( + "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", + params + ), + Enum.Operation.Call + ); + + unchecked { + ++i; + } + } + } + + function _createAdminHatAndAccount( + IHats hatsProtocol, + uint256 topHatId, + Hat calldata hat, + address topHatAccount, + IERC6551Registry registry, + address hatsAccountImplementation, + bytes32 salt + ) internal returns (uint256 adminHatId, address accountAddress) { + adminHatId = _createHat(hatsProtocol, topHatId, hat, topHatAccount); + + accountAddress = _createAccount( + registry, + hatsAccountImplementation, + salt, + address(hatsProtocol), + adminHatId + ); + address adminInstance = _deployDecentAutonomousAdmin(salt, topHatId); + hatsProtocol.mintHat(adminHatId, adminInstance); + } + + function _deployDecentAutonomousAdmin( + bytes32 salt, + uint256 _adminHatId + ) internal returns (address deployedAddress) { + bytes memory creationCode = _getCreationCode(_adminHatId); + + assembly { + deployedAddress := create2( + 0, + add(creationCode, 0x20), + mload(creationCode), + salt + ) + } + + require(deployedAddress != address(0), "CREATE2: Failed on deploy"); + } + + function _getCreationCode( + uint256 _adminHatId + ) internal pure returns (bytes memory) { + bytes memory bytecode = type(DecentAutonomousAdmin).creationCode; + bytes memory constructorArgs = abi.encode(_adminHatId); + return abi.encodePacked(bytecode, constructorArgs); + } + + function _createElectionModuleAndExecuteFirstTerm( + IHats hatsProtocol, + IHatsModuleFactory hatsModuleFactory, + address hatsElectionEligibilityImplementation, + uint256 hatId, + uint256 topHatId, + TermedParams calldata termedParams, + uint256 saltNonce + ) internal returns (address) { + address electionModuleAddress = hatsModuleFactory.createHatsModule( + hatsElectionEligibilityImplementation, + hatId, + abi.encode(topHatId, uint256(0)), + abi.encode(termedParams.termEndDateTs), + saltNonce + ); + hatsProtocol.changeHatEligibility(hatId, electionModuleAddress); + + IHatsElectionEligibility(electionModuleAddress).elect( + termedParams.termEndDateTs, + termedParams.nominatedWearers + ); + + return electionModuleAddress; + } +} diff --git a/deploy/core/018_deploy_DecentHats_0_2_0.ts b/deploy/core/018_deploy_DecentHats_0_2_0.ts new file mode 100644 index 00000000..9122e65e --- /dev/null +++ b/deploy/core/018_deploy_DecentHats_0_2_0.ts @@ -0,0 +1,9 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + await deployNonUpgradeable(hre, "DecentHats_0_2_0", ["0.2.0"]); +}; + +export default func; From 558b3b72f945887aafcb6c48c59be6943587b56f Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:06:42 -0400 Subject: [PATCH 034/119] reorder structs for gas efficiency --- contracts/DecentHats_0_2_0.sol | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index 3d353fd4..7286aa80 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -19,12 +19,12 @@ contract DecentHats_0_2_0 { struct SablierStreamParams { ISablierV2LockupLinear sablier; address sender; - uint128 totalAmount; address asset; - bool cancelable; - bool transferable; LockupLinear.Timestamps timestamps; LockupLinear.Broker broker; + uint128 totalAmount; + bool cancelable; + bool transferable; } struct TermedParams { @@ -33,27 +33,27 @@ contract DecentHats_0_2_0 { } struct Hat { - uint32 maxSupply; + address wearer; string details; string imageURI; + SablierStreamParams[] sablierParams; + TermedParams[] termedParams; + uint32 maxSupply; bool isMutable; - address wearer; bool isTermed; - SablierStreamParams[] sablierParams; // Optional Sablier stream parameters - TermedParams[] termedParams; // Optional termed parameters } struct CreateTreeParams { IHats hatsProtocol; - address hatsAccountImplementation; IERC6551Registry registry; + IHatsModuleFactory hatsModuleFactory; + address hatsAccountImplementation; address keyValuePairs; - string topHatDetails; - string topHatImageURI; + address hatsElectionEligibilityImplementation; Hat adminHat; Hat[] hats; - IHatsModuleFactory hatsModuleFactory; - address hatsElectionEligibilityImplementation; + string topHatDetails; + string topHatImageURI; } /* ///////////////////////////////////////////////////////////////////////////// From 26d843fbb95f7600960313aaa59c1af83a6c28b4 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:19:01 -0400 Subject: [PATCH 035/119] revert 0_1_0 test and add 0_2_0 tests --- test/DecentHats_0_1_0.test.ts | 68 ---- test/DecentHats_0_2_0.test.ts | 656 ++++++++++++++++++++++++++++++++++ 2 files changed, 656 insertions(+), 68 deletions(-) create mode 100644 test/DecentHats_0_2_0.test.ts diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index e93af2f8..c9a8352d 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -15,8 +15,6 @@ import { MockSablierV2LockupLinear, MockERC20__factory, MockERC20, - MockHatsModuleFactory__factory, - MockHatsElectionEligibility__factory, } from "../typechain-types" import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" @@ -93,9 +91,6 @@ describe("DecentHats_0_1_0", () => { let mockERC20: MockERC20 let mockERC20Address: string - let mockHatsElectionEligibilityImplementationAddress: string - let mockHatsModuleFactoryAddress: string - beforeEach(async () => { const signers = await hre.ethers.getSigners() const [deployer] = signers @@ -103,12 +98,6 @@ describe("DecentHats_0_1_0", () => { mockHats = await new MockHats__factory(deployer).deploy() mockHatsAddress = await mockHats.getAddress() - const mockHatsElectionEligibilityImplementation = - await new MockHatsElectionEligibility__factory(deployer).deploy() - mockHatsElectionEligibilityImplementationAddress = - await mockHatsElectionEligibilityImplementation.getAddress() - const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy() - mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress() keyValuePairs = await new KeyValuePairs__factory(deployer).deploy() erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy() @@ -209,11 +198,6 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, hats: [ { @@ -223,11 +207,6 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, { maxSupply: 1, @@ -236,16 +215,8 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, ], - hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -293,16 +264,8 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, hats: [], - hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -393,11 +356,6 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, hats: [ { @@ -422,11 +380,6 @@ describe("DecentHats_0_1_0", () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, { maxSupply: 1, @@ -435,16 +388,8 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, ], - hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, }, ] ), @@ -527,11 +472,6 @@ describe("DecentHats_0_1_0", () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, hats: [ { @@ -570,16 +510,8 @@ describe("DecentHats_0_1_0", () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], - isTermed: false, - termedParams: { - termEndDateTs: 0, - nominatedWearers: [], - }, }, ], - hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, }, ] ), diff --git a/test/DecentHats_0_2_0.test.ts b/test/DecentHats_0_2_0.test.ts new file mode 100644 index 00000000..fb530edb --- /dev/null +++ b/test/DecentHats_0_2_0.test.ts @@ -0,0 +1,656 @@ +import { + GnosisSafeL2, + GnosisSafeL2__factory, + DecentHats_0_2_0__factory, + KeyValuePairs, + KeyValuePairs__factory, + MockHats__factory, + ERC6551Registry__factory, + MockHatsAccount__factory, + ERC6551Registry, + DecentHats_0_2_0, + MockHatsAccount, + MockHats, + MockSablierV2LockupLinear__factory, + MockSablierV2LockupLinear, + MockERC20__factory, + MockERC20, + MockHatsModuleFactory__factory, + MockHatsElectionEligibility__factory, +} from "../typechain-types" + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" +import { expect } from "chai" +import { ethers, solidityPackedKeccak256 } from "ethers" +import hre from "hardhat" + +import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from "./GlobalSafeDeployments.test" +import { + buildSafeTransaction, + buildSignatureBytes, + predictGnosisSafeAddress, + safeSignTypedData, +} from "./helpers" + +const executeSafeTransaction = async ({ + safe, + to, + transactionData, + signers, +}: { + safe: GnosisSafeL2 + to: string + transactionData: string + signers: SignerWithAddress[] +}) => { + const safeTx = buildSafeTransaction({ + to, + data: transactionData, + nonce: await safe.nonce(), + }) + + const sigs = await Promise.all( + signers.map(async (signer) => await safeSignTypedData(signer, safe, safeTx)) + ) + + const tx = await safe.execTransaction( + safeTx.to, + safeTx.value, + safeTx.data, + safeTx.operation, + safeTx.safeTxGas, + safeTx.baseGas, + safeTx.gasPrice, + safeTx.gasToken, + safeTx.refundReceiver, + buildSignatureBytes(sigs) + ) + + return tx +} + +describe("DecentHats_0_2_0", () => { + let dao: SignerWithAddress + + let mockHats: MockHats + let mockHatsAddress: string + + let keyValuePairs: KeyValuePairs + let gnosisSafe: GnosisSafeL2 + + let decentHats: DecentHats_0_2_0 + let decentHatsAddress: string + + let gnosisSafeAddress: string + let erc6551Registry: ERC6551Registry + + let mockHatsAccountImplementation: MockHatsAccount + let mockHatsAccountImplementationAddress: string + + let mockSablier: MockSablierV2LockupLinear + let mockSablierAddress: string + + let mockERC20: MockERC20 + let mockERC20Address: string + + let mockHatsElectionEligibilityImplementationAddress: string + let mockHatsModuleFactoryAddress: string + + beforeEach(async () => { + const signers = await hre.ethers.getSigners() + const [deployer] = signers + ;[, dao] = signers + + mockHats = await new MockHats__factory(deployer).deploy() + mockHatsAddress = await mockHats.getAddress() + const mockHatsElectionEligibilityImplementation = + await new MockHatsElectionEligibility__factory(deployer).deploy() + mockHatsElectionEligibilityImplementationAddress = + await mockHatsElectionEligibilityImplementation.getAddress() + const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy() + mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress() + keyValuePairs = await new KeyValuePairs__factory(deployer).deploy() + erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() + mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy() + mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress() + decentHats = await new DecentHats_0_2_0__factory(deployer).deploy() + decentHatsAddress = await decentHats.getAddress() + + const gnosisSafeProxyFactory = getGnosisSafeProxyFactory() + const gnosisSafeL2Singleton = getGnosisSafeL2Singleton() + const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress() + + const createGnosisSetupCalldata = GnosisSafeL2__factory.createInterface().encodeFunctionData( + "setup", + [ + [dao.address], + 1, + hre.ethers.ZeroAddress, + hre.ethers.ZeroHash, + hre.ethers.ZeroAddress, + hre.ethers.ZeroAddress, + 0, + hre.ethers.ZeroAddress, + ] + ) + + const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString("hex")}`) + + const predictedGnosisSafeAddress = await predictGnosisSafeAddress( + createGnosisSetupCalldata, + saltNum, + gnosisSafeL2SingletonAddress, + gnosisSafeProxyFactory + ) + gnosisSafeAddress = predictedGnosisSafeAddress + + await gnosisSafeProxyFactory.createProxyWithNonce( + gnosisSafeL2SingletonAddress, + createGnosisSetupCalldata, + saltNum + ) + + gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer) + + // Deploy MockSablierV2LockupLinear + mockSablier = await new MockSablierV2LockupLinear__factory(deployer).deploy() + mockSablierAddress = await mockSablier.getAddress() + + mockERC20 = await new MockERC20__factory(deployer).deploy("MockERC20", "MCK") + mockERC20Address = await mockERC20.getAddress() + + await mockERC20.mint(gnosisSafeAddress, ethers.parseEther("1000000")) + }) + + describe("DecentHats as a Module", () => { + let enableModuleTx: ethers.ContractTransactionResponse + + beforeEach(async () => { + enableModuleTx = await executeSafeTransaction({ + safe: gnosisSafe, + to: gnosisSafeAddress, + transactionData: GnosisSafeL2__factory.createInterface().encodeFunctionData( + "enableModule", + [decentHatsAddress] + ), + signers: [dao], + }) + }) + + it("Emits an ExecutionSuccess event", async () => { + await expect(enableModuleTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) + + it("Emits an EnabledModule event", async () => { + await expect(enableModuleTx).to.emit(gnosisSafe, "EnabledModule").withArgs(decentHatsAddress) + }) + + describe("Creating a new Top Hat and Tree", () => { + let createAndDeclareTreeTx: ethers.ContractTransactionResponse + + beforeEach(async () => { + createAndDeclareTreeTx = await executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + hats: [ + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + ], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + }, + ] + ), + signers: [dao], + }) + }) + + it("Emits an ExecutionSuccess event", async () => { + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) + + it("Emits an ExecutionFromModuleSuccess event", async () => { + await expect(createAndDeclareTreeTx) + .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") + .withArgs(decentHatsAddress) + }) + + it("Emits some hatsTreeId ValueUpdated events", async () => { + await expect(createAndDeclareTreeTx) + .to.emit(keyValuePairs, "ValueUpdated") + .withArgs(gnosisSafeAddress, "topHatId", "0") + }) + + describe("Multiple calls", () => { + let createAndDeclareTreeTx2: ethers.ContractTransactionResponse + + beforeEach(async () => { + createAndDeclareTreeTx2 = await executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + hats: [], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + }, + ] + ), + signers: [dao], + }) + }) + + it("Emits an ExecutionSuccess event", async () => { + await expect(createAndDeclareTreeTx2).to.emit(gnosisSafe, "ExecutionSuccess") + }) + + it("Emits an ExecutionFromModuleSuccess event", async () => { + await expect(createAndDeclareTreeTx2) + .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") + .withArgs(decentHatsAddress) + }) + + it("Creates Top Hats with sequential IDs", async () => { + await expect(createAndDeclareTreeTx2) + .to.emit(keyValuePairs, "ValueUpdated") + .withArgs(gnosisSafeAddress, "topHatId", "4") + }) + }) + + describe("Creating Hats Accounts", () => { + let salt: string + + beforeEach(async () => { + salt = solidityPackedKeccak256( + ["string", "uint256", "address"], + ["DecentHats_0_2_0", await hre.getChainId(), decentHatsAddress] + ) + }) + + const getHatAccount = async (hatId: bigint) => { + const hatAccountAddress = await erc6551Registry.account( + mockHatsAccountImplementationAddress, + salt, + await hre.getChainId(), + mockHatsAddress, + hatId + ) + + const hatAccount = MockHatsAccount__factory.connect( + hatAccountAddress, + hre.ethers.provider + ) + + return hatAccount + } + + it("Generates the correct Addresses for the current Hats", async () => { + const currentCount = await mockHats.count() + + for (let i = 0n; i < currentCount; i++) { + const topHatAccount = await getHatAccount(i) + expect(await topHatAccount.tokenId()).eq(i) + expect(await topHatAccount.tokenImplementation()).eq(mockHatsAddress) + } + }) + }) + }) + + describe("Creating a new Top Hat and Tree with Sablier Streams", () => { + let createAndDeclareTreeTx: ethers.ContractTransactionResponse + let currentBlockTimestamp: number + + beforeEach(async () => { + currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp + + createAndDeclareTreeTx = await executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + hats: [ + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("100"), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: 0, + end: currentBlockTimestamp + 2592000, // 30 days from now + }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + ], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + }, + ] + ), + signers: [dao], + }) + }) + + it("Emits an ExecutionSuccess event", async () => { + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) + + it("Emits an ExecutionFromModuleSuccess event", async () => { + await expect(createAndDeclareTreeTx) + .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") + .withArgs(decentHatsAddress) + }) + + it("Emits some hatsTreeId ValueUpdated events", async () => { + await expect(createAndDeclareTreeTx) + .to.emit(keyValuePairs, "ValueUpdated") + .withArgs(gnosisSafeAddress, "topHatId", "0") + }) + + it("Creates a Sablier stream for the hat with stream parameters", async () => { + const streamCreatedEvents = await mockSablier.queryFilter( + mockSablier.filters.StreamCreated() + ) + expect(streamCreatedEvents.length).to.equal(1) + + const event = streamCreatedEvents[0] + expect(event.args.sender).to.equal(gnosisSafeAddress) + expect(event.args.recipient).to.not.equal(ethers.ZeroAddress) + expect(event.args.totalAmount).to.equal(ethers.parseEther("100")) + }) + + it("Does not create a Sablier stream for hats without stream parameters", async () => { + const streamCreatedEvents = await mockSablier.queryFilter( + mockSablier.filters.StreamCreated() + ) + expect(streamCreatedEvents.length).to.equal(1) // Only one stream should be created + }) + + it("Creates a Sablier stream with correct timestamps", async () => { + const streamCreatedEvents = await mockSablier.queryFilter( + mockSablier.filters.StreamCreated() + ) + expect(streamCreatedEvents.length).to.equal(1) + + const streamId = streamCreatedEvents[0].args.streamId + const stream = await mockSablier.getStream(streamId) + + expect(stream.startTime).to.equal(currentBlockTimestamp) + expect(stream.endTime).to.equal(currentBlockTimestamp + 2592000) + }) + }) + + describe("Creating a new Top Hat and Tree with Multiple Sablier Streams per Hat", () => { + let currentBlockTimestamp: number + + beforeEach(async () => { + currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp + + await executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + hats: [ + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("100"), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: currentBlockTimestamp + 86400, // 1 day cliff + end: currentBlockTimestamp + 2592000, // 30 days from now + }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("50"), + asset: mockERC20Address, + cancelable: false, + transferable: true, + timestamps: { + start: currentBlockTimestamp, + cliff: 0, // No cliff + end: currentBlockTimestamp + 1296000, // 15 days from now + }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + isTermed: false, + termedParams: [ + { + termEndDateTs: 0, + nominatedWearers: [], + }, + ], + }, + ], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + }, + ] + ), + signers: [dao], + }) + }) + + it("Creates multiple Sablier streams for a single hat", async () => { + const streamCreatedEvents = await mockSablier.queryFilter( + mockSablier.filters.StreamCreated() + ) + expect(streamCreatedEvents.length).to.equal(2) + + const event1 = streamCreatedEvents[0] + expect(event1.args.sender).to.equal(gnosisSafeAddress) + expect(event1.args.recipient).to.not.equal(ethers.ZeroAddress) + expect(event1.args.totalAmount).to.equal(ethers.parseEther("100")) + + const event2 = streamCreatedEvents[1] + expect(event2.args.sender).to.equal(gnosisSafeAddress) + expect(event2.args.recipient).to.equal(event1.args.recipient) + expect(event2.args.totalAmount).to.equal(ethers.parseEther("50")) + }) + + it("Creates streams with correct parameters", async () => { + const streamCreatedEvents = await mockSablier.queryFilter( + mockSablier.filters.StreamCreated() + ) + + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) + expect(stream1.cancelable).to.be.true + expect(stream1.transferable).to.be.false + expect(stream1.endTime - stream1.startTime).to.equal(2592000) + + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) + expect(stream2.cancelable).to.be.false + expect(stream2.transferable).to.be.true + expect(stream2.endTime - stream2.startTime).to.equal(1296000) + }) + + it("Creates streams with correct timestamps", async () => { + const streamCreatedEvents = await mockSablier.queryFilter( + mockSablier.filters.StreamCreated() + ) + + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) + expect(stream1.startTime).to.equal(currentBlockTimestamp) + expect(stream1.endTime).to.equal(currentBlockTimestamp + 2592000) + + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) + expect(stream2.startTime).to.equal(currentBlockTimestamp) + expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000) + }) + }) + }) +}) From 0d410232bf9389b3d06494954bb3b670e55c6780 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:33:17 -0400 Subject: [PATCH 036/119] update test --- test/DecentAutonomousAdmin.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index 7dcadc64..62082fbb 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -1,4 +1,3 @@ -import { ModuleProxyFactory } from "../typechain-types/@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory" import { DecentAutonomousAdmin, DecentAutonomousAdmin__factory, @@ -6,7 +5,6 @@ import { MockHatsAutoAdmin__factory, MockHatsElectionEligibility, MockHatsElectionEligibility__factory, - ModuleProxyFactory__factory, } from "../typechain-types" import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" import { expect } from "chai" @@ -23,8 +21,6 @@ describe("DecentAutonomousAdminHat", function () { let hatsProtocol: MockHatsAutoAdmin let hatsElectionModule: MockHatsElectionEligibility let adminHat: DecentAutonomousAdmin - let adminHatMasterCopy: DecentAutonomousAdmin - let moduleProxyFactory: ModuleProxyFactory // Variables let userHatId: bigint @@ -33,7 +29,6 @@ describe("DecentAutonomousAdminHat", function () { // Get signers ;[deployer, currentWearer, nominatedWearer, randomUser] = await hre.ethers.getSigners() - moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() // Deploy MockHatsAutoAdmin (Mock Hats Protocol) hatsProtocol = await new MockHatsAutoAdmin__factory(deployer).deploy() @@ -54,7 +49,7 @@ describe("DecentAutonomousAdminHat", function () { const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0] // Deploy DecentAutonomousAdminHat contract with the admin hat ID - adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy("TEST", adminHatId) + adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy(adminHatId) const adminHatAddress = await adminHat.getAddress() // Mint the admin hat to adminHatWearer await hatsProtocol.mintHat(adminHatId, adminHatAddress) From 1bafbf844a258db50bf78e209cbf62e197c065cf Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:46:39 -0400 Subject: [PATCH 037/119] fix deployment script --- deploy/core/018_deploy_DecentHats_0_2_0.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/core/018_deploy_DecentHats_0_2_0.ts b/deploy/core/018_deploy_DecentHats_0_2_0.ts index 9122e65e..66da759a 100644 --- a/deploy/core/018_deploy_DecentHats_0_2_0.ts +++ b/deploy/core/018_deploy_DecentHats_0_2_0.ts @@ -3,7 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types"; import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentHats_0_2_0", ["0.2.0"]); + await deployNonUpgradeable(hre, "DecentHats_0_2_0"); }; export default func; From bf80360d78bc941f6c2dc7f36413ae2005bb9828 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:32:39 -0400 Subject: [PATCH 038/119] use ModuleProxyFactory to deploy AutoAdmin --- contracts/DecentAutonomousAdmin.sol | 8 +-- contracts/DecentHats_0_2_0.sol | 51 +++++++++---------- .../core/019_deploy_DecentAutonomousAdmin.ts | 9 ++++ 3 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 deploy/core/019_deploy_DecentAutonomousAdmin.ts diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index ce23043a..fd7b7cb4 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -8,8 +8,6 @@ contract DecentAutonomousAdmin { string public constant NAME = "DecentAutonomousAdmin"; string public version_ = "0.1.0"; - uint256 public adminHatId; - struct SablierStreamInfo { uint256 streamId; ISablierV2LockupLinear sablierV2LockupLinear; @@ -23,11 +21,9 @@ contract DecentAutonomousAdmin { } // ////////////////////////////////////////////////////////////// - // Constructor + // initializer // ////////////////////////////////////////////////////////////// - constructor(uint256 _adminHatId) { - adminHatId = _adminHatId; - } + function setUp() public {} // ////////////////////////////////////////////////////////////// // Public Functions diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index 7286aa80..58681c53 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -12,6 +12,8 @@ import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; contract DecentHats_0_2_0 { string public constant NAME = "DecentHats_0_2_0"; @@ -47,6 +49,8 @@ contract DecentHats_0_2_0 { IHats hatsProtocol; IERC6551Registry registry; IHatsModuleFactory hatsModuleFactory; + ModuleProxyFactory moduleProxyFactory; + address decentAutonomousAdminMasterCopy; address hatsAccountImplementation; address keyValuePairs; address hatsElectionEligibilityImplementation; @@ -75,11 +79,13 @@ contract DecentHats_0_2_0 { (uint256 adminHatId, ) = _createAdminHatAndAccount( params.hatsProtocol, - topHatId, - params.adminHat, - topHatAccount, params.registry, + params.moduleProxyFactory, + params.decentAutonomousAdminMasterCopy, params.hatsAccountImplementation, + topHatAccount, + topHatId, + params.adminHat, salt ); @@ -138,10 +144,12 @@ contract DecentHats_0_2_0 { address _keyValuePairs, uint256 topHatId ) internal { - string[] memory keys = new string[](1); - string[] memory values = new string[](1); + string[] memory keys = new string[](2); + string[] memory values = new string[](2); keys[0] = "topHatId"; values[0] = Strings.toString(topHatId); + keys[1] = "decentHatsAddress"; + values[1] = Strings.toHexString(address(this)); IAvatar(msg.sender).execTransactionFromModule( _keyValuePairs, @@ -281,11 +289,13 @@ contract DecentHats_0_2_0 { function _createAdminHatAndAccount( IHats hatsProtocol, - uint256 topHatId, - Hat calldata hat, - address topHatAccount, IERC6551Registry registry, + ModuleProxyFactory moduleProxyFactory, + address decentAutonomousAdminMasterCopy, address hatsAccountImplementation, + address topHatAccount, + uint256 topHatId, + Hat calldata hat, bytes32 salt ) internal returns (uint256 adminHatId, address accountAddress) { adminHatId = _createHat(hatsProtocol, topHatId, hat, topHatAccount); @@ -297,26 +307,15 @@ contract DecentHats_0_2_0 { address(hatsProtocol), adminHatId ); - address adminInstance = _deployDecentAutonomousAdmin(salt, topHatId); - hatsProtocol.mintHat(adminHatId, adminInstance); - } - - function _deployDecentAutonomousAdmin( - bytes32 salt, - uint256 _adminHatId - ) internal returns (address deployedAddress) { - bytes memory creationCode = _getCreationCode(_adminHatId); - assembly { - deployedAddress := create2( - 0, - add(creationCode, 0x20), - mload(creationCode), - salt + hatsProtocol.mintHat( + adminHatId, + moduleProxyFactory.deployModule( + decentAutonomousAdminMasterCopy, + abi.encodeWithSignature("setUp()"), + uint256(keccak256(abi.encodePacked(salt, adminHatId))) ) - } - - require(deployedAddress != address(0), "CREATE2: Failed on deploy"); + ); } function _getCreationCode( diff --git a/deploy/core/019_deploy_DecentAutonomousAdmin.ts b/deploy/core/019_deploy_DecentAutonomousAdmin.ts new file mode 100644 index 00000000..f1adb696 --- /dev/null +++ b/deploy/core/019_deploy_DecentAutonomousAdmin.ts @@ -0,0 +1,9 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + await deployNonUpgradeable(hre, "DecentAutonomousAdmin"); +}; + +export default func; From 216e1e018845065d424449c3a1b56500b9855765 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:32:51 -0400 Subject: [PATCH 039/119] update tests to support changes --- test/DecentAutonomousAdmin.test.ts | 2 +- test/DecentHats_0_2_0.test.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index 62082fbb..c9d9093f 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -49,7 +49,7 @@ describe("DecentAutonomousAdminHat", function () { const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0] // Deploy DecentAutonomousAdminHat contract with the admin hat ID - adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy(adminHatId) + adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy() const adminHatAddress = await adminHat.getAddress() // Mint the admin hat to adminHatWearer await hatsProtocol.mintHat(adminHatId, adminHatAddress) diff --git a/test/DecentHats_0_2_0.test.ts b/test/DecentHats_0_2_0.test.ts index fb530edb..e12d2f9e 100644 --- a/test/DecentHats_0_2_0.test.ts +++ b/test/DecentHats_0_2_0.test.ts @@ -17,6 +17,10 @@ import { MockERC20, MockHatsModuleFactory__factory, MockHatsElectionEligibility__factory, + ModuleProxyFactory__factory, + ModuleProxyFactory, + DecentAutonomousAdmin__factory, + DecentAutonomousAdmin, } from "../typechain-types" import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" @@ -96,6 +100,9 @@ describe("DecentHats_0_2_0", () => { let mockHatsElectionEligibilityImplementationAddress: string let mockHatsModuleFactoryAddress: string + let moduleProxyFactory: ModuleProxyFactory + let decentAutonomousAdminMasterCopy: DecentAutonomousAdmin + beforeEach(async () => { const signers = await hre.ethers.getSigners() const [deployer] = signers @@ -116,6 +123,9 @@ describe("DecentHats_0_2_0", () => { decentHats = await new DecentHats_0_2_0__factory(deployer).deploy() decentHatsAddress = await decentHats.getAddress() + moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() + decentAutonomousAdminMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy() + const gnosisSafeProxyFactory = getGnosisSafeProxyFactory() const gnosisSafeL2Singleton = getGnosisSafeL2Singleton() const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress() @@ -202,6 +212,8 @@ describe("DecentHats_0_2_0", () => { keyValuePairs: await keyValuePairs.getAddress(), topHatDetails: "", topHatImageURI: "", + decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, details: "", @@ -292,6 +304,8 @@ describe("DecentHats_0_2_0", () => { keyValuePairs: await keyValuePairs.getAddress(), topHatDetails: "", topHatImageURI: "", + decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, details: "", @@ -394,6 +408,8 @@ describe("DecentHats_0_2_0", () => { keyValuePairs: await keyValuePairs.getAddress(), topHatDetails: "", topHatImageURI: "", + decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, details: "", @@ -534,6 +550,8 @@ describe("DecentHats_0_2_0", () => { keyValuePairs: await keyValuePairs.getAddress(), topHatDetails: "", topHatImageURI: "", + decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, details: "", From a3bc214dded4f2d1b9c5d491e8061a644a275159 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 14:25:01 -0400 Subject: [PATCH 040/119] Downtype "sablier" in SablierStreamParams to an address, and rename it to sablierV2LockupLinear --- contracts/DecentHats_0_2_0.sol | 7 +++---- test/DecentHats_0_2_0.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index 58681c53..d24736b0 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -7,7 +7,6 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; import {IHats} from "./interfaces/hats/IHats.sol"; -import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; @@ -19,7 +18,7 @@ contract DecentHats_0_2_0 { string public constant NAME = "DecentHats_0_2_0"; struct SablierStreamParams { - ISablierV2LockupLinear sablier; + address sablierV2LockupLinear; address sender; address asset; LockupLinear.Timestamps timestamps; @@ -252,7 +251,7 @@ contract DecentHats_0_2_0 { 0, abi.encodeWithSignature( "approve(address,uint256)", - address(sablierParams.sablier), + sablierParams.sablierV2LockupLinear, sablierParams.totalAmount ), Enum.Operation.Call @@ -272,7 +271,7 @@ contract DecentHats_0_2_0 { // Proxy the Sablier call through IAvatar IAvatar(msg.sender).execTransactionFromModule( - address(sablierParams.sablier), + sablierParams.sablierV2LockupLinear, 0, abi.encodeWithSignature( "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", diff --git a/test/DecentHats_0_2_0.test.ts b/test/DecentHats_0_2_0.test.ts index e12d2f9e..0014cc33 100644 --- a/test/DecentHats_0_2_0.test.ts +++ b/test/DecentHats_0_2_0.test.ts @@ -434,7 +434,7 @@ describe("DecentHats_0_2_0", () => { wearer: ethers.ZeroAddress, sablierParams: [ { - sablier: mockSablierAddress, + sablierV2LockupLinear: mockSablierAddress, sender: gnosisSafeAddress, totalAmount: ethers.parseEther("100"), asset: mockERC20Address, @@ -576,7 +576,7 @@ describe("DecentHats_0_2_0", () => { wearer: ethers.ZeroAddress, sablierParams: [ { - sablier: mockSablierAddress, + sablierV2LockupLinear: mockSablierAddress, sender: gnosisSafeAddress, totalAmount: ethers.parseEther("100"), asset: mockERC20Address, @@ -590,7 +590,7 @@ describe("DecentHats_0_2_0", () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, { - sablier: mockSablierAddress, + sablierV2LockupLinear: mockSablierAddress, sender: gnosisSafeAddress, totalAmount: ethers.parseEther("50"), asset: mockERC20Address, From 85543b3494a411bab1a9f1c67748e7052d067195 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 12:21:03 -0400 Subject: [PATCH 041/119] Add PRBMath to package, for Sablier interfaces --- package-lock.json | 7 +++++++ package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2f51a13f..8b4f73ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.0", + "@prb/math": "^4.0.3", "dotenv": "^16.4.5", "hardhat": "^2.22.2", "hardhat-dependency-compiler": "^1.1.4", @@ -1811,6 +1812,12 @@ "integrity": "sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A==", "dev": true }, + "node_modules/@prb/math": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@prb/math/-/math-4.0.3.tgz", + "integrity": "sha512-/RSt3VU1k2m3ox6U6kUL1MrktnAHr8vhydXu4eDtqFAms1gm3XnGpoZIPaK1lm2zdJQmKBwJ4EXALPARsuOlaA==", + "dev": true + }, "node_modules/@scure/base": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", diff --git a/package.json b/package.json index d6620d11..de81f04e 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.0", + "@prb/math": "^4.0.3", "dotenv": "^16.4.5", "hardhat": "^2.22.2", "hardhat-dependency-compiler": "^1.1.4", From 42de8fc9b065548ff7d3e85ef04002ba06cfefa8 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 12:23:14 -0400 Subject: [PATCH 042/119] Added minimum amount of "full" Sablier interfaces needed for DecentSablierStreamManagement, to also be future-proof --- .../interfaces/sablier/full/IAdminable.sol | 41 ++ .../interfaces/sablier/full/IERC4096.sol | 20 + .../sablier/full/ISablierV2Lockup.sol | 395 ++++++++++++++++++ .../sablier/full/ISablierV2NFTDescriptor.sol | 19 + .../sablier/full/types/DataTypes.sol | 374 +++++++++++++++++ 5 files changed, 849 insertions(+) create mode 100644 contracts/interfaces/sablier/full/IAdminable.sol create mode 100644 contracts/interfaces/sablier/full/IERC4096.sol create mode 100644 contracts/interfaces/sablier/full/ISablierV2Lockup.sol create mode 100644 contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol create mode 100644 contracts/interfaces/sablier/full/types/DataTypes.sol diff --git a/contracts/interfaces/sablier/full/IAdminable.sol b/contracts/interfaces/sablier/full/IAdminable.sol new file mode 100644 index 00000000..e5ee0956 --- /dev/null +++ b/contracts/interfaces/sablier/full/IAdminable.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19; + +/// @title IAdminable +/// @notice Contract module that provides a basic access control mechanism, with an admin that can be +/// granted exclusive access to specific functions. The inheriting contract must set the initial admin +/// in the constructor. +interface IAdminable { + /*////////////////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Emitted when the admin is transferred. + /// @param oldAdmin The address of the old admin. + /// @param newAdmin The address of the new admin. + event TransferAdmin(address indexed oldAdmin, address indexed newAdmin); + + /*////////////////////////////////////////////////////////////////////////// + CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice The address of the admin account or contract. + function admin() external view returns (address); + + /*////////////////////////////////////////////////////////////////////////// + NON-CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Transfers the contract admin to a new address. + /// + /// @dev Notes: + /// - Does not revert if the admin is the same. + /// - This function can potentially leave the contract without an admin, thereby removing any + /// functionality that is only available to the admin. + /// + /// Requirements: + /// - `msg.sender` must be the contract admin. + /// + /// @param newAdmin The address of the new admin. + function transferAdmin(address newAdmin) external; +} diff --git a/contracts/interfaces/sablier/full/IERC4096.sol b/contracts/interfaces/sablier/full/IERC4096.sol new file mode 100644 index 00000000..29bcc6ea --- /dev/null +++ b/contracts/interfaces/sablier/full/IERC4096.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4906.sol) + +pragma solidity ^0.8.19; + +import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +/// @title ERC-721 Metadata Update Extension +interface IERC4906 is IERC165, IERC721 { + /// @dev This event emits when the metadata of a token is changed. + /// So that the third-party platforms such as NFT market could + /// timely update the images and related attributes of the NFT. + event MetadataUpdate(uint256 _tokenId); + + /// @dev This event emits when the metadata of a range of tokens is changed. + /// So that the third-party platforms such as NFT market could + /// timely update the images and related attributes of the NFTs. + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); +} diff --git a/contracts/interfaces/sablier/full/ISablierV2Lockup.sol b/contracts/interfaces/sablier/full/ISablierV2Lockup.sol new file mode 100644 index 00000000..8e5b21e3 --- /dev/null +++ b/contracts/interfaces/sablier/full/ISablierV2Lockup.sol @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19; + +import {IERC4906} from "./IERC4096.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import {UD60x18} from "@prb/math/src/UD60x18.sol"; + +import {Lockup} from "./types/DataTypes.sol"; +import {IAdminable} from "./IAdminable.sol"; +import {ISablierV2NFTDescriptor} from "./ISablierV2NFTDescriptor.sol"; + +/// @title ISablierV2Lockup +/// @notice Common logic between all Sablier V2 Lockup contracts. +interface ISablierV2Lockup is + IAdminable, // 0 inherited components + IERC4906, // 2 inherited components + IERC721Metadata // 2 inherited components +{ + /*////////////////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Emitted when the admin allows a new recipient contract to hook to Sablier. + /// @param admin The address of the current contract admin. + /// @param recipient The address of the recipient contract put on the allowlist. + event AllowToHook(address indexed admin, address recipient); + + /// @notice Emitted when a stream is canceled. + /// @param streamId The ID of the stream. + /// @param sender The address of the stream's sender. + /// @param recipient The address of the stream's recipient. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param senderAmount The amount of assets refunded to the stream's sender, denoted in units of the asset's + /// decimals. + /// @param recipientAmount The amount of assets left for the stream's recipient to withdraw, denoted in units of the + /// asset's decimals. + event CancelLockupStream( + uint256 streamId, + address indexed sender, + address indexed recipient, + IERC20 indexed asset, + uint128 senderAmount, + uint128 recipientAmount + ); + + /// @notice Emitted when a sender gives up the right to cancel a stream. + /// @param streamId The ID of the stream. + event RenounceLockupStream(uint256 indexed streamId); + + /// @notice Emitted when the admin sets a new NFT descriptor contract. + /// @param admin The address of the current contract admin. + /// @param oldNFTDescriptor The address of the old NFT descriptor contract. + /// @param newNFTDescriptor The address of the new NFT descriptor contract. + event SetNFTDescriptor( + address indexed admin, + ISablierV2NFTDescriptor oldNFTDescriptor, + ISablierV2NFTDescriptor newNFTDescriptor + ); + + /// @notice Emitted when assets are withdrawn from a stream. + /// @param streamId The ID of the stream. + /// @param to The address that has received the withdrawn assets. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param amount The amount of assets withdrawn, denoted in units of the asset's decimals. + event WithdrawFromLockupStream( + uint256 indexed streamId, + address indexed to, + IERC20 indexed asset, + uint128 amount + ); + + /*////////////////////////////////////////////////////////////////////////// + CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Retrieves the address of the ERC-20 asset to be distributed. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getAsset(uint256 streamId) external view returns (IERC20 asset); + + /// @notice Retrieves the amount deposited in the stream, denoted in units of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getDepositedAmount( + uint256 streamId + ) external view returns (uint128 depositedAmount); + + /// @notice Retrieves the stream's end time, which is a Unix timestamp. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getEndTime( + uint256 streamId + ) external view returns (uint40 endTime); + + /// @notice Retrieves the stream's recipient. + /// @dev Reverts if the NFT has been burned. + /// @param streamId The stream ID for the query. + function getRecipient( + uint256 streamId + ) external view returns (address recipient); + + /// @notice Retrieves the amount refunded to the sender after a cancellation, denoted in units of the asset's + /// decimals. This amount is always zero unless the stream was canceled. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getRefundedAmount( + uint256 streamId + ) external view returns (uint128 refundedAmount); + + /// @notice Retrieves the stream's sender. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getSender(uint256 streamId) external view returns (address sender); + + /// @notice Retrieves the stream's start time, which is a Unix timestamp. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getStartTime( + uint256 streamId + ) external view returns (uint40 startTime); + + /// @notice Retrieves the amount withdrawn from the stream, denoted in units of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getWithdrawnAmount( + uint256 streamId + ) external view returns (uint128 withdrawnAmount); + + /// @notice Retrieves a flag indicating whether the provided address is a contract allowed to hook to Sablier + /// when a stream is canceled or when assets are withdrawn. + /// @dev See {ISablierLockupRecipient} for more information. + function isAllowedToHook( + address recipient + ) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream can be canceled. When the stream is cold, this + /// flag is always `false`. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isCancelable(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream is cold, i.e. settled, canceled, or depleted. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isCold(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream is depleted. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isDepleted(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream exists. + /// @dev Does not revert if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isStream(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream NFT can be transferred. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isTransferable( + uint256 streamId + ) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream is warm, i.e. either pending or streaming. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isWarm(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves the maximum broker fee that can be charged by the broker, denoted as a fixed-point + /// number where 1e18 is 100%. + /// @dev This value is hard coded as a constant. + function MAX_BROKER_FEE() external view returns (UD60x18); + + /// @notice Counter for stream IDs, used in the create functions. + function nextStreamId() external view returns (uint256); + + /// @notice Contract that generates the non-fungible token URI. + function nftDescriptor() external view returns (ISablierV2NFTDescriptor); + + /// @notice Calculates the amount that the sender would be refunded if the stream were canceled, denoted in units + /// of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function refundableAmountOf( + uint256 streamId + ) external view returns (uint128 refundableAmount); + + /// @notice Retrieves the stream's status. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function statusOf( + uint256 streamId + ) external view returns (Lockup.Status status); + + /// @notice Calculates the amount streamed to the recipient, denoted in units of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// + /// Notes: + /// - Upon cancellation of the stream, the amount streamed is calculated as the difference between the deposited + /// amount and the refunded amount. Ultimately, when the stream becomes depleted, the streamed amount is equivalent + /// to the total amount withdrawn. + /// + /// @param streamId The stream ID for the query. + function streamedAmountOf( + uint256 streamId + ) external view returns (uint128 streamedAmount); + + /// @notice Retrieves a flag indicating whether the stream was canceled. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function wasCanceled(uint256 streamId) external view returns (bool result); + + /// @notice Calculates the amount that the recipient can withdraw from the stream, denoted in units of the asset's + /// decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function withdrawableAmountOf( + uint256 streamId + ) external view returns (uint128 withdrawableAmount); + + /*////////////////////////////////////////////////////////////////////////// + NON-CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Allows a recipient contract to hook to Sablier when a stream is canceled or when assets are withdrawn. + /// Useful for implementing contracts that hold streams on behalf of users, such as vaults or staking contracts. + /// + /// @dev Emits an {AllowToHook} event. + /// + /// Notes: + /// - Does not revert if the contract is already on the allowlist. + /// - This is an irreversible operation. The contract cannot be removed from the allowlist. + /// + /// Requirements: + /// - `msg.sender` must be the contract admin. + /// - `recipient` must have a non-zero code size. + /// - `recipient` must implement {ISablierLockupRecipient}. + /// + /// @param recipient The address of the contract to allow for hooks. + function allowToHook(address recipient) external; + + /// @notice Burns the NFT associated with the stream. + /// + /// @dev Emits a {Transfer} event. + /// + /// Requirements: + /// - Must not be delegate called. + /// - `streamId` must reference a depleted stream. + /// - The NFT must exist. + /// - `msg.sender` must be either the NFT owner or an approved third party. + /// + /// @param streamId The ID of the stream NFT to burn. + function burn(uint256 streamId) external; + + /// @notice Cancels the stream and refunds any remaining assets to the sender. + /// + /// @dev Emits a {Transfer}, {CancelLockupStream}, and {MetadataUpdate} event. + /// + /// Notes: + /// - If there any assets left for the recipient to withdraw, the stream is marked as canceled. Otherwise, the + /// stream is marked as depleted. + /// - This function attempts to invoke a hook on the recipient, if the resolved address is a contract. + /// + /// Requirements: + /// - Must not be delegate called. + /// - The stream must be warm and cancelable. + /// - `msg.sender` must be the stream's sender. + /// + /// @param streamId The ID of the stream to cancel. + function cancel(uint256 streamId) external; + + /// @notice Cancels multiple streams and refunds any remaining assets to the sender. + /// + /// @dev Emits multiple {Transfer}, {CancelLockupStream}, and {MetadataUpdate} events. + /// + /// Notes: + /// - Refer to the notes in {cancel}. + /// + /// Requirements: + /// - All requirements from {cancel} must be met for each stream. + /// + /// @param streamIds The IDs of the streams to cancel. + function cancelMultiple(uint256[] calldata streamIds) external; + + /// @notice Removes the right of the stream's sender to cancel the stream. + /// + /// @dev Emits a {RenounceLockupStream} and {MetadataUpdate} event. + /// + /// Notes: + /// - This is an irreversible operation. + /// + /// Requirements: + /// - Must not be delegate called. + /// - `streamId` must reference a warm stream. + /// - `msg.sender` must be the stream's sender. + /// - The stream must be cancelable. + /// + /// @param streamId The ID of the stream to renounce. + function renounce(uint256 streamId) external; + + /// @notice Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. + /// + /// @dev Emits a {SetNFTDescriptor} and {BatchMetadataUpdate} event. + /// + /// Notes: + /// - Does not revert if the NFT descriptor is the same. + /// + /// Requirements: + /// - `msg.sender` must be the contract admin. + /// + /// @param newNFTDescriptor The address of the new NFT descriptor contract. + function setNFTDescriptor( + ISablierV2NFTDescriptor newNFTDescriptor + ) external; + + /// @notice Withdraws the provided amount of assets from the stream to the `to` address. + /// + /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. + /// + /// Notes: + /// - This function attempts to call a hook on the recipient of the stream, unless `msg.sender` is the recipient. + /// + /// Requirements: + /// - Must not be delegate called. + /// - `streamId` must not reference a null or depleted stream. + /// - `to` must not be the zero address. + /// - `amount` must be greater than zero and must not exceed the withdrawable amount. + /// - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. + /// + /// @param streamId The ID of the stream to withdraw from. + /// @param to The address receiving the withdrawn assets. + /// @param amount The amount to withdraw, denoted in units of the asset's decimals. + function withdraw(uint256 streamId, address to, uint128 amount) external; + + /// @notice Withdraws the maximum withdrawable amount from the stream to the provided address `to`. + /// + /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. + /// + /// Notes: + /// - Refer to the notes in {withdraw}. + /// + /// Requirements: + /// - Refer to the requirements in {withdraw}. + /// + /// @param streamId The ID of the stream to withdraw from. + /// @param to The address receiving the withdrawn assets. + /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. + function withdrawMax( + uint256 streamId, + address to + ) external returns (uint128 withdrawnAmount); + + /// @notice Withdraws the maximum withdrawable amount from the stream to the current recipient, and transfers the + /// NFT to `newRecipient`. + /// + /// @dev Emits a {WithdrawFromLockupStream} and a {Transfer} event. + /// + /// Notes: + /// - If the withdrawable amount is zero, the withdrawal is skipped. + /// - Refer to the notes in {withdraw}. + /// + /// Requirements: + /// - `msg.sender` must be the stream's recipient. + /// - Refer to the requirements in {withdraw}. + /// - Refer to the requirements in {IERC721.transferFrom}. + /// + /// @param streamId The ID of the stream NFT to transfer. + /// @param newRecipient The address of the new owner of the stream NFT. + /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. + function withdrawMaxAndTransfer( + uint256 streamId, + address newRecipient + ) external returns (uint128 withdrawnAmount); + + /// @notice Withdraws assets from streams to the recipient of each stream. + /// + /// @dev Emits multiple {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} events. + /// + /// Notes: + /// - This function attempts to call a hook on the recipient of each stream, unless `msg.sender` is the recipient. + /// + /// Requirements: + /// - Must not be delegate called. + /// - There must be an equal number of `streamIds` and `amounts`. + /// - Each stream ID in the array must not reference a null or depleted stream. + /// - Each amount in the array must be greater than zero and must not exceed the withdrawable amount. + /// + /// @param streamIds The IDs of the streams to withdraw from. + /// @param amounts The amounts to withdraw, denoted in units of the asset's decimals. + function withdrawMultiple( + uint256[] calldata streamIds, + uint128[] calldata amounts + ) external; +} diff --git a/contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol b/contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol new file mode 100644 index 00000000..49fec926 --- /dev/null +++ b/contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19; + +import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; + +/// @title ISablierV2NFTDescriptor +/// @notice This contract generates the URI describing the Sablier V2 stream NFTs. +/// @dev Inspired by Uniswap V3 Positions NFTs. +interface ISablierV2NFTDescriptor { + /// @notice Produces the URI describing a particular stream NFT. + /// @dev This is a data URI with the JSON contents directly inlined. + /// @param sablier The address of the Sablier contract the stream was created in. + /// @param streamId The ID of the stream for which to produce a description. + /// @return uri The URI of the ERC721-compliant metadata. + function tokenURI( + IERC721Metadata sablier, + uint256 streamId + ) external view returns (string memory uri); +} diff --git a/contracts/interfaces/sablier/full/types/DataTypes.sol b/contracts/interfaces/sablier/full/types/DataTypes.sol new file mode 100644 index 00000000..b7b50c59 --- /dev/null +++ b/contracts/interfaces/sablier/full/types/DataTypes.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {UD2x18} from "@prb/math/src/UD2x18.sol"; +import {UD60x18} from "@prb/math/src/UD60x18.sol"; + +// DataTypes.sol +// +// This file defines all structs used in V2 Core, most of which are organized under three namespaces: +// +// - Lockup +// - LockupDynamic +// - LockupLinear +// - LockupTranched +// +// You will notice that some structs contain "slot" annotations - they are used to indicate the +// storage layout of the struct. It is more gas efficient to group small data types together so +// that they fit in a single 32-byte slot. + +/// @notice Struct encapsulating the broker parameters passed to the create functions. Both can be set to zero. +/// @param account The address receiving the broker's fee. +/// @param fee The broker's percentage fee from the total amount, denoted as a fixed-point number where 1e18 is 100%. +struct Broker { + address account; + UD60x18 fee; +} + +/// @notice Namespace for the structs used in both {SablierV2LockupLinear} and {SablierV2LockupDynamic}. +library Lockup { + /// @notice Struct encapsulating the deposit, withdrawn, and refunded amounts, all denoted in units of the asset's + /// decimals. + /// @dev Because the deposited and the withdrawn amount are often read together, declaring them in the same slot + /// saves gas. + /// @param deposited The initial amount deposited in the stream, net of broker fee. + /// @param withdrawn The cumulative amount withdrawn from the stream. + /// @param refunded The amount refunded to the sender. Unless the stream was canceled, this is always zero. + struct Amounts { + // slot 0 + uint128 deposited; + uint128 withdrawn; + // slot 1 + uint128 refunded; + } + + /// @notice Struct encapsulating (i) the deposit amount and (ii) the broker fee amount, both denoted in units of the + /// asset's decimals. + /// @param deposit The amount to deposit in the stream. + /// @param brokerFee The broker fee amount. + struct CreateAmounts { + uint128 deposit; + uint128 brokerFee; + } + + /// @notice Enum representing the different statuses of a stream. + /// @custom:value0 PENDING Stream created but not started; assets are in a pending state. + /// @custom:value1 STREAMING Active stream where assets are currently being streamed. + /// @custom:value2 SETTLED All assets have been streamed; recipient is due to withdraw them. + /// @custom:value3 CANCELED Canceled stream; remaining assets await recipient's withdrawal. + /// @custom:value4 DEPLETED Depleted stream; all assets have been withdrawn and/or refunded. + enum Status { + PENDING, + STREAMING, + SETTLED, + CANCELED, + DEPLETED + } + + /// @notice A common data structure to be stored in all {SablierV2Lockup} models. + /// @dev The fields are arranged like this to save gas via tight variable packing. + /// @param sender The address distributing the assets, with the ability to cancel the stream. + /// @param startTime The Unix timestamp indicating the stream's start. + /// @param endTime The Unix timestamp indicating the stream's end. + /// @param isCancelable Boolean indicating if the stream is cancelable. + /// @param wasCanceled Boolean indicating if the stream was canceled. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param isDepleted Boolean indicating if the stream is depleted. + /// @param isStream Boolean indicating if the struct entity exists. + /// @param isTransferable Boolean indicating if the stream NFT is transferable. + /// @param amounts Struct encapsulating the deposit, withdrawn, and refunded amounts, all denoted in units of the + /// asset's decimals. + struct Stream { + // slot 0 + address sender; + uint40 startTime; + uint40 endTime; + bool isCancelable; + bool wasCanceled; + // slot 1 + IERC20 asset; + bool isDepleted; + bool isStream; + bool isTransferable; + // slot 2 and 3 + Lockup.Amounts amounts; + } +} + +/// @notice Namespace for the structs used in {SablierV2LockupDynamic}. +library LockupDynamic { + /// @notice Struct encapsulating the parameters of the {SablierV2LockupDynamic.createWithDurations} function. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets. + /// @param totalAmount The total amount of ERC-20 assets to be distributed, including the stream deposit and any + /// broker fee, denoted in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param segments Segments with durations used to compose the dynamic distribution function. Timestamps are + /// calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithDurations { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + SegmentWithDuration[] segments; + Broker broker; + } + + /// @notice Struct encapsulating the parameters of the {SablierV2LockupDynamic.createWithTimestamps} function. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets. + /// @param totalAmount The total amount of ERC-20 assets to be distributed, including the stream deposit and any + /// broker fee, denoted in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param startTime The Unix timestamp indicating the stream's start. + /// @param segments Segments used to compose the dynamic distribution function. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithTimestamps { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + uint40 startTime; + Segment[] segments; + Broker broker; + } + + /// @notice Segment struct used in the Lockup Dynamic stream. + /// @param amount The amount of assets to be streamed in the segment, denoted in units of the asset's decimals. + /// @param exponent The exponent of the segment, denoted as a fixed-point number. + /// @param timestamp The Unix timestamp indicating the segment's end. + struct Segment { + // slot 0 + uint128 amount; + UD2x18 exponent; + uint40 timestamp; + } + + /// @notice Segment struct used at runtime in {SablierV2LockupDynamic.createWithDurations}. + /// @param amount The amount of assets to be streamed in the segment, denoted in units of the asset's decimals. + /// @param exponent The exponent of the segment, denoted as a fixed-point number. + /// @param duration The time difference in seconds between the segment and the previous one. + struct SegmentWithDuration { + uint128 amount; + UD2x18 exponent; + uint40 duration; + } + + /// @notice Struct encapsulating the full details of a stream. + /// @dev Extends `Lockup.Stream` by including the recipient and the segments. + struct StreamLD { + address sender; + address recipient; + uint40 startTime; + uint40 endTime; + bool isCancelable; + bool wasCanceled; + IERC20 asset; + bool isDepleted; + bool isStream; + bool isTransferable; + Lockup.Amounts amounts; + Segment[] segments; + } + + /// @notice Struct encapsulating the LockupDynamic timestamps. + /// @param start The Unix timestamp indicating the stream's start. + /// @param end The Unix timestamp indicating the stream's end. + struct Timestamps { + uint40 start; + uint40 end; + } +} + +/// @notice Namespace for the structs used in {SablierV2LockupLinear}. +library LockupLinear { + /// @notice Struct encapsulating the parameters of the {SablierV2LockupLinear.createWithDurations} function. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets. + /// @param totalAmount The total amount of ERC-20 assets to be distributed, including the stream deposit and any + /// broker fee, denoted in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param durations Struct encapsulating (i) cliff period duration and (ii) total stream duration, both in seconds. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithDurations { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + Durations durations; + Broker broker; + } + + /// @notice Struct encapsulating the parameters of the {SablierV2LockupLinear.createWithTimestamps} function. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets. + /// @param totalAmount The total amount of ERC-20 assets to be distributed, including the stream deposit and any + /// broker fee, denoted in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param timestamps Struct encapsulating (i) the stream's start time, (ii) cliff time, and (iii) end time, all as + /// Unix timestamps. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithTimestamps { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + Timestamps timestamps; + Broker broker; + } + + /// @notice Struct encapsulating the cliff duration and the total duration. + /// @param cliff The cliff duration in seconds. + /// @param total The total duration in seconds. + struct Durations { + uint40 cliff; + uint40 total; + } + + /// @notice Struct encapsulating the full details of a stream. + /// @dev Extends `Lockup.Stream` by including the recipient and the cliff time. + struct StreamLL { + address sender; + address recipient; + uint40 startTime; + bool isCancelable; + bool wasCanceled; + IERC20 asset; + uint40 endTime; + bool isDepleted; + bool isStream; + bool isTransferable; + Lockup.Amounts amounts; + uint40 cliffTime; + } + + /// @notice Struct encapsulating the LockupLinear timestamps. + /// @param start The Unix timestamp for the stream's start. + /// @param cliff The Unix timestamp for the cliff period's end. A value of zero means there is no cliff. + /// @param end The Unix timestamp for the stream's end. + struct Timestamps { + uint40 start; + uint40 cliff; + uint40 end; + } +} + +/// @notice Namespace for the structs used in {SablierV2LockupTranched}. +library LockupTranched { + /// @notice Struct encapsulating the parameters of the {SablierV2LockupTranched.createWithDurations} function. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets. + /// @param totalAmount The total amount of ERC-20 assets to be distributed, including the stream deposit and any + /// broker fee, denoted in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param tranches Tranches with durations used to compose the tranched distribution function. Timestamps are + /// calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithDurations { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + TrancheWithDuration[] tranches; + Broker broker; + } + + /// @notice Struct encapsulating the parameters of the {SablierV2LockupTranched.createWithTimestamps} function. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets. + /// @param totalAmount The total amount of ERC-20 assets to be distributed, including the stream deposit and any + /// broker fee, denoted in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param startTime The Unix timestamp indicating the stream's start. + /// @param tranches Tranches used to compose the tranched distribution function. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithTimestamps { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + uint40 startTime; + Tranche[] tranches; + Broker broker; + } + + /// @notice Struct encapsulating the full details of a stream. + /// @dev Extends `Lockup.Stream` by including the recipient and the tranches. + struct StreamLT { + address sender; + address recipient; + uint40 startTime; + uint40 endTime; + bool isCancelable; + bool wasCanceled; + IERC20 asset; + bool isDepleted; + bool isStream; + bool isTransferable; + Lockup.Amounts amounts; + Tranche[] tranches; + } + + /// @notice Struct encapsulating the LockupTranched timestamps. + /// @param start The Unix timestamp indicating the stream's start. + /// @param end The Unix timestamp indicating the stream's end. + struct Timestamps { + uint40 start; + uint40 end; + } + + /// @notice Tranche struct used in the Lockup Tranched stream. + /// @param amount The amount of assets to be unlocked in the tranche, denoted in units of the asset's decimals. + /// @param timestamp The Unix timestamp indicating the tranche's end. + struct Tranche { + // slot 0 + uint128 amount; + uint40 timestamp; + } + + /// @notice Tranche struct used at runtime in {SablierV2LockupTranched.createWithDurations}. + /// @param amount The amount of assets to be unlocked in the tranche, denoted in units of the asset's decimals. + /// @param duration The time difference in seconds between the tranche and the previous one. + struct TrancheWithDuration { + uint128 amount; + uint40 duration; + } +} From ffc7c0fc4d2fe50823e3d755d5d76b66b530f207 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 14:50:34 -0400 Subject: [PATCH 043/119] Fix MockSablierV2LockupLinear contract to actually implement full ISablierV2Lockup interface --- contracts/mocks/MockSablierV2LockupLinear.sol | 189 +++++++++++++++++- 1 file changed, 183 insertions(+), 6 deletions(-) diff --git a/contracts/mocks/MockSablierV2LockupLinear.sol b/contracts/mocks/MockSablierV2LockupLinear.sol index 8511d364..99ec6c7b 100644 --- a/contracts/mocks/MockSablierV2LockupLinear.sol +++ b/contracts/mocks/MockSablierV2LockupLinear.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity =0.8.19; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../interfaces/sablier/ISablierV2LockupLinear.sol"; -import {LockupLinear} from "../interfaces/sablier/LockupLinear.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {UD60x18} from "@prb/math/src/UD60x18.sol"; +import {ISablierV2NFTDescriptor} from "../interfaces/sablier/full/ISablierV2NFTDescriptor.sol"; +import {ISablierV2Lockup} from "../interfaces/sablier/full/ISablierV2Lockup.sol"; +import {LockupLinear, Lockup} from "../interfaces/sablier/full/types/DataTypes.sol"; -contract MockSablierV2LockupLinear is ISablierV2LockupLinear { +contract MockSablierV2LockupLinear is ISablierV2Lockup { // Define the Stream struct here struct Stream { address sender; @@ -38,7 +40,7 @@ contract MockSablierV2LockupLinear is ISablierV2LockupLinear { function createWithTimestamps( LockupLinear.CreateWithTimestamps calldata params - ) external override returns (uint256 streamId) { + ) external returns (uint256 streamId) { require( params.asset.transferFrom( msg.sender, @@ -167,6 +169,181 @@ contract MockSablierV2LockupLinear is ISablierV2LockupLinear { Stream storage stream = streams[streamId]; stream.totalAmount -= withdrawnAmount; IERC20(stream.asset).transfer(to, withdrawnAmount); - emit WithdrawFromLockupStream(streamId, to, IERC20(stream.asset), withdrawnAmount); + emit WithdrawFromLockupStream( + streamId, + to, + IERC20(stream.asset), + withdrawnAmount + ); } + + function getAsset( + uint256 streamId + ) external view override returns (IERC20) {} + + function getDepositedAmount( + uint256 streamId + ) external view override returns (uint128) {} + + function getEndTime( + uint256 streamId + ) external view override returns (uint40) {} + + function getRecipient( + uint256 streamId + ) external view override returns (address) {} + + function getSender( + uint256 streamId + ) external view override returns (address) {} + + function getStartTime( + uint256 streamId + ) external view override returns (uint40) {} + + function isCancelable( + uint256 streamId + ) external view override returns (bool) {} + + function isTransferable( + uint256 streamId + ) external view override returns (bool) {} + + function refundableAmountOf( + uint256 streamId + ) external view override returns (uint128) {} + + function streamedAmountOf( + uint256 streamId + ) external view override returns (uint128) {} + + function wasCanceled( + uint256 streamId + ) external pure override returns (bool) {} + + function withdrawMultiple( + uint256[] calldata streamIds, + uint128[] calldata amounts + ) external {} + + function withdrawMaxAndTransfer( + uint256 streamId, + address newRecipient + ) external returns (uint128 withdrawnAmount) {} + + function getRefundedAmount( + uint256 streamId + ) external view returns (uint128 refundedAmount) {} + + function getWithdrawnAmount( + uint256 streamId + ) external view returns (uint128 withdrawnAmount) {} + + function isAllowedToHook( + address recipient + ) external view returns (bool result) {} + + function isCold(uint256 streamId) external view returns (bool result) {} + + function isDepleted(uint256 streamId) external view returns (bool result) {} + + function isStream(uint256 streamId) external view returns (bool result) {} + + function isWarm(uint256 streamId) external view returns (bool result) {} + + function getBrokerFee(uint256 streamId) external view returns (uint256) {} + + function getBroker(uint256 streamId) external view returns (address) {} + + function getBrokerFeeBips( + uint256 streamId + ) external view returns (uint256) {} + + function admin() external view override returns (address) {} + + function transferAdmin(address newAdmin) external override {} + + function supportsInterface( + bytes4 interfaceId + ) external view override returns (bool) {} + + function balanceOf( + address owner + ) external view override returns (uint256 balance) {} + + function ownerOf( + uint256 tokenId + ) external view override returns (address owner) {} + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external override {} + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external override {} + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external override {} + + function approve(address to, uint256 tokenId) external override {} + + function setApprovalForAll( + address operator, + bool _approved + ) external override {} + + function getApproved( + uint256 tokenId + ) external view override returns (address operator) {} + + function isApprovedForAll( + address owner, + address operator + ) external view override returns (bool) {} + + function name() external view override returns (string memory) {} + + function symbol() external view override returns (string memory) {} + + function tokenURI( + uint256 tokenId + ) external view override returns (string memory) {} + + function MAX_BROKER_FEE() external view override returns (UD60x18) {} + + function nftDescriptor() + external + view + override + returns (ISablierV2NFTDescriptor) + {} + + function statusOf( + uint256 streamId + ) external view override returns (Lockup.Status status) {} + + function allowToHook(address recipient) external override {} + + function burn(uint256 streamId) external override {} + + function cancelMultiple(uint256[] calldata streamIds) external override {} + + function setNFTDescriptor( + ISablierV2NFTDescriptor newNFTDescriptor + ) external override {} + + function withdraw( + uint256 streamId, + address to, + uint128 amount + ) external override {} } From 60fb6d86b16ca1c838ce14af19cfcba39e2e5fba Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 14:51:28 -0400 Subject: [PATCH 044/119] Use new "full" Sablier interfaces in DecenHats_0_2_0 --- contracts/DecentHats_0_2_0.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index d24736b0..4a78de51 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -7,7 +7,7 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; import {IHats} from "./interfaces/hats/IHats.sol"; -import {LockupLinear} from "./interfaces/sablier/LockupLinear.sol"; +import {LockupLinear, Broker} from "./interfaces/sablier/full/types/DataTypes.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; @@ -22,7 +22,7 @@ contract DecentHats_0_2_0 { address sender; address asset; LockupLinear.Timestamps timestamps; - LockupLinear.Broker broker; + Broker broker; uint128 totalAmount; bool cancelable; bool transferable; From fabf38daad1a7f8e38ef7120f1b98cbc311d09cd Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 14:52:52 -0400 Subject: [PATCH 045/119] Type the sablier address as the "full" ISablierV2Lockup type in DecentAutonomousAdmin --- contracts/DecentAutonomousAdmin.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index fd7b7cb4..191470e6 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.19; import "./interfaces/hats/IHats.sol"; import "./interfaces/hats/IHatsElectionEligibility.sol"; -import "./interfaces/sablier/ISablierV2LockupLinear.sol"; +import "./interfaces/sablier/full/ISablierV2Lockup.sol"; contract DecentAutonomousAdmin { string public constant NAME = "DecentAutonomousAdmin"; @@ -10,7 +10,7 @@ contract DecentAutonomousAdmin { struct SablierStreamInfo { uint256 streamId; - ISablierV2LockupLinear sablierV2LockupLinear; + ISablierV2Lockup sablierV2Lockup; } struct TriggerStartArgs { address currentWearer; @@ -80,7 +80,7 @@ contract DecentAutonomousAdmin { address withdrawTo ) internal { for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { - _sablierStreamInfo[i].sablierV2LockupLinear.withdrawMax( + _sablierStreamInfo[i].sablierV2Lockup.withdrawMax( _sablierStreamInfo[i].streamId, withdrawTo ); @@ -99,7 +99,7 @@ contract DecentAutonomousAdmin { for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { uint128 withdrawableAmount = _sablierStreamInfo[i] - .sablierV2LockupLinear + .sablierV2Lockup .withdrawableAmountOf(_sablierStreamInfo[i].streamId); if (withdrawableAmount > 0) { @@ -115,7 +115,7 @@ contract DecentAutonomousAdmin { for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { uint128 withdrawableAmount = _sablierStreamInfo[i] - .sablierV2LockupLinear + .sablierV2Lockup .withdrawableAmountOf(_sablierStreamInfo[i].streamId); if (withdrawableAmount > 0) { From 38f80ef50032de593e44d7f38852a8d696dfe7f4 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 14:53:23 -0400 Subject: [PATCH 046/119] Remove changes from original ISablierV2LockupLinear interface, so no bytecode changes to DecentHats_0_1_0 occur --- .../sablier/ISablierV2LockupLinear.sol | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/contracts/interfaces/sablier/ISablierV2LockupLinear.sol b/contracts/interfaces/sablier/ISablierV2LockupLinear.sol index 4bf0c724..0aa6cac9 100644 --- a/contracts/interfaces/sablier/ISablierV2LockupLinear.sol +++ b/contracts/interfaces/sablier/ISablierV2LockupLinear.sol @@ -8,35 +8,4 @@ interface ISablierV2LockupLinear { function createWithTimestamps( LockupLinear.CreateWithTimestamps calldata params ) external returns (uint256 streamId); - - /// @notice Emitted when assets are withdrawn from a stream. - /// @param streamId The ID of the stream. - /// @param to The address that has received the withdrawn assets. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param amount The amount of assets withdrawn, denoted in units of the asset's decimals. - event WithdrawFromLockupStream(uint256 indexed streamId, address indexed to, IERC20 indexed asset, uint128 amount); - - /// @notice Withdraws the maximum withdrawable amount from the stream to the provided address `to`. - /// - /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. - /// - /// Notes: - /// - Refer to the notes in {withdraw}. - /// - /// Requirements: - /// - Refer to the requirements in {withdraw}. - /// - /// @param streamId The ID of the stream to withdraw from. - /// @param to The address receiving the withdrawn assets. - /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. - function withdrawMax( - uint256 streamId, - address to - ) external returns (uint128 withdrawnAmount); - - /// @notice Calculates the amount that the recipient can withdraw from the stream, denoted in units of the asset's - /// decimals. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function withdrawableAmountOf(uint256 streamId) external view returns (uint128 withdrawableAmount); } From 1470b4ed014cc2f1d04251954978703fcc20dd9c Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 14:54:34 -0400 Subject: [PATCH 047/119] Named imports in DecentAutonomousAdmin --- contracts/DecentAutonomousAdmin.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 191470e6..21915faa 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity =0.8.19; -import "./interfaces/hats/IHats.sol"; -import "./interfaces/hats/IHatsElectionEligibility.sol"; -import "./interfaces/sablier/full/ISablierV2Lockup.sol"; +import {IHats} from "./interfaces/hats/IHats.sol"; +import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; +import {ISablierV2Lockup} from "./interfaces/sablier/full/ISablierV2Lockup.sol"; contract DecentAutonomousAdmin { string public constant NAME = "DecentAutonomousAdmin"; From 43fad8e9418c9b13087063a0fd12c6778dc592b0 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 14:57:25 -0400 Subject: [PATCH 048/119] Copy in the FULL Hats interfaces --- contracts/interfaces/hats/full/HatsErrors.sol | 86 ++++++++ contracts/interfaces/hats/full/HatsEvents.sol | 92 ++++++++ contracts/interfaces/hats/full/IHats.sol | 205 ++++++++++++++++++ .../interfaces/hats/full/IHatsIdUtilities.sol | 68 ++++++ 4 files changed, 451 insertions(+) create mode 100644 contracts/interfaces/hats/full/HatsErrors.sol create mode 100644 contracts/interfaces/hats/full/HatsEvents.sol create mode 100644 contracts/interfaces/hats/full/IHats.sol create mode 100644 contracts/interfaces/hats/full/IHatsIdUtilities.sol diff --git a/contracts/interfaces/hats/full/HatsErrors.sol b/contracts/interfaces/hats/full/HatsErrors.sol new file mode 100644 index 00000000..b592529c --- /dev/null +++ b/contracts/interfaces/hats/full/HatsErrors.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0 +// Copyright (C) 2023 Haberdasher Labs +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.13; + +interface HatsErrors { + /// @notice Emitted when `user` is attempting to perform an action on `hatId` but is not wearing one of `hatId`'s admin hats + /// @dev Can be equivalent to `NotHatWearer(buildHatId(hatId))`, such as when emitted by `approveLinkTopHatToTree` or `relinkTopHatToTree` + error NotAdmin(address user, uint256 hatId); + + /// @notice Emitted when attempting to perform an action as or for an account that is not a wearer of a given hat + error NotHatWearer(); + + /// @notice Emitted when attempting to perform an action that requires being either an admin or wearer of a given hat + error NotAdminOrWearer(); + + /// @notice Emitted when attempting to mint `hatId` but `hatId`'s maxSupply has been reached + error AllHatsWorn(uint256 hatId); + + /// @notice Emitted when attempting to create a hat with a level 14 hat as its admin + error MaxLevelsReached(); + + /// @notice Emitted when an attempted hat id has empty intermediate level(s) + error InvalidHatId(); + + /// @notice Emitted when attempting to mint `hatId` to a `wearer` who is already wearing the hat + error AlreadyWearingHat(address wearer, uint256 hatId); + + /// @notice Emitted when attempting to mint a non-existant hat + error HatDoesNotExist(uint256 hatId); + + /// @notice Emmitted when attempting to mint or transfer a hat that is not active + error HatNotActive(); + + /// @notice Emitted when attempting to mint or transfer a hat to an ineligible wearer + error NotEligible(); + + /// @notice Emitted when attempting to check or set a hat's status from an account that is not that hat's toggle module + error NotHatsToggle(); + + /// @notice Emitted when attempting to check or set a hat wearer's status from an account that is not that hat's eligibility module + error NotHatsEligibility(); + + /// @notice Emitted when array arguments to a batch function have mismatching lengths + error BatchArrayLengthMismatch(); + + /// @notice Emitted when attempting to mutate or transfer an immutable hat + error Immutable(); + + /// @notice Emitted when attempting to change a hat's maxSupply to a value lower than its current supply + error NewMaxSupplyTooLow(); + + /// @notice Emitted when attempting to link a tophat to a new admin for which the tophat serves as an admin + error CircularLinkage(); + + /// @notice Emitted when attempting to link or relink a tophat to a separate tree + error CrossTreeLinkage(); + + /// @notice Emitted when attempting to link a tophat without a request + error LinkageNotRequested(); + + /// @notice Emitted when attempting to unlink a tophat that does not have a wearer + /// @dev This ensures that unlinking never results in a bricked tophat + error InvalidUnlink(); + + /// @notice Emmited when attempting to change a hat's eligibility or toggle module to the zero address + error ZeroAddress(); + + /// @notice Emmitted when attempting to change a hat's details or imageURI to a string with over 7000 bytes (~characters) + /// @dev This protects against a DOS attack where an admin iteratively extend's a hat's details or imageURI + /// to be so long that reading it exceeds the block gas limit, breaking `uri()` and `viewHat()` + error StringTooLong(); +} diff --git a/contracts/interfaces/hats/full/HatsEvents.sol b/contracts/interfaces/hats/full/HatsEvents.sol new file mode 100644 index 00000000..817e4ec1 --- /dev/null +++ b/contracts/interfaces/hats/full/HatsEvents.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0 +// Copyright (C) 2023 Haberdasher Labs +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.13; + +interface HatsEvents { + /// @notice Emitted when a new hat is created + /// @param id The id for the new hat + /// @param details A description of the Hat + /// @param maxSupply The total instances of the Hat that can be worn at once + /// @param eligibility The address that can report on the Hat wearer's status + /// @param toggle The address that can deactivate the Hat + /// @param mutable_ Whether the hat's properties are changeable after creation + /// @param imageURI The image uri for this hat and the fallback for its + event HatCreated( + uint256 id, + string details, + uint32 maxSupply, + address eligibility, + address toggle, + bool mutable_, + string imageURI + ); + + /// @notice Emitted when a hat wearer's standing is updated + /// @dev Eligibility is excluded since the source of truth for eligibility is the eligibility module and may change without a transaction + /// @param hatId The id of the wearer's hat + /// @param wearer The wearer's address + /// @param wearerStanding Whether the wearer is in good standing for the hat + event WearerStandingChanged( + uint256 hatId, + address wearer, + bool wearerStanding + ); + + /// @notice Emitted when a hat's status is updated + /// @param hatId The id of the hat + /// @param newStatus Whether the hat is active + event HatStatusChanged(uint256 hatId, bool newStatus); + + /// @notice Emitted when a hat's details are updated + /// @param hatId The id of the hat + /// @param newDetails The updated details + event HatDetailsChanged(uint256 hatId, string newDetails); + + /// @notice Emitted when a hat's eligibility module is updated + /// @param hatId The id of the hat + /// @param newEligibility The updated eligibiliy module + event HatEligibilityChanged(uint256 hatId, address newEligibility); + + /// @notice Emitted when a hat's toggle module is updated + /// @param hatId The id of the hat + /// @param newToggle The updated toggle module + event HatToggleChanged(uint256 hatId, address newToggle); + + /// @notice Emitted when a hat's mutability is updated + /// @param hatId The id of the hat + event HatMutabilityChanged(uint256 hatId); + + /// @notice Emitted when a hat's maximum supply is updated + /// @param hatId The id of the hat + /// @param newMaxSupply The updated max supply + event HatMaxSupplyChanged(uint256 hatId, uint32 newMaxSupply); + + /// @notice Emitted when a hat's image URI is updated + /// @param hatId The id of the hat + /// @param newImageURI The updated image URI + event HatImageURIChanged(uint256 hatId, string newImageURI); + + /// @notice Emitted when a tophat linkage is requested by its admin + /// @param domain The domain of the tree tophat to link + /// @param newAdmin The tophat's would-be admin in the parent tree + event TopHatLinkRequested(uint32 domain, uint256 newAdmin); + + /// @notice Emitted when a tophat is linked to a another tree + /// @param domain The domain of the newly-linked tophat + /// @param newAdmin The tophat's new admin in the parent tree + event TopHatLinked(uint32 domain, uint256 newAdmin); +} diff --git a/contracts/interfaces/hats/full/IHats.sol b/contracts/interfaces/hats/full/IHats.sol new file mode 100644 index 00000000..5d0cc188 --- /dev/null +++ b/contracts/interfaces/hats/full/IHats.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: AGPL-3.0 +// Copyright (C) 2023 Haberdasher Labs +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.13; + +import "./IHatsIdUtilities.sol"; +import "./HatsErrors.sol"; +import "./HatsEvents.sol"; + +interface IHats is IHatsIdUtilities, HatsErrors, HatsEvents { + function mintTopHat( + address _target, + string memory _details, + string memory _imageURI + ) external returns (uint256 topHatId); + + function createHat( + uint256 _admin, + string calldata _details, + uint32 _maxSupply, + address _eligibility, + address _toggle, + bool _mutable, + string calldata _imageURI + ) external returns (uint256 newHatId); + + function batchCreateHats( + uint256[] calldata _admins, + string[] calldata _details, + uint32[] calldata _maxSupplies, + address[] memory _eligibilityModules, + address[] memory _toggleModules, + bool[] calldata _mutables, + string[] calldata _imageURIs + ) external returns (bool success); + + function getNextId(uint256 _admin) external view returns (uint256 nextId); + + function mintHat( + uint256 _hatId, + address _wearer + ) external returns (bool success); + + function batchMintHats( + uint256[] calldata _hatIds, + address[] calldata _wearers + ) external returns (bool success); + + function setHatStatus( + uint256 _hatId, + bool _newStatus + ) external returns (bool toggled); + + function checkHatStatus(uint256 _hatId) external returns (bool toggled); + + function setHatWearerStatus( + uint256 _hatId, + address _wearer, + bool _eligible, + bool _standing + ) external returns (bool updated); + + function checkHatWearerStatus( + uint256 _hatId, + address _wearer + ) external returns (bool updated); + + function renounceHat(uint256 _hatId) external; + + function transferHat(uint256 _hatId, address _from, address _to) external; + + /*////////////////////////////////////////////////////////////// + HATS ADMIN FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function makeHatImmutable(uint256 _hatId) external; + + function changeHatDetails( + uint256 _hatId, + string memory _newDetails + ) external; + + function changeHatEligibility( + uint256 _hatId, + address _newEligibility + ) external; + + function changeHatToggle(uint256 _hatId, address _newToggle) external; + + function changeHatImageURI( + uint256 _hatId, + string memory _newImageURI + ) external; + + function changeHatMaxSupply(uint256 _hatId, uint32 _newMaxSupply) external; + + function requestLinkTopHatToTree( + uint32 _topHatId, + uint256 _newAdminHat + ) external; + + function approveLinkTopHatToTree( + uint32 _topHatId, + uint256 _newAdminHat, + address _eligibility, + address _toggle, + string calldata _details, + string calldata _imageURI + ) external; + + function unlinkTopHatFromTree(uint32 _topHatId, address _wearer) external; + + function relinkTopHatWithinTree( + uint32 _topHatDomain, + uint256 _newAdminHat, + address _eligibility, + address _toggle, + string calldata _details, + string calldata _imageURI + ) external; + + /*////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function viewHat( + uint256 _hatId + ) + external + view + returns ( + string memory details, + uint32 maxSupply, + uint32 supply, + address eligibility, + address toggle, + string memory imageURI, + uint16 lastHatId, + bool mutable_, + bool active + ); + + function isWearerOfHat( + address _user, + uint256 _hatId + ) external view returns (bool isWearer); + + function isAdminOfHat( + address _user, + uint256 _hatId + ) external view returns (bool isAdmin); + + function isInGoodStanding( + address _wearer, + uint256 _hatId + ) external view returns (bool standing); + + function isEligible( + address _wearer, + uint256 _hatId + ) external view returns (bool eligible); + + function getHatEligibilityModule( + uint256 _hatId + ) external view returns (address eligibility); + + function getHatToggleModule( + uint256 _hatId + ) external view returns (address toggle); + + function getHatMaxSupply( + uint256 _hatId + ) external view returns (uint32 maxSupply); + + function hatSupply(uint256 _hatId) external view returns (uint32 supply); + + function getImageURIForHat( + uint256 _hatId + ) external view returns (string memory _uri); + + function balanceOf( + address wearer, + uint256 hatId + ) external view returns (uint256 balance); + + function balanceOfBatch( + address[] calldata _wearers, + uint256[] calldata _hatIds + ) external view returns (uint256[] memory); + + function uri(uint256 id) external view returns (string memory _uri); +} diff --git a/contracts/interfaces/hats/full/IHatsIdUtilities.sol b/contracts/interfaces/hats/full/IHatsIdUtilities.sol new file mode 100644 index 00000000..dcf640fd --- /dev/null +++ b/contracts/interfaces/hats/full/IHatsIdUtilities.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0 +// Copyright (C) 2023 Haberdasher Labs +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.13; + +interface IHatsIdUtilities { + function buildHatId( + uint256 _admin, + uint16 _newHat + ) external pure returns (uint256 id); + + function getHatLevel(uint256 _hatId) external view returns (uint32 level); + + function getLocalHatLevel( + uint256 _hatId + ) external pure returns (uint32 level); + + function isTopHat(uint256 _hatId) external view returns (bool _topHat); + + function isLocalTopHat( + uint256 _hatId + ) external pure returns (bool _localTopHat); + + function isValidHatId( + uint256 _hatId + ) external view returns (bool validHatId); + + function getAdminAtLevel( + uint256 _hatId, + uint32 _level + ) external view returns (uint256 admin); + + function getAdminAtLocalLevel( + uint256 _hatId, + uint32 _level + ) external pure returns (uint256 admin); + + function getTopHatDomain( + uint256 _hatId + ) external view returns (uint32 domain); + + function getTippyTopHatDomain( + uint32 _topHatDomain + ) external view returns (uint32 domain); + + function noCircularLinkage( + uint32 _topHatDomain, + uint256 _linkedAdmin + ) external view returns (bool notCircular); + + function sameTippyTopHatDomain( + uint32 _topHatDomain, + uint256 _newAdminHat + ) external view returns (bool sameDomain); +} From dca553be7fd1baa94546db79211057eb9ff8786b Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:08:57 -0400 Subject: [PATCH 049/119] Rename IHatModuleFactory to IHatsModuleFactory, place it in Hats directory, make sure the interface and mock implementation are complete --- contracts/DecentHats_0_2_0.sol | 2 +- contracts/interfaces/IHatModuleFactory.sol | 20 ------- .../hats/full/IHatsModuleFactory.sol | 56 +++++++++++++++++++ contracts/mocks/MockHatsModuleFactory.sol | 21 ++++++- 4 files changed, 77 insertions(+), 22 deletions(-) delete mode 100644 contracts/interfaces/IHatModuleFactory.sol create mode 100644 contracts/interfaces/hats/full/IHatsModuleFactory.sol diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index 4a78de51..a3b68312 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -9,7 +9,7 @@ import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; import {IHats} from "./interfaces/hats/IHats.sol"; import {LockupLinear, Broker} from "./interfaces/sablier/full/types/DataTypes.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; -import {IHatsModuleFactory} from "./interfaces/IHatModuleFactory.sol"; +import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; diff --git a/contracts/interfaces/IHatModuleFactory.sol b/contracts/interfaces/IHatModuleFactory.sol deleted file mode 100644 index 46c74ac0..00000000 --- a/contracts/interfaces/IHatModuleFactory.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - - -interface IHatsModuleFactory { - function createHatsModule( - address _implementation, - uint256 _hatId, - bytes calldata _otherImmutableArgs, - bytes calldata _initData, - uint256 _saltNonce - ) external returns (address _instance); - - function getHatsModuleAddress( - address _implementation, - uint256 _hatId, - bytes calldata _otherImmutableArgs, - uint256 _saltNonce - ) external view returns (address); -} diff --git a/contracts/interfaces/hats/full/IHatsModuleFactory.sol b/contracts/interfaces/hats/full/IHatsModuleFactory.sol new file mode 100644 index 00000000..1aa8804d --- /dev/null +++ b/contracts/interfaces/hats/full/IHatsModuleFactory.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IHatsModuleFactory { + error HatsModuleFactory_ModuleAlreadyDeployed( + address implementation, + uint256 hatId, + bytes otherImmutableArgs, + uint256 saltNonce + ); + + error BatchArrayLengthMismatch(); + + event HatsModuleFactory_ModuleDeployed( + address implementation, + address instance, + uint256 hatId, + bytes otherImmutableArgs, + bytes initData, + uint256 saltNonce + ); + + function HATS() external view returns (address); + + function version() external view returns (string memory); + + function createHatsModule( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + bytes calldata _initData, + uint256 _saltNonce + ) external returns (address _instance); + + function batchCreateHatsModule( + address[] calldata _implementations, + uint256[] calldata _hatIds, + bytes[] calldata _otherImmutableArgsArray, + bytes[] calldata _initDataArray, + uint256[] calldata _saltNonces + ) external returns (bool success); + + function getHatsModuleAddress( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + uint256 _saltNonce + ) external view returns (address); + + function deployed( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + uint256 _saltNonce + ) external view returns (bool); +} diff --git a/contracts/mocks/MockHatsModuleFactory.sol b/contracts/mocks/MockHatsModuleFactory.sol index 2bfb92ba..944d3401 100644 --- a/contracts/mocks/MockHatsModuleFactory.sol +++ b/contracts/mocks/MockHatsModuleFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IHatsModuleFactory} from "../interfaces/IHatModuleFactory.sol"; +import {IHatsModuleFactory} from "../interfaces/hats/full/IHatsModuleFactory.sol"; contract MockHatsModuleFactory is IHatsModuleFactory { function createHatsModule( @@ -33,4 +33,23 @@ contract MockHatsModuleFactory is IHatsModuleFactory { _saltNonce; return address(0); } + + function HATS() external view override returns (address) {} + + function version() external view override returns (string memory) {} + + function batchCreateHatsModule( + address[] calldata _implementations, + uint256[] calldata _hatIds, + bytes[] calldata _otherImmutableArgsArray, + bytes[] calldata _initDataArray, + uint256[] calldata _saltNonces + ) external override returns (bool success) {} + + function deployed( + address _implementation, + uint256 _hatId, + bytes calldata _otherImmutableArgs, + uint256 _saltNonce + ) external view override returns (bool) {} } From 49ef5d7bf2e9194fe16a8e563c0215c5eb5b16c6 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:14:02 -0400 Subject: [PATCH 050/119] Move IHatsElectionsElegibility into "full" directory --- contracts/DecentAutonomousAdmin.sol | 2 +- contracts/DecentHats_0_2_0.sol | 2 +- .../interfaces/hats/{ => full}/IHatsElectionEligibility.sol | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename contracts/interfaces/hats/{ => full}/IHatsElectionEligibility.sol (100%) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 21915faa..4f48c4df 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity =0.8.19; import {IHats} from "./interfaces/hats/IHats.sol"; -import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; +import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {ISablierV2Lockup} from "./interfaces/sablier/full/ISablierV2Lockup.sol"; contract DecentAutonomousAdmin { diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index a3b68312..f93c06b9 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -10,7 +10,7 @@ import {IHats} from "./interfaces/hats/IHats.sol"; import {LockupLinear, Broker} from "./interfaces/sablier/full/types/DataTypes.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol"; -import {IHatsElectionEligibility} from "./interfaces/hats/IHatsElectionEligibility.sol"; +import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; diff --git a/contracts/interfaces/hats/IHatsElectionEligibility.sol b/contracts/interfaces/hats/full/IHatsElectionEligibility.sol similarity index 100% rename from contracts/interfaces/hats/IHatsElectionEligibility.sol rename to contracts/interfaces/hats/full/IHatsElectionEligibility.sol From 1633f2662fb0ad12c6d4b6c7a8c4a53708d10a19 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:16:59 -0400 Subject: [PATCH 051/119] If you don't need to use a variable in a function just delete the name of it in the input params --- contracts/mocks/MockHatsAdmin.sol | 38 ++++++++++++------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/contracts/mocks/MockHatsAdmin.sol b/contracts/mocks/MockHatsAdmin.sol index 914c0d30..a32683ef 100644 --- a/contracts/mocks/MockHatsAdmin.sol +++ b/contracts/mocks/MockHatsAdmin.sol @@ -11,33 +11,22 @@ contract MockHatsAutoAdmin is IHats { event HatCreated(uint256 hatId); function mintTopHat( - address _target, - string memory _details, - string memory _imageURI + address, + string memory, + string memory ) external pure returns (uint256 topHatId) { - // Silence unused variable warnings - _target; - _details; - _imageURI; return 0; } function createHat( - uint256 _admin, - string calldata _details, - uint32 _maxSupply, + uint256, + string calldata, + uint32, address _eligibility, - address _toggle, - bool _mutable, - string calldata _imageURI + address, + bool, + string calldata ) external returns (uint256 newHatId) { - // Silence unused variable warnings - _admin; - _details; - _maxSupply; - _toggle; - _mutable; - _imageURI; hatId++; eligibility[hatId] = _eligibility; emit HatCreated(hatId); @@ -67,15 +56,16 @@ contract MockHatsAutoAdmin is IHats { function transferHat( uint256 _hatId, - address from, + address, address to ) external override { - // Silence unused variable warnings - from; wearer[_hatId] = to; } - function changeHatEligibility(uint256 _hatId, address _newEligibility) external override { + function changeHatEligibility( + uint256 _hatId, + address _newEligibility + ) external override { eligibility[_hatId] = _newEligibility; } } From 004e4a33177adae705187e7a449c64733406e4b5 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:18:08 -0400 Subject: [PATCH 052/119] Finish implementing full Hats interface in MockHatsAdmin --- contracts/mocks/MockHatsAdmin.sol | 211 +++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-) diff --git a/contracts/mocks/MockHatsAdmin.sol b/contracts/mocks/MockHatsAdmin.sol index a32683ef..9fc250b4 100644 --- a/contracts/mocks/MockHatsAdmin.sol +++ b/contracts/mocks/MockHatsAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity =0.8.19; -import "../interfaces/hats/IHats.sol"; +import {IHats} from "../interfaces/hats/full/IHats.sol"; contract MockHatsAutoAdmin is IHats { uint256 hatId = 0; @@ -68,4 +68,213 @@ contract MockHatsAutoAdmin is IHats { ) external override { eligibility[_hatId] = _newEligibility; } + + function buildHatId( + uint256 _admin, + uint16 _newHat + ) external pure override returns (uint256 id) {} + + function getHatLevel( + uint256 _hatId + ) external view override returns (uint32 level) {} + + function getLocalHatLevel( + uint256 _hatId + ) external pure override returns (uint32 level) {} + + function isTopHat( + uint256 _hatId + ) external view override returns (bool _topHat) {} + + function isLocalTopHat( + uint256 _hatId + ) external pure override returns (bool _localTopHat) {} + + function isValidHatId( + uint256 _hatId + ) external view override returns (bool validHatId) {} + + function getAdminAtLevel( + uint256 _hatId, + uint32 _level + ) external view override returns (uint256 admin) {} + + function getAdminAtLocalLevel( + uint256 _hatId, + uint32 _level + ) external pure override returns (uint256 admin) {} + + function getTopHatDomain( + uint256 _hatId + ) external view override returns (uint32 domain) {} + + function getTippyTopHatDomain( + uint32 _topHatDomain + ) external view override returns (uint32 domain) {} + + function noCircularLinkage( + uint32 _topHatDomain, + uint256 _linkedAdmin + ) external view override returns (bool notCircular) {} + + function sameTippyTopHatDomain( + uint32 _topHatDomain, + uint256 _newAdminHat + ) external view override returns (bool sameDomain) {} + + function batchCreateHats( + uint256[] calldata _admins, + string[] calldata _details, + uint32[] calldata _maxSupplies, + address[] memory _eligibilityModules, + address[] memory _toggleModules, + bool[] calldata _mutables, + string[] calldata _imageURIs + ) external override returns (bool success) {} + + function getNextId( + uint256 _admin + ) external view override returns (uint256 nextId) {} + + function batchMintHats( + uint256[] calldata _hatIds, + address[] calldata _wearers + ) external override returns (bool success) {} + + function setHatStatus( + uint256 _hatId, + bool _newStatus + ) external override returns (bool toggled) {} + + function checkHatStatus( + uint256 _hatId + ) external override returns (bool toggled) {} + + function setHatWearerStatus( + uint256 _hatId, + address _wearer, + bool _eligible, + bool _standing + ) external override returns (bool updated) {} + + function checkHatWearerStatus( + uint256 _hatId, + address _wearer + ) external override returns (bool updated) {} + + function renounceHat(uint256 _hatId) external override {} + + function makeHatImmutable(uint256 _hatId) external override {} + + function changeHatDetails( + uint256 _hatId, + string memory _newDetails + ) external override {} + + function changeHatToggle( + uint256 _hatId, + address _newToggle + ) external override {} + + function changeHatImageURI( + uint256 _hatId, + string memory _newImageURI + ) external override {} + + function changeHatMaxSupply( + uint256 _hatId, + uint32 _newMaxSupply + ) external override {} + + function requestLinkTopHatToTree( + uint32 _topHatId, + uint256 _newAdminHat + ) external override {} + + function approveLinkTopHatToTree( + uint32 _topHatId, + uint256 _newAdminHat, + address _eligibility, + address _toggle, + string calldata _details, + string calldata _imageURI + ) external override {} + + function unlinkTopHatFromTree( + uint32 _topHatId, + address _wearer + ) external override {} + + function relinkTopHatWithinTree( + uint32 _topHatDomain, + uint256 _newAdminHat, + address _eligibility, + address _toggle, + string calldata _details, + string calldata _imageURI + ) external override {} + + function viewHat( + uint256 _hatId + ) + external + view + override + returns ( + string memory _details, + uint32 _maxSupply, + uint32 _supply, + address _eligibility, + address _toggle, + string memory _imageURI, + uint16 _lastHatId, + bool _mutable, + bool _active + ) + {} + + function isAdminOfHat( + address _user, + uint256 _hatId + ) external view override returns (bool isAdmin) {} + + function isInGoodStanding( + address _wearer, + uint256 _hatId + ) external view override returns (bool standing) {} + + function isEligible( + address _wearer, + uint256 _hatId + ) external view override returns (bool eligible) {} + + function getHatToggleModule( + uint256 _hatId + ) external view override returns (address toggle) {} + + function getHatMaxSupply( + uint256 _hatId + ) external view override returns (uint32 maxSupply) {} + + function hatSupply( + uint256 _hatId + ) external view override returns (uint32 supply) {} + + function getImageURIForHat( + uint256 _hatId + ) external view override returns (string memory _uri) {} + + function balanceOf( + address _wearer, + uint256 _hatId + ) external view override returns (uint256 balance) {} + + function balanceOfBatch( + address[] calldata _wearers, + uint256[] calldata _hatIds + ) external view override returns (uint256[] memory) {} + + function uri( + uint256 id + ) external view override returns (string memory _uri) {} } From 71ac995dfac7d2916af553bae5facc57cd7f5f9c Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:18:35 -0400 Subject: [PATCH 053/119] Use "full" IHats interface in DecentAutonomousAdmin and DecentHats_0_2_0 --- contracts/DecentAutonomousAdmin.sol | 2 +- contracts/DecentHats_0_2_0.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 4f48c4df..becb8746 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity =0.8.19; -import {IHats} from "./interfaces/hats/IHats.sol"; +import {IHats} from "./interfaces/hats/full/IHats.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {ISablierV2Lockup} from "./interfaces/sablier/full/ISablierV2Lockup.sol"; diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index f93c06b9..161dfd4c 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -6,7 +6,7 @@ import {IAvatar} from "@gnosis.pm/zodiac/contracts/interfaces/IAvatar.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; -import {IHats} from "./interfaces/hats/IHats.sol"; +import {IHats} from "./interfaces/hats/full/IHats.sol"; import {LockupLinear, Broker} from "./interfaces/sablier/full/types/DataTypes.sol"; import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol"; From f90ac5ac46f7770af415cab71e1f5684e9e30dcf Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:18:45 -0400 Subject: [PATCH 054/119] Revert changes to original IHats interface --- contracts/interfaces/hats/IHats.sol | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/contracts/interfaces/hats/IHats.sol b/contracts/interfaces/hats/IHats.sol index 0f4218b7..c460c46d 100644 --- a/contracts/interfaces/hats/IHats.sol +++ b/contracts/interfaces/hats/IHats.sol @@ -39,15 +39,4 @@ interface IHats { ) external returns (bool success); function transferHat(uint256 _hatId, address _from, address _to) external; - - function getHatEligibilityModule( - uint256 _hatId - ) external view returns (address eligibility); - - function isWearerOfHat( - address _user, - uint256 _hatId - ) external view returns (bool isWearer); - - function changeHatEligibility(uint256 _hatId, address _newEligibility) external; } From 0ce7bbd365a31f0ed250a5b8267e56d5144db63a Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:20:48 -0400 Subject: [PATCH 055/119] Revert change to DecentHats_0_1_0 contract -- this actually does cause a difference in compiled bytecode! --- contracts/DecentHats_0_1_0.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index 5d29a42e..c00dadaa 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -247,4 +247,4 @@ contract DecentHats_0_1_0 { params.hatsProtocol.transferHat(topHatId, address(this), msg.sender); } -} \ No newline at end of file +} From 80981eec889c0bf86653f3380c630314827e05cc Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 15:29:00 -0400 Subject: [PATCH 056/119] Remove unused import from DecentHats_0_2_0 --- contracts/DecentHats_0_2_0.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index 161dfd4c..ad05cea1 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -11,7 +11,6 @@ import {LockupLinear, Broker} from "./interfaces/sablier/full/types/DataTypes.so import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; contract DecentHats_0_2_0 { From ee3478cc5b76e4bc66a2cc7eb02c73ac0849c118 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 17:27:23 -0400 Subject: [PATCH 057/119] Update Sablier contracts to their actual solidity pragma --- contracts/interfaces/sablier/full/IAdminable.sol | 2 +- contracts/interfaces/sablier/full/IERC4096.sol | 2 +- contracts/interfaces/sablier/full/ISablierV2Lockup.sol | 2 +- contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol | 2 +- contracts/interfaces/sablier/full/types/DataTypes.sol | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/interfaces/sablier/full/IAdminable.sol b/contracts/interfaces/sablier/full/IAdminable.sol index e5ee0956..62a13d1a 100644 --- a/contracts/interfaces/sablier/full/IAdminable.sol +++ b/contracts/interfaces/sablier/full/IAdminable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.19; +pragma solidity >=0.8.22; /// @title IAdminable /// @notice Contract module that provides a basic access control mechanism, with an admin that can be diff --git a/contracts/interfaces/sablier/full/IERC4096.sol b/contracts/interfaces/sablier/full/IERC4096.sol index 29bcc6ea..017e3b59 100644 --- a/contracts/interfaces/sablier/full/IERC4096.sol +++ b/contracts/interfaces/sablier/full/IERC4096.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4906.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; diff --git a/contracts/interfaces/sablier/full/ISablierV2Lockup.sol b/contracts/interfaces/sablier/full/ISablierV2Lockup.sol index 8e5b21e3..af593567 100644 --- a/contracts/interfaces/sablier/full/ISablierV2Lockup.sol +++ b/contracts/interfaces/sablier/full/ISablierV2Lockup.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.19; +pragma solidity >=0.8.22; import {IERC4906} from "./IERC4096.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol b/contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol index 49fec926..1d03bc95 100644 --- a/contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol +++ b/contracts/interfaces/sablier/full/ISablierV2NFTDescriptor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.19; +pragma solidity >=0.8.22; import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; diff --git a/contracts/interfaces/sablier/full/types/DataTypes.sol b/contracts/interfaces/sablier/full/types/DataTypes.sol index b7b50c59..c150ccff 100644 --- a/contracts/interfaces/sablier/full/types/DataTypes.sol +++ b/contracts/interfaces/sablier/full/types/DataTypes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.19; +pragma solidity >=0.8.22; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UD2x18} from "@prb/math/src/UD2x18.sol"; From 518e0532cab792bbdbb0e58fd317c1c71d51e774 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 17:24:06 -0400 Subject: [PATCH 058/119] Support solidity version 0.8.28 in hardhat config --- hardhat.config.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 85d222f6..9f896ddf 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -13,13 +13,26 @@ const dummyPrivateKey = const config: HardhatUserConfig = { solidity: { - version: "0.8.19", - settings: { - optimizer: { - enabled: true, - runs: 200, + compilers: [ + { + version: "0.8.19", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, }, - }, + { + version: "0.8.28", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], }, dependencyCompiler: { paths: [ From 6a45c949dde79eae79575064931df30821357be7 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 9 Oct 2024 17:29:49 -0400 Subject: [PATCH 059/119] Use solidity version 0.8.28 for new contracts --- contracts/DecentAutonomousAdmin.sol | 3 ++- contracts/DecentHats_0_2_0.sol | 2 +- contracts/mocks/MockSablierV2LockupLinear.sol | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index becb8746..921f871e 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity 0.8.28; + import {IHats} from "./interfaces/hats/full/IHats.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {ISablierV2Lockup} from "./interfaces/sablier/full/ISablierV2Lockup.sol"; diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats_0_2_0.sol index ad05cea1..29160a68 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats_0_2_0.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity 0.8.28; import {Enum} from "@gnosis.pm/safe-contracts/contracts/common/Enum.sol"; import {IAvatar} from "@gnosis.pm/zodiac/contracts/interfaces/IAvatar.sol"; diff --git a/contracts/mocks/MockSablierV2LockupLinear.sol b/contracts/mocks/MockSablierV2LockupLinear.sol index 99ec6c7b..c4694ed9 100644 --- a/contracts/mocks/MockSablierV2LockupLinear.sol +++ b/contracts/mocks/MockSablierV2LockupLinear.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity 0.8.28; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UD60x18} from "@prb/math/src/UD60x18.sol"; From d0a6e044016bbf82cff15123253843b97e8a9ed6 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:30:54 -0400 Subject: [PATCH 060/119] renaming and move salt to constant --- .../{DecentHats_0_2_0.sol => DecentHats.sol} | 62 +++++-------------- deploy/core/011_deploy_ModuleProxyFactory.ts | 2 +- deploy/core/017_deploy_DecentHats_0_1_0.ts | 16 ++--- ...Hats_0_2_0.ts => 018_deploy_DecentHats.ts} | 2 +- ...tHats_0_2_0.test.ts => DecentHats.test.ts} | 32 +++++----- 5 files changed, 45 insertions(+), 69 deletions(-) rename contracts/{DecentHats_0_2_0.sol => DecentHats.sol} (88%) rename deploy/core/{018_deploy_DecentHats_0_2_0.ts => 018_deploy_DecentHats.ts} (83%) rename test/{DecentHats_0_2_0.test.ts => DecentHats.test.ts} (95%) diff --git a/contracts/DecentHats_0_2_0.sol b/contracts/DecentHats.sol similarity index 88% rename from contracts/DecentHats_0_2_0.sol rename to contracts/DecentHats.sol index 29160a68..7009d80e 100644 --- a/contracts/DecentHats_0_2_0.sol +++ b/contracts/DecentHats.sol @@ -13,8 +13,9 @@ import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol" import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; -contract DecentHats_0_2_0 { - string public constant NAME = "DecentHats_0_2_0"; +contract DecentHats { + string public constant NAME = "DecentHats"; + bytes32 public constant SALT = 0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072; struct SablierStreamParams { address sablierV2LockupLinear; @@ -56,21 +57,20 @@ contract DecentHats_0_2_0 { Hat[] hats; string topHatDetails; string topHatImageURI; + } /* ///////////////////////////////////////////////////////////////////////////// EXTERNAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ function createAndDeclareTree(CreateTreeParams calldata params) public { - bytes32 salt = _getSalt(); (uint256 topHatId, address topHatAccount) = _createTopHatAndAccount( params.hatsProtocol, params.topHatDetails, params.topHatImageURI, params.registry, - params.hatsAccountImplementation, - salt + params.hatsAccountImplementation ); _updateKeyValuePairs(params.keyValuePairs, topHatId); @@ -83,8 +83,7 @@ contract DecentHats_0_2_0 { params.hatsAccountImplementation, topHatAccount, topHatId, - params.adminHat, - salt + params.adminHat ); for (uint256 i = 0; i < params.hats.length; ) { @@ -94,8 +93,7 @@ contract DecentHats_0_2_0 { topHatAccount, params.hatsAccountImplementation, adminHatId, - params.hats[i], - salt + params.hats[i] ); if (params.hats[i].isTermed) { @@ -106,8 +104,7 @@ contract DecentHats_0_2_0 { params.hatsElectionEligibilityImplementation, hatId, topHatId, - params.hats[i].termedParams[0], - uint256(keccak256(abi.encode(salt, hatId))) + params.hats[i].termedParams[0] ); } @@ -123,31 +120,14 @@ contract DecentHats_0_2_0 { INTERAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ - function _getSalt() internal view returns (bytes32 salt) { - uint256 chainId; - assembly { - chainId := chainid() - } - - bytes memory concatenatedSaltInput = abi.encodePacked( - NAME, - chainId, - address(this) - ); - - salt = keccak256(concatenatedSaltInput); - } - function _updateKeyValuePairs( address _keyValuePairs, uint256 topHatId ) internal { - string[] memory keys = new string[](2); - string[] memory values = new string[](2); + string[] memory keys = new string[](1); + string[] memory values = new string[](1); keys[0] = "topHatId"; values[0] = Strings.toString(topHatId); - keys[1] = "decentHatsAddress"; - values[1] = Strings.toHexString(address(this)); IAvatar(msg.sender).execTransactionFromModule( _keyValuePairs, @@ -182,14 +162,13 @@ contract DecentHats_0_2_0 { function _createAccount( IERC6551Registry _registry, address _hatsAccountImplementation, - bytes32 salt, address protocolAddress, uint256 hatId ) internal returns (address) { return _registry.createAccount( _hatsAccountImplementation, - salt, + SALT, block.chainid, protocolAddress, hatId @@ -201,8 +180,7 @@ contract DecentHats_0_2_0 { string memory _topHatDetails, string memory _topHatImageURI, IERC6551Registry _registry, - address _hatsAccountImplementation, - bytes32 salt + address _hatsAccountImplementation ) internal returns (uint256 topHatId, address topHatAccount) { topHatId = _hatsProtocol.mintTopHat( address(this), @@ -213,7 +191,6 @@ contract DecentHats_0_2_0 { topHatAccount = _createAccount( _registry, _hatsAccountImplementation, - salt, address(_hatsProtocol), topHatId ); @@ -225,15 +202,13 @@ contract DecentHats_0_2_0 { address topHatAccount, address hatsAccountImplementation, uint256 adminHatId, - Hat calldata hat, - bytes32 salt + Hat calldata hat ) internal returns (uint256 hatId, address accountAddress) { hatId = _createHat(hatsProtocol, adminHatId, hat, topHatAccount); accountAddress = _createAccount( registry, hatsAccountImplementation, - salt, address(hatsProtocol), hatId ); @@ -293,15 +268,13 @@ contract DecentHats_0_2_0 { address hatsAccountImplementation, address topHatAccount, uint256 topHatId, - Hat calldata hat, - bytes32 salt + Hat calldata hat ) internal returns (uint256 adminHatId, address accountAddress) { adminHatId = _createHat(hatsProtocol, topHatId, hat, topHatAccount); accountAddress = _createAccount( registry, hatsAccountImplementation, - salt, address(hatsProtocol), adminHatId ); @@ -311,7 +284,7 @@ contract DecentHats_0_2_0 { moduleProxyFactory.deployModule( decentAutonomousAdminMasterCopy, abi.encodeWithSignature("setUp()"), - uint256(keccak256(abi.encodePacked(salt, adminHatId))) + uint256(keccak256(abi.encodePacked(SALT, adminHatId))) ) ); } @@ -330,15 +303,14 @@ contract DecentHats_0_2_0 { address hatsElectionEligibilityImplementation, uint256 hatId, uint256 topHatId, - TermedParams calldata termedParams, - uint256 saltNonce + TermedParams calldata termedParams ) internal returns (address) { address electionModuleAddress = hatsModuleFactory.createHatsModule( hatsElectionEligibilityImplementation, hatId, abi.encode(topHatId, uint256(0)), abi.encode(termedParams.termEndDateTs), - saltNonce + uint256(SALT) ); hatsProtocol.changeHatEligibility(hatId, electionModuleAddress); diff --git a/deploy/core/011_deploy_ModuleProxyFactory.ts b/deploy/core/011_deploy_ModuleProxyFactory.ts index 17873611..15494c3a 100644 --- a/deploy/core/011_deploy_ModuleProxyFactory.ts +++ b/deploy/core/011_deploy_ModuleProxyFactory.ts @@ -2,7 +2,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; // import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { +const func: DeployFunction = async (_: HardhatRuntimeEnvironment) => { // No longer deploying ModuleProxyFactory to any new networks.. // This contract is deployed by the Zodiac team. // await deployNonUpgradeable(hre, "ModuleProxyFactory", []); diff --git a/deploy/core/017_deploy_DecentHats_0_1_0.ts b/deploy/core/017_deploy_DecentHats_0_1_0.ts index 6272efd1..3b66e607 100644 --- a/deploy/core/017_deploy_DecentHats_0_1_0.ts +++ b/deploy/core/017_deploy_DecentHats_0_1_0.ts @@ -1,9 +1,11 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; -import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; +import { HardhatRuntimeEnvironment } from "hardhat/types" +import { DeployFunction } from "hardhat-deploy/types" +// import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentHats_0_1_0"); -}; +const func: DeployFunction = async (_: HardhatRuntimeEnvironment) => { + // No longer deploying DecentHats_0_1_0 to any new networks.. + // This contract has been depreciated. + // await deployNonUpgradeable(hre, "DecentHats_0_1_0"); +} -export default func; +export default func diff --git a/deploy/core/018_deploy_DecentHats_0_2_0.ts b/deploy/core/018_deploy_DecentHats.ts similarity index 83% rename from deploy/core/018_deploy_DecentHats_0_2_0.ts rename to deploy/core/018_deploy_DecentHats.ts index 66da759a..a372d413 100644 --- a/deploy/core/018_deploy_DecentHats_0_2_0.ts +++ b/deploy/core/018_deploy_DecentHats.ts @@ -3,7 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types"; import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentHats_0_2_0"); + await deployNonUpgradeable(hre, "DecentHats"); }; export default func; diff --git a/test/DecentHats_0_2_0.test.ts b/test/DecentHats.test.ts similarity index 95% rename from test/DecentHats_0_2_0.test.ts rename to test/DecentHats.test.ts index 0014cc33..a88ef950 100644 --- a/test/DecentHats_0_2_0.test.ts +++ b/test/DecentHats.test.ts @@ -1,14 +1,14 @@ import { GnosisSafeL2, GnosisSafeL2__factory, - DecentHats_0_2_0__factory, + DecentHats__factory, KeyValuePairs, KeyValuePairs__factory, MockHats__factory, ERC6551Registry__factory, MockHatsAccount__factory, ERC6551Registry, - DecentHats_0_2_0, + DecentHats, MockHatsAccount, MockHats, MockSablierV2LockupLinear__factory, @@ -25,7 +25,7 @@ import { import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" import { expect } from "chai" -import { ethers, solidityPackedKeccak256 } from "ethers" +import { ethers, keccak256, solidityPackedKeccak256, toUtf8Bytes } from "ethers" import hre from "hardhat" import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from "./GlobalSafeDeployments.test" @@ -73,7 +73,7 @@ const executeSafeTransaction = async ({ return tx } -describe("DecentHats_0_2_0", () => { +describe.only("DecentHats", () => { let dao: SignerWithAddress let mockHats: MockHats @@ -82,7 +82,7 @@ describe("DecentHats_0_2_0", () => { let keyValuePairs: KeyValuePairs let gnosisSafe: GnosisSafeL2 - let decentHats: DecentHats_0_2_0 + let decentHats: DecentHats let decentHatsAddress: string let gnosisSafeAddress: string @@ -120,7 +120,7 @@ describe("DecentHats_0_2_0", () => { erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy() mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress() - decentHats = await new DecentHats_0_2_0__factory(deployer).deploy() + decentHats = await new DecentHats__factory(deployer).deploy() decentHatsAddress = await decentHats.getAddress() moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() @@ -202,7 +202,7 @@ describe("DecentHats_0_2_0", () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + transactionData: DecentHats__factory.createInterface().encodeFunctionData( "createAndDeclareTree", [ { @@ -264,6 +264,7 @@ describe("DecentHats_0_2_0", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + pepper: keccak256(toUtf8Bytes("pepper")), }, ] ), @@ -294,7 +295,7 @@ describe("DecentHats_0_2_0", () => { createAndDeclareTreeTx2 = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + transactionData: DecentHats__factory.createInterface().encodeFunctionData( "createAndDeclareTree", [ { @@ -304,7 +305,8 @@ describe("DecentHats_0_2_0", () => { keyValuePairs: await keyValuePairs.getAddress(), topHatDetails: "", topHatImageURI: "", - decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + decentAutonomousAdminMasterCopy: + await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, @@ -325,6 +327,7 @@ describe("DecentHats_0_2_0", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + pepper: keccak256(toUtf8Bytes("pepper")), }, ] ), @@ -353,10 +356,7 @@ describe("DecentHats_0_2_0", () => { let salt: string beforeEach(async () => { - salt = solidityPackedKeccak256( - ["string", "uint256", "address"], - ["DecentHats_0_2_0", await hre.getChainId(), decentHatsAddress] - ) + salt = keccak256(toUtf8Bytes("pepper")) }) const getHatAccount = async (hatId: bigint) => { @@ -398,7 +398,7 @@ describe("DecentHats_0_2_0", () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + transactionData: DecentHats__factory.createInterface().encodeFunctionData( "createAndDeclareTree", [ { @@ -475,6 +475,7 @@ describe("DecentHats_0_2_0", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + pepper: keccak256(toUtf8Bytes("pepper")), }, ] ), @@ -540,7 +541,7 @@ describe("DecentHats_0_2_0", () => { await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: DecentHats_0_2_0__factory.createInterface().encodeFunctionData( + transactionData: DecentHats__factory.createInterface().encodeFunctionData( "createAndDeclareTree", [ { @@ -616,6 +617,7 @@ describe("DecentHats_0_2_0", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, + pepper: keccak256(toUtf8Bytes("pepper")), }, ] ), From 58c34688cc8f02017020c6c16af9aa71a7ed0554 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:35:58 -0400 Subject: [PATCH 061/119] update tests to read SALT from contract --- test/DecentHats.test.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index a88ef950..22a361d9 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -73,7 +73,7 @@ const executeSafeTransaction = async ({ return tx } -describe.only("DecentHats", () => { +describe("DecentHats", () => { let dao: SignerWithAddress let mockHats: MockHats @@ -264,7 +264,6 @@ describe.only("DecentHats", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, - pepper: keccak256(toUtf8Bytes("pepper")), }, ] ), @@ -327,7 +326,7 @@ describe.only("DecentHats", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, - pepper: keccak256(toUtf8Bytes("pepper")), + }, ] ), @@ -356,7 +355,7 @@ describe.only("DecentHats", () => { let salt: string beforeEach(async () => { - salt = keccak256(toUtf8Bytes("pepper")) + salt = await decentHats.SALT() }) const getHatAccount = async (hatId: bigint) => { @@ -475,7 +474,7 @@ describe.only("DecentHats", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, - pepper: keccak256(toUtf8Bytes("pepper")), + }, ] ), @@ -617,7 +616,7 @@ describe.only("DecentHats", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, - pepper: keccak256(toUtf8Bytes("pepper")), + }, ] ), From bdd2678e1b62beba30de67af85bf261b0bea7c42 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Fri, 11 Oct 2024 03:03:01 -0400 Subject: [PATCH 062/119] reorder new deploy scripts --- .../core/{018_deploy_DecentHats.ts => 019_deploy_DecentHats.ts} | 0 ...centAutonomousAdmin.ts => 020_deploy_DecentAutonomousAdmin.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename deploy/core/{018_deploy_DecentHats.ts => 019_deploy_DecentHats.ts} (100%) rename deploy/core/{019_deploy_DecentAutonomousAdmin.ts => 020_deploy_DecentAutonomousAdmin.ts} (100%) diff --git a/deploy/core/018_deploy_DecentHats.ts b/deploy/core/019_deploy_DecentHats.ts similarity index 100% rename from deploy/core/018_deploy_DecentHats.ts rename to deploy/core/019_deploy_DecentHats.ts diff --git a/deploy/core/019_deploy_DecentAutonomousAdmin.ts b/deploy/core/020_deploy_DecentAutonomousAdmin.ts similarity index 100% rename from deploy/core/019_deploy_DecentAutonomousAdmin.ts rename to deploy/core/020_deploy_DecentAutonomousAdmin.ts From a05a74ba4ef7915d919f2d30676abacdd5ecc7fa Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:32:44 -0400 Subject: [PATCH 063/119] revert changes made to struct --- contracts/DecentHats.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 7009d80e..3d4dd4bf 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -12,13 +12,15 @@ import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; +import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; contract DecentHats { string public constant NAME = "DecentHats"; - bytes32 public constant SALT = 0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072; + bytes32 public constant SALT = + 0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072; struct SablierStreamParams { - address sablierV2LockupLinear; + ISablierV2LockupLinear sablier; address sender; address asset; LockupLinear.Timestamps timestamps; @@ -57,14 +59,12 @@ contract DecentHats { Hat[] hats; string topHatDetails; string topHatImageURI; - } /* ///////////////////////////////////////////////////////////////////////////// EXTERNAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ function createAndDeclareTree(CreateTreeParams calldata params) public { - (uint256 topHatId, address topHatAccount) = _createTopHatAndAccount( params.hatsProtocol, params.topHatDetails, @@ -225,7 +225,7 @@ contract DecentHats { 0, abi.encodeWithSignature( "approve(address,uint256)", - sablierParams.sablierV2LockupLinear, + sablierParams.sablier, sablierParams.totalAmount ), Enum.Operation.Call @@ -245,7 +245,7 @@ contract DecentHats { // Proxy the Sablier call through IAvatar IAvatar(msg.sender).execTransactionFromModule( - sablierParams.sablierV2LockupLinear, + address(sablierParams.sablier), 0, abi.encodeWithSignature( "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", From cc595bd7bf497cc224ea133bd6a8ec3a10db4dc1 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:54:54 -0400 Subject: [PATCH 064/119] set eligibility module and elect when new term role --- contracts/DecentHats.sol | 69 ++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 3d4dd4bf..952ff5e3 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -87,24 +87,32 @@ contract DecentHats { ); for (uint256 i = 0; i < params.hats.length; ) { - (uint256 hatId, ) = _createHatAndAccountAndMintAndStreams( + address eligibilityAddress = topHatAccount; + if (params.hats[i].isTermed) { + uint256 hatId = params.hatsProtocol.getNextId(adminHatId); + // Create election module and set as eligiblity, elect, and start next term + eligibilityAddress = _createElectionModule( + params.hatsModuleFactory, + params.hatsElectionEligibilityImplementation, + hatId, + topHatId, + params.hats[i].termedParams[0] + ); + } + _createHatAndAccountAndMintAndStreams( params.hatsProtocol, params.registry, topHatAccount, params.hatsAccountImplementation, adminHatId, - params.hats[i] + params.hats[i], + eligibilityAddress ); if (params.hats[i].isTermed) { - // Create election module and set as eligiblity, elect, and start next term - _createElectionModuleAndExecuteFirstTerm( - params.hatsProtocol, - params.hatsModuleFactory, - params.hatsElectionEligibilityImplementation, - hatId, - topHatId, - params.hats[i].termedParams[0] + IHatsElectionEligibility(eligibilityAddress).elect( + params.hats[i].termedParams[0].termEndDateTs, + params.hats[i].termedParams[0].nominatedWearers ); } @@ -145,15 +153,16 @@ contract DecentHats { IHats _hatsProtocol, uint256 adminHatId, Hat memory _hat, - address topHatAccount + address toggle, + address eligibility ) internal returns (uint256) { return _hatsProtocol.createHat( adminHatId, _hat.details, _hat.maxSupply, - topHatAccount, - topHatAccount, + eligibility, + toggle, _hat.isMutable, _hat.imageURI ); @@ -202,9 +211,16 @@ contract DecentHats { address topHatAccount, address hatsAccountImplementation, uint256 adminHatId, - Hat calldata hat + Hat calldata hat, + address eligibilityAddress ) internal returns (uint256 hatId, address accountAddress) { - hatId = _createHat(hatsProtocol, adminHatId, hat, topHatAccount); + hatId = _createHat( + hatsProtocol, + adminHatId, + hat, + topHatAccount, + eligibilityAddress + ); accountAddress = _createAccount( registry, @@ -270,7 +286,13 @@ contract DecentHats { uint256 topHatId, Hat calldata hat ) internal returns (uint256 adminHatId, address accountAddress) { - adminHatId = _createHat(hatsProtocol, topHatId, hat, topHatAccount); + adminHatId = _createHat( + hatsProtocol, + topHatId, + hat, + topHatAccount, + topHatAccount + ); accountAddress = _createAccount( registry, @@ -297,28 +319,19 @@ contract DecentHats { return abi.encodePacked(bytecode, constructorArgs); } - function _createElectionModuleAndExecuteFirstTerm( - IHats hatsProtocol, + function _createElectionModule( IHatsModuleFactory hatsModuleFactory, address hatsElectionEligibilityImplementation, uint256 hatId, uint256 topHatId, TermedParams calldata termedParams - ) internal returns (address) { - address electionModuleAddress = hatsModuleFactory.createHatsModule( + ) internal returns (address electionModuleAddress) { + electionModuleAddress = hatsModuleFactory.createHatsModule( hatsElectionEligibilityImplementation, hatId, abi.encode(topHatId, uint256(0)), abi.encode(termedParams.termEndDateTs), uint256(SALT) ); - hatsProtocol.changeHatEligibility(hatId, electionModuleAddress); - - IHatsElectionEligibility(electionModuleAddress).elect( - termedParams.termEndDateTs, - termedParams.nominatedWearers - ); - - return electionModuleAddress; } } From 80a133110054127d3bc87fb9ea6255b8fec14371 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:56:17 -0400 Subject: [PATCH 065/119] fix tests --- test/DecentHats.test.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index 22a361d9..e3aa14f2 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -326,7 +326,6 @@ describe("DecentHats", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, - }, ] ), @@ -433,7 +432,7 @@ describe("DecentHats", () => { wearer: ethers.ZeroAddress, sablierParams: [ { - sablierV2LockupLinear: mockSablierAddress, + sablier: mockSablierAddress, sender: gnosisSafeAddress, totalAmount: ethers.parseEther("100"), asset: mockERC20Address, @@ -474,7 +473,6 @@ describe("DecentHats", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, - }, ] ), @@ -576,7 +574,7 @@ describe("DecentHats", () => { wearer: ethers.ZeroAddress, sablierParams: [ { - sablierV2LockupLinear: mockSablierAddress, + sablier: mockSablierAddress, sender: gnosisSafeAddress, totalAmount: ethers.parseEther("100"), asset: mockERC20Address, @@ -590,7 +588,7 @@ describe("DecentHats", () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, { - sablierV2LockupLinear: mockSablierAddress, + sablier: mockSablierAddress, sender: gnosisSafeAddress, totalAmount: ethers.parseEther("50"), asset: mockERC20Address, @@ -616,7 +614,6 @@ describe("DecentHats", () => { hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, - }, ] ), From 075a281442a4f32ba79721491528958762807fc2 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:58:08 -0400 Subject: [PATCH 066/119] remove flushing of payments when triggering new term --- contracts/DecentAutonomousAdmin.sol | 74 +---------------------------- 1 file changed, 2 insertions(+), 72 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 921f871e..f8fb712a 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -18,7 +18,6 @@ contract DecentAutonomousAdmin { IHats userHatProtocol; uint256 userHatId; address nominatedWearer; - SablierStreamInfo[] sablierStreamInfo; } // ////////////////////////////////////////////////////////////// @@ -48,83 +47,14 @@ contract DecentAutonomousAdmin { hatsElectionModule.startNextTerm(); // transfer user hat to self - args.userHatProtocol.transferHat( + args.userHatProtocol.checkHatWearerStatus( args.userHatId, - args.currentWearer, - address(this) - ); - - // for each withdrawable stream, withdraw funds to current wearer of hat - _flushUnclaimedFunds( - _getStreamsWithUnclaimedFunds(args.sablierStreamInfo), args.currentWearer ); - - // transfer hat to nominated wearer - args.userHatProtocol.transferHat( - args.userHatId, - address(this), - args.nominatedWearer - ); + args.userHatProtocol.mintHat(args.userHatId, args.nominatedWearer); } // ////////////////////////////////////////////////////////////// // Internal Functions // ////////////////////////////////////////////////////////////// - - /** - * @dev Withdraws unclaimed funds from Sablier streams. - * @param _sablierStreamInfo SablierStreamInfo array - */ - function _flushUnclaimedFunds( - SablierStreamInfo[] memory _sablierStreamInfo, - address withdrawTo - ) internal { - for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { - _sablierStreamInfo[i].sablierV2Lockup.withdrawMax( - _sablierStreamInfo[i].streamId, - withdrawTo - ); - } - } - - /** - * @dev Returns an array of Sablier stream ids that have unclaimed funds. - * @param _sablierStreamInfo SablierStreamInfo array - * @return streamsWithUnclaimedFunds An array of SablierStreamInfo that have unclaimed funds - */ - function _getStreamsWithUnclaimedFunds( - SablierStreamInfo[] memory _sablierStreamInfo - ) internal view returns (SablierStreamInfo[] memory) { - uint256 streamsWithUnclaimedFundsCount = 0; - - for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { - uint128 withdrawableAmount = _sablierStreamInfo[i] - .sablierV2Lockup - .withdrawableAmountOf(_sablierStreamInfo[i].streamId); - - if (withdrawableAmount > 0) { - streamsWithUnclaimedFundsCount++; - } - } - - SablierStreamInfo[] - memory streamsWithUnclaimedFunds = new SablierStreamInfo[]( - streamsWithUnclaimedFundsCount - ); - uint256 index = 0; - - for (uint256 i = 0; i < _sablierStreamInfo.length; i++) { - uint128 withdrawableAmount = _sablierStreamInfo[i] - .sablierV2Lockup - .withdrawableAmountOf(_sablierStreamInfo[i].streamId); - - if (withdrawableAmount > 0) { - streamsWithUnclaimedFunds[index] = _sablierStreamInfo[i]; - index++; - } - } - - return streamsWithUnclaimedFunds; - } } From 0c9963bd154c2b73b32da870ec3f8c28dee44f8f Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 19 Oct 2024 04:07:00 -0400 Subject: [PATCH 067/119] termed role payments are direct to nominated wearer --- contracts/DecentHats.sol | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 952ff5e3..48b44cca 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -109,13 +109,6 @@ contract DecentHats { eligibilityAddress ); - if (params.hats[i].isTermed) { - IHatsElectionEligibility(eligibilityAddress).elect( - params.hats[i].termedParams[0].termEndDateTs, - params.hats[i].termedParams[0].nominatedWearers - ); - } - unchecked { ++i; } @@ -221,13 +214,22 @@ contract DecentHats { topHatAccount, eligibilityAddress ); + if (!hat.isTermed) { + accountAddress = _createAccount( + registry, + hatsAccountImplementation, + address(hatsProtocol), + hatId + ); + } else { + IHatsElectionEligibility(eligibilityAddress).elect( + hat.termedParams[0].termEndDateTs, + hat.termedParams[0].nominatedWearers + ); + // Payments are made directly to the hat wearer + accountAddress = hat.wearer; + } - accountAddress = _createAccount( - registry, - hatsAccountImplementation, - address(hatsProtocol), - hatId - ); if (hat.wearer != address(0)) { hatsProtocol.mintHat(hatId, hat.wearer); } From 09cf6c952b0c21359137dcf17714921b7a100e8c Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:29:07 -0400 Subject: [PATCH 068/119] clean up - seperate out logic for minting termed roles --- contracts/DecentHats.sol | 142 +++++++++++++++++++++++++++------------ 1 file changed, 99 insertions(+), 43 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 48b44cca..4f1e931e 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -87,27 +87,33 @@ contract DecentHats { ); for (uint256 i = 0; i < params.hats.length; ) { - address eligibilityAddress = topHatAccount; if (params.hats[i].isTermed) { uint256 hatId = params.hatsProtocol.getNextId(adminHatId); - // Create election module and set as eligiblity, elect, and start next term - eligibilityAddress = _createElectionModule( - params.hatsModuleFactory, - params.hatsElectionEligibilityImplementation, - hatId, - topHatId, - params.hats[i].termedParams[0] + + // Create election module and set as eligiblity + _createTermedHatAndAccountAndMintAndStreams( + params.hatsProtocol, + topHatAccount, + _createElectionEligiblityModule( + params.hatsModuleFactory, + params.hatsElectionEligibilityImplementation, + hatId, + topHatId, + params.hats[i].termedParams[0] + ), + adminHatId, + params.hats[i] + ); + } else { + _createHatAndAccountAndMintAndStreams( + params.hatsProtocol, + params.registry, + topHatAccount, + params.hatsAccountImplementation, + adminHatId, + params.hats[i] ); } - _createHatAndAccountAndMintAndStreams( - params.hatsProtocol, - params.registry, - topHatAccount, - params.hatsAccountImplementation, - adminHatId, - params.hats[i], - eligibilityAddress - ); unchecked { ++i; @@ -204,31 +210,89 @@ contract DecentHats { address topHatAccount, address hatsAccountImplementation, uint256 adminHatId, - Hat calldata hat, - address eligibilityAddress + Hat calldata hat ) internal returns (uint256 hatId, address accountAddress) { hatId = _createHat( hatsProtocol, adminHatId, hat, topHatAccount, - eligibilityAddress + topHatAccount ); - if (!hat.isTermed) { - accountAddress = _createAccount( - registry, - hatsAccountImplementation, - address(hatsProtocol), - hatId + accountAddress = _createAccount( + registry, + hatsAccountImplementation, + address(hatsProtocol), + hatId + ); + + if (hat.wearer != address(0)) { + hatsProtocol.mintHat(hatId, hat.wearer); + } + + for (uint256 i = 0; i < hat.sablierParams.length; ) { + SablierStreamParams memory sablierParams = hat.sablierParams[i]; + + // Approve tokens for Sablier + IAvatar(msg.sender).execTransactionFromModule( + sablierParams.asset, + 0, + abi.encodeWithSignature( + "approve(address,uint256)", + sablierParams.sablier, + sablierParams.totalAmount + ), + Enum.Operation.Call ); - } else { - IHatsElectionEligibility(eligibilityAddress).elect( - hat.termedParams[0].termEndDateTs, - hat.termedParams[0].nominatedWearers + + LockupLinear.CreateWithTimestamps memory params = LockupLinear + .CreateWithTimestamps({ + sender: sablierParams.sender, + recipient: accountAddress, + totalAmount: sablierParams.totalAmount, + asset: IERC20(sablierParams.asset), + cancelable: sablierParams.cancelable, + transferable: sablierParams.transferable, + timestamps: sablierParams.timestamps, + broker: sablierParams.broker + }); + + // Proxy the Sablier call through IAvatar + IAvatar(msg.sender).execTransactionFromModule( + address(sablierParams.sablier), + 0, + abi.encodeWithSignature( + "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", + params + ), + Enum.Operation.Call ); - // Payments are made directly to the hat wearer - accountAddress = hat.wearer; + + unchecked { + ++i; + } } + } + + function _createTermedHatAndAccountAndMintAndStreams( + IHats hatsProtocol, + address topHatAccount, + address eligibilityAddress, + uint256 adminHatId, + Hat calldata hat + ) internal { + uint256 hatId = _createHat( + hatsProtocol, + adminHatId, + hat, + topHatAccount, + eligibilityAddress + ); + + IHatsElectionEligibility(eligibilityAddress).elect( + hat.termedParams[0].termEndDateTs, + hat.termedParams[0].nominatedWearers + ); if (hat.wearer != address(0)) { hatsProtocol.mintHat(hatId, hat.wearer); @@ -252,7 +316,7 @@ contract DecentHats { LockupLinear.CreateWithTimestamps memory params = LockupLinear .CreateWithTimestamps({ sender: sablierParams.sender, - recipient: accountAddress, + recipient: hat.wearer, totalAmount: sablierParams.totalAmount, asset: IERC20(sablierParams.asset), cancelable: sablierParams.cancelable, @@ -307,21 +371,13 @@ contract DecentHats { adminHatId, moduleProxyFactory.deployModule( decentAutonomousAdminMasterCopy, - abi.encodeWithSignature("setUp()"), + abi.encodeWithSignature("setUp(bytes)", bytes("")), uint256(keccak256(abi.encodePacked(SALT, adminHatId))) ) ); } - function _getCreationCode( - uint256 _adminHatId - ) internal pure returns (bytes memory) { - bytes memory bytecode = type(DecentAutonomousAdmin).creationCode; - bytes memory constructorArgs = abi.encode(_adminHatId); - return abi.encodePacked(bytecode, constructorArgs); - } - - function _createElectionModule( + function _createElectionEligiblityModule( IHatsModuleFactory hatsModuleFactory, address hatsElectionEligibilityImplementation, uint256 hatId, From e56fede0b432a6673d7b7ebb6e3f6a0f0ae96349 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:29:26 -0400 Subject: [PATCH 069/119] make factoryFriendly --- contracts/DecentAutonomousAdmin.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index f8fb712a..31030b5b 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -4,10 +4,11 @@ pragma solidity 0.8.28; import {IHats} from "./interfaces/hats/full/IHats.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {ISablierV2Lockup} from "./interfaces/sablier/full/ISablierV2Lockup.sol"; +import {FactoryFriendly} from "@gnosis.pm/zodiac/contracts/factory/FactoryFriendly.sol"; -contract DecentAutonomousAdmin { +contract DecentAutonomousAdmin is FactoryFriendly { string public constant NAME = "DecentAutonomousAdmin"; - string public version_ = "0.1.0"; + string public constant version_ = "0.1.0"; struct SablierStreamInfo { uint256 streamId; @@ -23,7 +24,7 @@ contract DecentAutonomousAdmin { // ////////////////////////////////////////////////////////////// // initializer // ////////////////////////////////////////////////////////////// - function setUp() public {} + function setUp(bytes memory initializeParams) public override initializer {} // ////////////////////////////////////////////////////////////// // Public Functions From ffcf907e75b66f86487110254766c8766629538b Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:34:55 -0400 Subject: [PATCH 070/119] revert changes --- contracts/mocks/MockSablierV2LockupLinear.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mocks/MockSablierV2LockupLinear.sol b/contracts/mocks/MockSablierV2LockupLinear.sol index 327c0eeb..9926a994 100644 --- a/contracts/mocks/MockSablierV2LockupLinear.sol +++ b/contracts/mocks/MockSablierV2LockupLinear.sol @@ -153,4 +153,4 @@ contract MockSablierV2LockupLinear { return MockLockupLinear.Status.SETTLED; } } -} \ No newline at end of file +} From 78e415139e9666b9ac7db1078fbd9bf0f1288aae Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:36:28 -0400 Subject: [PATCH 071/119] revert changes --- deploy/core/011_deploy_ModuleProxyFactory.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/core/011_deploy_ModuleProxyFactory.ts b/deploy/core/011_deploy_ModuleProxyFactory.ts index 15494c3a..cc106be9 100644 --- a/deploy/core/011_deploy_ModuleProxyFactory.ts +++ b/deploy/core/011_deploy_ModuleProxyFactory.ts @@ -1,11 +1,11 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; +// import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types" // import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; -const func: DeployFunction = async (_: HardhatRuntimeEnvironment) => { +const func: DeployFunction = async (/* hre: HardhatRuntimeEnvironment */) => { // No longer deploying ModuleProxyFactory to any new networks.. // This contract is deployed by the Zodiac team. // await deployNonUpgradeable(hre, "ModuleProxyFactory", []); -}; +} -export default func; +export default func From f424617ec9e081a0a8f528ec2511ddb50a62e39a Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:37:53 -0400 Subject: [PATCH 072/119] change for consistency --- deploy/core/017_deploy_DecentHats_0_1_0.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/core/017_deploy_DecentHats_0_1_0.ts b/deploy/core/017_deploy_DecentHats_0_1_0.ts index 3b66e607..75de4306 100644 --- a/deploy/core/017_deploy_DecentHats_0_1_0.ts +++ b/deploy/core/017_deploy_DecentHats_0_1_0.ts @@ -1,8 +1,8 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types" +// import { HardhatRuntimeEnvironment } from "hardhat/types" import { DeployFunction } from "hardhat-deploy/types" // import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; -const func: DeployFunction = async (_: HardhatRuntimeEnvironment) => { +const func: DeployFunction = async (/* hre: HardhatRuntimeEnvironment */) => { // No longer deploying DecentHats_0_1_0 to any new networks.. // This contract has been depreciated. // await deployNonUpgradeable(hre, "DecentHats_0_1_0"); From 0b6c791cbea894aff60daa82889644d01e807e58 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:38:33 -0400 Subject: [PATCH 073/119] revert change --- test/DecentHats_0_1_0.test.ts | 679 ++++++++++++++++------------------ 1 file changed, 315 insertions(+), 364 deletions(-) diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index c75a6c2b..722b8aa0 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -15,71 +15,61 @@ import { MockSablierV2LockupLinear, MockERC20__factory, MockERC20, -} from "../typechain-types"; +} from "../typechain-types" -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "ethers"; -import hre from "hardhat"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" +import { expect } from "chai" +import { ethers } from "ethers" +import hre from "hardhat" -import { - getGnosisSafeL2Singleton, - getGnosisSafeProxyFactory, -} from "./GlobalSafeDeployments.test"; -import { - executeSafeTransaction, - getHatAccount, - predictGnosisSafeAddress, -} from "./helpers"; +import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from "./GlobalSafeDeployments.test" +import { executeSafeTransaction, getHatAccount, predictGnosisSafeAddress } from "./helpers" describe("DecentHats_0_1_0", () => { - let dao: SignerWithAddress; + let dao: SignerWithAddress - let mockHats: MockHats; - let mockHatsAddress: string; + let mockHats: MockHats + let mockHatsAddress: string - let keyValuePairs: KeyValuePairs; - let gnosisSafe: GnosisSafeL2; + let keyValuePairs: KeyValuePairs + let gnosisSafe: GnosisSafeL2 - let decentHats: DecentHats_0_1_0; - let decentHatsAddress: string; + let decentHats: DecentHats_0_1_0 + let decentHatsAddress: string - let gnosisSafeAddress: string; - let erc6551Registry: ERC6551Registry; + let gnosisSafeAddress: string + let erc6551Registry: ERC6551Registry - let mockHatsAccountImplementation: MockHatsAccount; - let mockHatsAccountImplementationAddress: string; + let mockHatsAccountImplementation: MockHatsAccount + let mockHatsAccountImplementationAddress: string - let mockSablier: MockSablierV2LockupLinear; - let mockSablierAddress: string; + let mockSablier: MockSablierV2LockupLinear + let mockSablierAddress: string - let mockERC20: MockERC20; - let mockERC20Address: string; + let mockERC20: MockERC20 + let mockERC20Address: string beforeEach(async () => { - const signers = await hre.ethers.getSigners(); - const [deployer] = signers; - [, dao] = signers; - - mockHats = await new MockHats__factory(deployer).deploy(); - mockHatsAddress = await mockHats.getAddress(); - keyValuePairs = await new KeyValuePairs__factory(deployer).deploy(); - erc6551Registry = await new ERC6551Registry__factory(deployer).deploy(); - mockHatsAccountImplementation = await new MockHatsAccount__factory( - deployer - ).deploy(); - mockHatsAccountImplementationAddress = - await mockHatsAccountImplementation.getAddress(); - decentHats = await new DecentHats_0_1_0__factory(deployer).deploy(); - decentHatsAddress = await decentHats.getAddress(); - - const gnosisSafeProxyFactory = getGnosisSafeProxyFactory(); - const gnosisSafeL2Singleton = getGnosisSafeL2Singleton(); - const gnosisSafeL2SingletonAddress = - await gnosisSafeL2Singleton.getAddress(); - - const createGnosisSetupCalldata = - GnosisSafeL2__factory.createInterface().encodeFunctionData("setup", [ + const signers = await hre.ethers.getSigners() + const [deployer] = signers + ;[, dao] = signers + + mockHats = await new MockHats__factory(deployer).deploy() + mockHatsAddress = await mockHats.getAddress() + keyValuePairs = await new KeyValuePairs__factory(deployer).deploy() + erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() + mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy() + mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress() + decentHats = await new DecentHats_0_1_0__factory(deployer).deploy() + decentHatsAddress = await decentHats.getAddress() + + const gnosisSafeProxyFactory = getGnosisSafeProxyFactory() + const gnosisSafeL2Singleton = getGnosisSafeL2Singleton() + const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress() + + const createGnosisSetupCalldata = GnosisSafeL2__factory.createInterface().encodeFunctionData( + "setup", + [ [dao.address], 1, hre.ethers.ZeroAddress, @@ -88,94 +78,91 @@ describe("DecentHats_0_1_0", () => { hre.ethers.ZeroAddress, 0, hre.ethers.ZeroAddress, - ]); + ] + ) - const saltNum = BigInt( - `0x${Buffer.from(hre.ethers.randomBytes(32)).toString("hex")}` - ); + const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString("hex")}`) const predictedGnosisSafeAddress = await predictGnosisSafeAddress( createGnosisSetupCalldata, saltNum, gnosisSafeL2SingletonAddress, gnosisSafeProxyFactory - ); - gnosisSafeAddress = predictedGnosisSafeAddress; + ) + gnosisSafeAddress = predictedGnosisSafeAddress await gnosisSafeProxyFactory.createProxyWithNonce( gnosisSafeL2SingletonAddress, createGnosisSetupCalldata, saltNum - ); + ) - gnosisSafe = GnosisSafeL2__factory.connect( - predictedGnosisSafeAddress, - deployer - ); + gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer) // Deploy MockSablierV2LockupLinear - mockSablier = await new MockSablierV2LockupLinear__factory( - deployer - ).deploy(); - mockSablierAddress = await mockSablier.getAddress(); + mockSablier = await new MockSablierV2LockupLinear__factory(deployer).deploy() + mockSablierAddress = await mockSablier.getAddress() - mockERC20 = await new MockERC20__factory(deployer).deploy( - "MockERC20", - "MCK" - ); - mockERC20Address = await mockERC20.getAddress(); + mockERC20 = await new MockERC20__factory(deployer).deploy("MockERC20", "MCK") + mockERC20Address = await mockERC20.getAddress() - await mockERC20.mint(gnosisSafeAddress, ethers.parseEther("1000000")); - }); + await mockERC20.mint(gnosisSafeAddress, ethers.parseEther("1000000")) + }) describe("DecentHats", () => { - let enableModuleTx: ethers.ContractTransactionResponse; + let enableModuleTx: ethers.ContractTransactionResponse beforeEach(async () => { enableModuleTx = await executeSafeTransaction({ safe: gnosisSafe, to: gnosisSafeAddress, - transactionData: - GnosisSafeL2__factory.createInterface().encodeFunctionData( - "enableModule", - [decentHatsAddress] - ), + transactionData: GnosisSafeL2__factory.createInterface().encodeFunctionData( + "enableModule", + [decentHatsAddress] + ), signers: [dao], - }); - }); + }) + }) describe("Enabled as a module", () => { it("Emits an ExecutionSuccess event", async () => { - await expect(enableModuleTx).to.emit(gnosisSafe, "ExecutionSuccess"); - }); + await expect(enableModuleTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an EnabledModule event", async () => { await expect(enableModuleTx) .to.emit(gnosisSafe, "EnabledModule") - .withArgs(decentHatsAddress); - }); - }); + .withArgs(decentHatsAddress) + }) + }) describe("Creating a new Top Hat and Tree", () => { - let createAndDeclareTreeTx: ethers.ContractTransactionResponse; + let createAndDeclareTreeTx: ethers.ContractTransactionResponse beforeEach(async () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + }, + hats: [ + { maxSupply: 1, details: "", imageURI: "", @@ -183,108 +170,91 @@ describe("DecentHats_0_1_0", () => { wearer: ethers.ZeroAddress, sablierParams: [], }, - hats: [ - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - ], - }, - ] - ), + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + }, + ], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx).to.emit( - gnosisSafe, - "ExecutionSuccess" - ); - }); + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an ExecutionFromModuleSuccess event", async () => { await expect(createAndDeclareTreeTx) .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress); - }); + .withArgs(decentHatsAddress) + }) it("Emits some hatsTreeId ValueUpdated events", async () => { await expect(createAndDeclareTreeTx) .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "0"); - }); + .withArgs(gnosisSafeAddress, "topHatId", "0") + }) describe("Multiple calls", () => { - let createAndDeclareTreeTx2: ethers.ContractTransactionResponse; + let createAndDeclareTreeTx2: ethers.ContractTransactionResponse beforeEach(async () => { createAndDeclareTreeTx2 = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - hats: [], + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], }, - ] - ), + hats: [], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx2).to.emit( - gnosisSafe, - "ExecutionSuccess" - ); - }); + await expect(createAndDeclareTreeTx2).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an ExecutionFromModuleSuccess event", async () => { await expect(createAndDeclareTreeTx2) .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress); - }); + .withArgs(decentHatsAddress) + }) it("Creates Top Hats with sequential IDs", async () => { await expect(createAndDeclareTreeTx2) .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "4"); - }); - }); + .withArgs(gnosisSafeAddress, "topHatId", "4") + }) + }) describe("Creating Hats Accounts", () => { it("Generates the correct Addresses for the current Hats", async () => { - const currentCount = await mockHats.count(); + const currentCount = await mockHats.count() for (let i = 0n; i < currentCount; i++) { const topHatAccount = await getHatAccount( @@ -292,41 +262,68 @@ describe("DecentHats_0_1_0", () => { erc6551Registry, mockHatsAccountImplementationAddress, mockHatsAddress - ); + ) - expect(await topHatAccount.tokenId()).eq(i); - expect(await topHatAccount.tokenImplementation()).eq( - mockHatsAddress - ); + expect(await topHatAccount.tokenId()).eq(i) + expect(await topHatAccount.tokenImplementation()).eq(mockHatsAddress) } - }); - }); - }); + }) + }) + }) describe("Creating a new Top Hat and Tree with Sablier Streams", () => { - let createAndDeclareTreeTx: ethers.ContractTransactionResponse; - let currentBlockTimestamp: number; + let createAndDeclareTreeTx: ethers.ContractTransactionResponse + let currentBlockTimestamp: number beforeEach(async () => { - currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))! - .timestamp; + currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + }, + hats: [ + { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("100"), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: 0, + end: currentBlockTimestamp + 2592000, // 30 days from now + }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + }, + { maxSupply: 1, details: "", imageURI: "", @@ -334,228 +331,182 @@ describe("DecentHats_0_1_0", () => { wearer: ethers.ZeroAddress, sablierParams: [], }, - hats: [ - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [ - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("100"), - asset: mockERC20Address, - cancelable: true, - transferable: false, - timestamps: { - start: currentBlockTimestamp, - cliff: 0, - end: currentBlockTimestamp + 2592000, // 30 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, - }, - ], - }, - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - }, - ], - }, - ] - ), + ], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx).to.emit( - gnosisSafe, - "ExecutionSuccess" - ); - }); + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") + }) it("Emits an ExecutionFromModuleSuccess event", async () => { await expect(createAndDeclareTreeTx) .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress); - }); + .withArgs(decentHatsAddress) + }) it("Emits some hatsTreeId ValueUpdated events", async () => { await expect(createAndDeclareTreeTx) .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "0"); - }); + .withArgs(gnosisSafeAddress, "topHatId", "0") + }) it("Creates a Sablier stream for the hat with stream parameters", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(1); + ) + expect(streamCreatedEvents.length).to.equal(1) - const event = streamCreatedEvents[0]; - expect(event.args.sender).to.equal(gnosisSafeAddress); - expect(event.args.recipient).to.not.equal(ethers.ZeroAddress); - expect(event.args.totalAmount).to.equal(ethers.parseEther("100")); - }); + const event = streamCreatedEvents[0] + expect(event.args.sender).to.equal(gnosisSafeAddress) + expect(event.args.recipient).to.not.equal(ethers.ZeroAddress) + expect(event.args.totalAmount).to.equal(ethers.parseEther("100")) + }) it("Does not create a Sablier stream for hats without stream parameters", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(1); // Only one stream should be created - }); + ) + expect(streamCreatedEvents.length).to.equal(1) // Only one stream should be created + }) it("Creates a Sablier stream with correct timestamps", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(1); + ) + expect(streamCreatedEvents.length).to.equal(1) - const streamId = streamCreatedEvents[0].args.streamId; - const stream = await mockSablier.getStream(streamId); + const streamId = streamCreatedEvents[0].args.streamId + const stream = await mockSablier.getStream(streamId) - expect(stream.startTime).to.equal(currentBlockTimestamp); - expect(stream.endTime).to.equal(currentBlockTimestamp + 2592000); - }); - }); + expect(stream.startTime).to.equal(currentBlockTimestamp) + expect(stream.endTime).to.equal(currentBlockTimestamp + 2592000) + }) + }) describe("Creating a new Top Hat and Tree with Multiple Sablier Streams per Hat", () => { - let currentBlockTimestamp: number; + let currentBlockTimestamp: number beforeEach(async () => { - currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))! - .timestamp; + currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, - transactionData: - DecentHats_0_1_0__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: - mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", - adminHat: { + transactionData: DecentHats_0_1_0__factory.createInterface().encodeFunctionData( + "createAndDeclareTree", + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: "", + topHatImageURI: "", + adminHat: { + maxSupply: 1, + details: "", + imageURI: "", + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + }, + hats: [ + { maxSupply: 1, details: "", imageURI: "", isMutable: false, wearer: ethers.ZeroAddress, - sablierParams: [], - }, - hats: [ - { - maxSupply: 1, - details: "", - imageURI: "", - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [ - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("100"), - asset: mockERC20Address, - cancelable: true, - transferable: false, - timestamps: { - start: currentBlockTimestamp, - cliff: currentBlockTimestamp + 86400, // 1 day cliff - end: currentBlockTimestamp + 2592000, // 30 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("100"), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: currentBlockTimestamp + 86400, // 1 day cliff + end: currentBlockTimestamp + 2592000, // 30 days from now }, - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("50"), - asset: mockERC20Address, - cancelable: false, - transferable: true, - timestamps: { - start: currentBlockTimestamp, - cliff: 0, // No cliff - end: currentBlockTimestamp + 1296000, // 15 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther("50"), + asset: mockERC20Address, + cancelable: false, + transferable: true, + timestamps: { + start: currentBlockTimestamp, + cliff: 0, // No cliff + end: currentBlockTimestamp + 1296000, // 15 days from now }, - ], - }, - ], - }, - ] - ), + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + }, + ], + }, + ] + ), signers: [dao], - }); - }); + }) + }) it("Creates multiple Sablier streams for a single hat", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - expect(streamCreatedEvents.length).to.equal(2); + ) + expect(streamCreatedEvents.length).to.equal(2) - const event1 = streamCreatedEvents[0]; - expect(event1.args.sender).to.equal(gnosisSafeAddress); - expect(event1.args.recipient).to.not.equal(ethers.ZeroAddress); - expect(event1.args.totalAmount).to.equal(ethers.parseEther("100")); + const event1 = streamCreatedEvents[0] + expect(event1.args.sender).to.equal(gnosisSafeAddress) + expect(event1.args.recipient).to.not.equal(ethers.ZeroAddress) + expect(event1.args.totalAmount).to.equal(ethers.parseEther("100")) - const event2 = streamCreatedEvents[1]; - expect(event2.args.sender).to.equal(gnosisSafeAddress); - expect(event2.args.recipient).to.equal(event1.args.recipient); - expect(event2.args.totalAmount).to.equal(ethers.parseEther("50")); - }); + const event2 = streamCreatedEvents[1] + expect(event2.args.sender).to.equal(gnosisSafeAddress) + expect(event2.args.recipient).to.equal(event1.args.recipient) + expect(event2.args.totalAmount).to.equal(ethers.parseEther("50")) + }) it("Creates streams with correct parameters", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - - const stream1 = await mockSablier.getStream( - streamCreatedEvents[0].args.streamId - ); - expect(stream1.cancelable).to.be.true; - expect(stream1.transferable).to.be.false; - expect(stream1.endTime - stream1.startTime).to.equal(2592000); - - const stream2 = await mockSablier.getStream( - streamCreatedEvents[1].args.streamId - ); - expect(stream2.cancelable).to.be.false; - expect(stream2.transferable).to.be.true; - expect(stream2.endTime - stream2.startTime).to.equal(1296000); - }); + ) + + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) + expect(stream1.cancelable).to.be.true + expect(stream1.transferable).to.be.false + expect(stream1.endTime - stream1.startTime).to.equal(2592000) + + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) + expect(stream2.cancelable).to.be.false + expect(stream2.transferable).to.be.true + expect(stream2.endTime - stream2.startTime).to.equal(1296000) + }) it("Creates streams with correct timestamps", async () => { const streamCreatedEvents = await mockSablier.queryFilter( mockSablier.filters.StreamCreated() - ); - - const stream1 = await mockSablier.getStream( - streamCreatedEvents[0].args.streamId - ); - expect(stream1.startTime).to.equal(currentBlockTimestamp); - expect(stream1.endTime).to.equal(currentBlockTimestamp + 2592000); - - const stream2 = await mockSablier.getStream( - streamCreatedEvents[1].args.streamId - ); - expect(stream2.startTime).to.equal(currentBlockTimestamp); - expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000); - }); - }); - }); -}); \ No newline at end of file + ) + + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) + expect(stream1.startTime).to.equal(currentBlockTimestamp) + expect(stream1.endTime).to.equal(currentBlockTimestamp + 2592000) + + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) + expect(stream2.startTime).to.equal(currentBlockTimestamp) + expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000) + }) + }) + }) +}) From ae18198fcee430b253efb6384b209051b66bbec7 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:41:51 -0400 Subject: [PATCH 074/119] add comments --- contracts/DecentAutonomousAdmin.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 31030b5b..221f8f12 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -47,11 +47,12 @@ contract DecentAutonomousAdmin is FactoryFriendly { hatsElectionModule.startNextTerm(); - // transfer user hat to self + // This will burn the hat since wearer is no longer eligible args.userHatProtocol.checkHatWearerStatus( args.userHatId, args.currentWearer ); + // This will mint the hat to the nominated wearer args.userHatProtocol.mintHat(args.userHatId, args.nominatedWearer); } From 418a93f1c129846cef8afd2c8a8b82af435b0357 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:48:45 -0400 Subject: [PATCH 075/119] small cleanup --- contracts/DecentHats.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 4f1e931e..fc73aa33 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -88,8 +88,6 @@ contract DecentHats { for (uint256 i = 0; i < params.hats.length; ) { if (params.hats[i].isTermed) { - uint256 hatId = params.hatsProtocol.getNextId(adminHatId); - // Create election module and set as eligiblity _createTermedHatAndAccountAndMintAndStreams( params.hatsProtocol, @@ -97,7 +95,7 @@ contract DecentHats { _createElectionEligiblityModule( params.hatsModuleFactory, params.hatsElectionEligibilityImplementation, - hatId, + params.hatsProtocol.getNextId(adminHatId), topHatId, params.hats[i].termedParams[0] ), From 79ac1f4e28e2aadda7cd9974ff3f1fd61e5f5201 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 22 Oct 2024 01:16:41 -0400 Subject: [PATCH 076/119] add ERC165 support --- contracts/DecentAutonomousAdmin.sol | 33 +++++++++---------- .../interfaces/IDecentAutonomousAdmin.sol | 21 ++++++++++++ 2 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 contracts/interfaces/IDecentAutonomousAdmin.sol diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 221f8f12..7d0fc5df 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -3,24 +3,15 @@ pragma solidity 0.8.28; import {IHats} from "./interfaces/hats/full/IHats.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; -import {ISablierV2Lockup} from "./interfaces/sablier/full/ISablierV2Lockup.sol"; import {FactoryFriendly} from "@gnosis.pm/zodiac/contracts/factory/FactoryFriendly.sol"; - -contract DecentAutonomousAdmin is FactoryFriendly { - string public constant NAME = "DecentAutonomousAdmin"; - string public constant version_ = "0.1.0"; - - struct SablierStreamInfo { - uint256 streamId; - ISablierV2Lockup sablierV2Lockup; - } - struct TriggerStartArgs { - address currentWearer; - IHats userHatProtocol; - uint256 userHatId; - address nominatedWearer; - } - +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {IDecentAutonomousAdmin} from "./interfaces/IDecentAutonomousAdmin.sol"; + +contract DecentAutonomousAdmin is + IDecentAutonomousAdmin, + ERC165, + FactoryFriendly +{ // ////////////////////////////////////////////////////////////// // initializer // ////////////////////////////////////////////////////////////// @@ -56,6 +47,14 @@ contract DecentAutonomousAdmin is FactoryFriendly { args.userHatProtocol.mintHat(args.userHatId, args.nominatedWearer); } + function supportsInterface( + bytes4 interfaceId + ) public view override returns (bool) { + return + interfaceId == type(IDecentAutonomousAdmin).interfaceId || + super.supportsInterface(interfaceId); + } + // ////////////////////////////////////////////////////////////// // Internal Functions // ////////////////////////////////////////////////////////////// diff --git a/contracts/interfaces/IDecentAutonomousAdmin.sol b/contracts/interfaces/IDecentAutonomousAdmin.sol new file mode 100644 index 00000000..ae4a7e34 --- /dev/null +++ b/contracts/interfaces/IDecentAutonomousAdmin.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {IHats} from "./hats/full/IHats.sol"; +import {ISablierV2Lockup} from "./sablier/full/ISablierV2Lockup.sol"; + +interface IDecentAutonomousAdmin { + struct SablierStreamInfo { + uint256 streamId; + ISablierV2Lockup sablierV2Lockup; + } + + struct TriggerStartArgs { + address currentWearer; + IHats userHatProtocol; + uint256 userHatId; + address nominatedWearer; + } + + function triggerStartNextTerm(TriggerStartArgs calldata args) external; +} From 1682d0178c430fabf6b2708251699a54b3c28b70 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 22 Oct 2024 12:37:30 -0400 Subject: [PATCH 077/119] Lint and pretty --- deploy/core/019_deploy_DecentHats.ts | 8 +- .../core/020_deploy_DecentAutonomousAdmin.ts | 8 +- test/DecentAutonomousAdmin.test.ts | 88 +-- test/DecentHats.test.ts | 526 +++++++++--------- 4 files changed, 314 insertions(+), 316 deletions(-) diff --git a/deploy/core/019_deploy_DecentHats.ts b/deploy/core/019_deploy_DecentHats.ts index a372d413..6f48e56d 100644 --- a/deploy/core/019_deploy_DecentHats.ts +++ b/deploy/core/019_deploy_DecentHats.ts @@ -1,9 +1,9 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; -import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; +import { deployNonUpgradeable } from '../helpers/deployNonUpgradeable'; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentHats"); + await deployNonUpgradeable(hre, 'DecentHats'); }; export default func; diff --git a/deploy/core/020_deploy_DecentAutonomousAdmin.ts b/deploy/core/020_deploy_DecentAutonomousAdmin.ts index f1adb696..a04d62d7 100644 --- a/deploy/core/020_deploy_DecentAutonomousAdmin.ts +++ b/deploy/core/020_deploy_DecentAutonomousAdmin.ts @@ -1,9 +1,9 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; -import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable"; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; +import { deployNonUpgradeable } from '../helpers/deployNonUpgradeable'; const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - await deployNonUpgradeable(hre, "DecentAutonomousAdmin"); + await deployNonUpgradeable(hre, 'DecentAutonomousAdmin'); }; export default func; diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index c9d9093f..fa4a2c49 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -1,3 +1,6 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import hre from 'hardhat'; import { DecentAutonomousAdmin, DecentAutonomousAdmin__factory, @@ -5,102 +8,99 @@ import { MockHatsAutoAdmin__factory, MockHatsElectionEligibility, MockHatsElectionEligibility__factory, -} from "../typechain-types" -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" -import { expect } from "chai" -import hre from "hardhat" +} from '../typechain-types'; -describe("DecentAutonomousAdminHat", function () { +describe('DecentAutonomousAdminHat', function () { // Signer accounts - let deployer: SignerWithAddress - let currentWearer: SignerWithAddress - let randomUser: SignerWithAddress - let nominatedWearer: SignerWithAddress + let deployer: SignerWithAddress; + let currentWearer: SignerWithAddress; + let randomUser: SignerWithAddress; + let nominatedWearer: SignerWithAddress; // Contract instances - let hatsProtocol: MockHatsAutoAdmin - let hatsElectionModule: MockHatsElectionEligibility - let adminHat: DecentAutonomousAdmin + let hatsProtocol: MockHatsAutoAdmin; + let hatsElectionModule: MockHatsElectionEligibility; + let adminHat: DecentAutonomousAdmin; // Variables - let userHatId: bigint + let userHatId: bigint; beforeEach(async function () { // Get signers - ;[deployer, currentWearer, nominatedWearer, randomUser] = await hre.ethers.getSigners() + [deployer, currentWearer, nominatedWearer, randomUser] = await hre.ethers.getSigners(); // Deploy MockHatsAutoAdmin (Mock Hats Protocol) - hatsProtocol = await new MockHatsAutoAdmin__factory(deployer).deploy() + hatsProtocol = await new MockHatsAutoAdmin__factory(deployer).deploy(); // Deploy MockHatsElectionEligibility (Eligibility Module) - hatsElectionModule = await new MockHatsElectionEligibility__factory(deployer).deploy() + hatsElectionModule = await new MockHatsElectionEligibility__factory(deployer).deploy(); // Create Admin Hat const createAdminTx = await hatsProtocol.createHat( await hatsProtocol.getAddress(), // Admin address (self-administered) - "Details", // Hat details + 'Details', // Hat details 100, // Max supply hre.ethers.ZeroAddress, // Eligibility module (none) hre.ethers.ZeroAddress, // Toggle module (none) true, // Is mutable - "imageURI" // Image URI - ) - const createAdminTxReceipt = await createAdminTx.wait() - const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0] + 'imageURI', // Image URI + ); + const createAdminTxReceipt = await createAdminTx.wait(); + const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0]; // Deploy DecentAutonomousAdminHat contract with the admin hat ID - adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy() - const adminHatAddress = await adminHat.getAddress() + adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy(); + const adminHatAddress = await adminHat.getAddress(); // Mint the admin hat to adminHatWearer - await hatsProtocol.mintHat(adminHatId, adminHatAddress) + await hatsProtocol.mintHat(adminHatId, adminHatAddress); // Create User Hat under the admin hat const createUserTx = await hatsProtocol.createHat( adminHatAddress, // Admin address (adminHat contract) - "Details", // Hat details + 'Details', // Hat details 100, // Max supply await hatsElectionModule.getAddress(), // Eligibility module (election module) hre.ethers.ZeroAddress, // Toggle module (none) false, // Is mutable - "imageURI" // Image URI - ) + 'imageURI', // Image URI + ); - const createUserTxReceipt = await createUserTx.wait() - userHatId = createUserTxReceipt?.toJSON().logs[0].args[0] + const createUserTxReceipt = await createUserTx.wait(); + userHatId = createUserTxReceipt?.toJSON().logs[0].args[0]; // Mint the user hat to currentWearer - await hatsProtocol.mintHat(userHatId, await currentWearer.getAddress()) - }) + await hatsProtocol.mintHat(userHatId, await currentWearer.getAddress()); + }); - describe("triggerStartNextTerm", function () { - it("should correctly validate current wearer and transfer", async function () { + describe('triggerStartNextTerm', function () { + it('should correctly validate current wearer and transfer', async function () { const args = { currentWearer: currentWearer.address, userHatProtocol: await hatsProtocol.getAddress(), userHatId: userHatId, nominatedWearer: nominatedWearer.address, sablierStreamInfo: [], // No Sablier stream info for this test - } + }; // Call triggerStartNextTerm on the adminHat contract - await adminHat.triggerStartNextTerm(args) + await adminHat.triggerStartNextTerm(args); // Verify the hat is now worn by the nominated wearer - expect(await hatsProtocol.isWearerOfHat(nominatedWearer.address, userHatId)).to.be.true - }) - it("should correctly invalidate random address as current wearer", async function () { + expect((await hatsProtocol.isWearerOfHat(nominatedWearer.address, userHatId)) === true); + }); + it('should correctly invalidate random address as current wearer', async function () { const args = { currentWearer: randomUser.address, userHatProtocol: await hatsProtocol.getAddress(), userHatId: userHatId, nominatedWearer: nominatedWearer.address, sablierStreamInfo: [], // No Sablier stream info for this test - } + }; // Verify the hat is now worn by the current wearer await expect(adminHat.connect(randomUser).triggerStartNextTerm(args)).to.be.revertedWith( - "Not current wearer" - ) - }) - }) -}) + 'Not current wearer', + ); + }); + }); +}); diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index e3aa14f2..8063981a 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -1,3 +1,6 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import hre, { ethers } from 'hardhat'; import { GnosisSafeL2, GnosisSafeL2__factory, @@ -21,20 +24,15 @@ import { ModuleProxyFactory, DecentAutonomousAdmin__factory, DecentAutonomousAdmin, -} from "../typechain-types" +} from '../typechain-types'; -import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" -import { expect } from "chai" -import { ethers, keccak256, solidityPackedKeccak256, toUtf8Bytes } from "ethers" -import hre from "hardhat" - -import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from "./GlobalSafeDeployments.test" +import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from './GlobalSafeDeployments.test'; import { buildSafeTransaction, buildSignatureBytes, predictGnosisSafeAddress, safeSignTypedData, -} from "./helpers" +} from './helpers'; const executeSafeTransaction = async ({ safe, @@ -42,20 +40,20 @@ const executeSafeTransaction = async ({ transactionData, signers, }: { - safe: GnosisSafeL2 - to: string - transactionData: string - signers: SignerWithAddress[] + safe: GnosisSafeL2; + to: string; + transactionData: string; + signers: SignerWithAddress[]; }) => { const safeTx = buildSafeTransaction({ to, data: transactionData, nonce: await safe.nonce(), - }) + }); const sigs = await Promise.all( - signers.map(async (signer) => await safeSignTypedData(signer, safe, safeTx)) - ) + signers.map(async signer => safeSignTypedData(signer, safe, safeTx)), + ); const tx = await safe.execTransaction( safeTx.to, @@ -67,71 +65,71 @@ const executeSafeTransaction = async ({ safeTx.gasPrice, safeTx.gasToken, safeTx.refundReceiver, - buildSignatureBytes(sigs) - ) + buildSignatureBytes(sigs), + ); - return tx -} + return tx; +}; -describe("DecentHats", () => { - let dao: SignerWithAddress +describe('DecentHats', () => { + let dao: SignerWithAddress; - let mockHats: MockHats - let mockHatsAddress: string + let mockHats: MockHats; + let mockHatsAddress: string; - let keyValuePairs: KeyValuePairs - let gnosisSafe: GnosisSafeL2 + let keyValuePairs: KeyValuePairs; + let gnosisSafe: GnosisSafeL2; - let decentHats: DecentHats - let decentHatsAddress: string + let decentHats: DecentHats; + let decentHatsAddress: string; - let gnosisSafeAddress: string - let erc6551Registry: ERC6551Registry + let gnosisSafeAddress: string; + let erc6551Registry: ERC6551Registry; - let mockHatsAccountImplementation: MockHatsAccount - let mockHatsAccountImplementationAddress: string + let mockHatsAccountImplementation: MockHatsAccount; + let mockHatsAccountImplementationAddress: string; - let mockSablier: MockSablierV2LockupLinear - let mockSablierAddress: string + let mockSablier: MockSablierV2LockupLinear; + let mockSablierAddress: string; - let mockERC20: MockERC20 - let mockERC20Address: string + let mockERC20: MockERC20; + let mockERC20Address: string; - let mockHatsElectionEligibilityImplementationAddress: string - let mockHatsModuleFactoryAddress: string + let mockHatsElectionEligibilityImplementationAddress: string; + let mockHatsModuleFactoryAddress: string; - let moduleProxyFactory: ModuleProxyFactory - let decentAutonomousAdminMasterCopy: DecentAutonomousAdmin + let moduleProxyFactory: ModuleProxyFactory; + let decentAutonomousAdminMasterCopy: DecentAutonomousAdmin; beforeEach(async () => { - const signers = await hre.ethers.getSigners() - const [deployer] = signers - ;[, dao] = signers + const signers = await hre.ethers.getSigners(); + const [deployer] = signers; + [, dao] = signers; - mockHats = await new MockHats__factory(deployer).deploy() - mockHatsAddress = await mockHats.getAddress() + mockHats = await new MockHats__factory(deployer).deploy(); + mockHatsAddress = await mockHats.getAddress(); const mockHatsElectionEligibilityImplementation = - await new MockHatsElectionEligibility__factory(deployer).deploy() + await new MockHatsElectionEligibility__factory(deployer).deploy(); mockHatsElectionEligibilityImplementationAddress = - await mockHatsElectionEligibilityImplementation.getAddress() - const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy() - mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress() - keyValuePairs = await new KeyValuePairs__factory(deployer).deploy() - erc6551Registry = await new ERC6551Registry__factory(deployer).deploy() - mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy() - mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress() - decentHats = await new DecentHats__factory(deployer).deploy() - decentHatsAddress = await decentHats.getAddress() - - moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy() - decentAutonomousAdminMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy() - - const gnosisSafeProxyFactory = getGnosisSafeProxyFactory() - const gnosisSafeL2Singleton = getGnosisSafeL2Singleton() - const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress() + await mockHatsElectionEligibilityImplementation.getAddress(); + const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy(); + mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress(); + keyValuePairs = await new KeyValuePairs__factory(deployer).deploy(); + erc6551Registry = await new ERC6551Registry__factory(deployer).deploy(); + mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy(); + mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress(); + decentHats = await new DecentHats__factory(deployer).deploy(); + decentHatsAddress = await decentHats.getAddress(); + + moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy(); + decentAutonomousAdminMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy(); + + const gnosisSafeProxyFactory = getGnosisSafeProxyFactory(); + const gnosisSafeL2Singleton = getGnosisSafeL2Singleton(); + const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress(); const createGnosisSetupCalldata = GnosisSafeL2__factory.createInterface().encodeFunctionData( - "setup", + 'setup', [ [dao.address], 1, @@ -141,83 +139,83 @@ describe("DecentHats", () => { hre.ethers.ZeroAddress, 0, hre.ethers.ZeroAddress, - ] - ) + ], + ); - const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString("hex")}`) + const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString('hex')}`); const predictedGnosisSafeAddress = await predictGnosisSafeAddress( createGnosisSetupCalldata, saltNum, gnosisSafeL2SingletonAddress, - gnosisSafeProxyFactory - ) - gnosisSafeAddress = predictedGnosisSafeAddress + gnosisSafeProxyFactory, + ); + gnosisSafeAddress = predictedGnosisSafeAddress; await gnosisSafeProxyFactory.createProxyWithNonce( gnosisSafeL2SingletonAddress, createGnosisSetupCalldata, - saltNum - ) + saltNum, + ); - gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer) + gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer); // Deploy MockSablierV2LockupLinear - mockSablier = await new MockSablierV2LockupLinear__factory(deployer).deploy() - mockSablierAddress = await mockSablier.getAddress() + mockSablier = await new MockSablierV2LockupLinear__factory(deployer).deploy(); + mockSablierAddress = await mockSablier.getAddress(); - mockERC20 = await new MockERC20__factory(deployer).deploy("MockERC20", "MCK") - mockERC20Address = await mockERC20.getAddress() + mockERC20 = await new MockERC20__factory(deployer).deploy('MockERC20', 'MCK'); + mockERC20Address = await mockERC20.getAddress(); - await mockERC20.mint(gnosisSafeAddress, ethers.parseEther("1000000")) - }) + await mockERC20.mint(gnosisSafeAddress, ethers.parseEther('1000000')); + }); - describe("DecentHats as a Module", () => { - let enableModuleTx: ethers.ContractTransactionResponse + describe('DecentHats as a Module', () => { + let enableModuleTx: ethers.ContractTransactionResponse; beforeEach(async () => { enableModuleTx = await executeSafeTransaction({ safe: gnosisSafe, to: gnosisSafeAddress, transactionData: GnosisSafeL2__factory.createInterface().encodeFunctionData( - "enableModule", - [decentHatsAddress] + 'enableModule', + [decentHatsAddress], ), signers: [dao], - }) - }) + }); + }); - it("Emits an ExecutionSuccess event", async () => { - await expect(enableModuleTx).to.emit(gnosisSafe, "ExecutionSuccess") - }) + it('Emits an ExecutionSuccess event', async () => { + await expect(enableModuleTx).to.emit(gnosisSafe, 'ExecutionSuccess'); + }); - it("Emits an EnabledModule event", async () => { - await expect(enableModuleTx).to.emit(gnosisSafe, "EnabledModule").withArgs(decentHatsAddress) - }) + it('Emits an EnabledModule event', async () => { + await expect(enableModuleTx).to.emit(gnosisSafe, 'EnabledModule').withArgs(decentHatsAddress); + }); - describe("Creating a new Top Hat and Tree", () => { - let createAndDeclareTreeTx: ethers.ContractTransactionResponse + describe('Creating a new Top Hat and Tree', () => { + let createAndDeclareTreeTx: ethers.ContractTransactionResponse; beforeEach(async () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, transactionData: DecentHats__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", + 'createAndDeclareTree', [ { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", + topHatDetails: '', + topHatImageURI: '', decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], @@ -232,8 +230,8 @@ describe("DecentHats", () => { hats: [ { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], @@ -247,8 +245,8 @@ describe("DecentHats", () => { }, { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], @@ -265,52 +263,52 @@ describe("DecentHats", () => { hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, - ] + ], ), signers: [dao], - }) - }) + }); + }); - it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") - }) + it('Emits an ExecutionSuccess event', async () => { + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, 'ExecutionSuccess'); + }); - it("Emits an ExecutionFromModuleSuccess event", async () => { + it('Emits an ExecutionFromModuleSuccess event', async () => { await expect(createAndDeclareTreeTx) - .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress) - }) + .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') + .withArgs(decentHatsAddress); + }); - it("Emits some hatsTreeId ValueUpdated events", async () => { + it('Emits some hatsTreeId ValueUpdated events', async () => { await expect(createAndDeclareTreeTx) - .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "0") - }) + .to.emit(keyValuePairs, 'ValueUpdated') + .withArgs(gnosisSafeAddress, 'topHatId', '0'); + }); - describe("Multiple calls", () => { - let createAndDeclareTreeTx2: ethers.ContractTransactionResponse + describe('Multiple calls', () => { + let createAndDeclareTreeTx2: ethers.ContractTransactionResponse; beforeEach(async () => { createAndDeclareTreeTx2 = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, transactionData: DecentHats__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", + 'createAndDeclareTree', [ { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", + topHatDetails: '', + topHatImageURI: '', decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], @@ -327,35 +325,35 @@ describe("DecentHats", () => { hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, - ] + ], ), signers: [dao], - }) - }) + }); + }); - it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx2).to.emit(gnosisSafe, "ExecutionSuccess") - }) + it('Emits an ExecutionSuccess event', async () => { + await expect(createAndDeclareTreeTx2).to.emit(gnosisSafe, 'ExecutionSuccess'); + }); - it("Emits an ExecutionFromModuleSuccess event", async () => { + it('Emits an ExecutionFromModuleSuccess event', async () => { await expect(createAndDeclareTreeTx2) - .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress) - }) + .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') + .withArgs(decentHatsAddress); + }); - it("Creates Top Hats with sequential IDs", async () => { + it('Creates Top Hats with sequential IDs', async () => { await expect(createAndDeclareTreeTx2) - .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "4") - }) - }) + .to.emit(keyValuePairs, 'ValueUpdated') + .withArgs(gnosisSafeAddress, 'topHatId', '4'); + }); + }); - describe("Creating Hats Accounts", () => { - let salt: string + describe('Creating Hats Accounts', () => { + let salt: string; beforeEach(async () => { - salt = await decentHats.SALT() - }) + salt = await decentHats.SALT(); + }); const getHatAccount = async (hatId: bigint) => { const hatAccountAddress = await erc6551Registry.account( @@ -363,55 +361,55 @@ describe("DecentHats", () => { salt, await hre.getChainId(), mockHatsAddress, - hatId - ) + hatId, + ); const hatAccount = MockHatsAccount__factory.connect( hatAccountAddress, - hre.ethers.provider - ) + hre.ethers.provider, + ); - return hatAccount - } + return hatAccount; + }; - it("Generates the correct Addresses for the current Hats", async () => { - const currentCount = await mockHats.count() + it('Generates the correct Addresses for the current Hats', async () => { + const currentCount = await mockHats.count(); for (let i = 0n; i < currentCount; i++) { - const topHatAccount = await getHatAccount(i) - expect(await topHatAccount.tokenId()).eq(i) - expect(await topHatAccount.tokenImplementation()).eq(mockHatsAddress) + const topHatAccount = await getHatAccount(i); + expect(await topHatAccount.tokenId()).eq(i); + expect(await topHatAccount.tokenImplementation()).eq(mockHatsAddress); } - }) - }) - }) + }); + }); + }); - describe("Creating a new Top Hat and Tree with Sablier Streams", () => { - let createAndDeclareTreeTx: ethers.ContractTransactionResponse - let currentBlockTimestamp: number + describe('Creating a new Top Hat and Tree with Sablier Streams', () => { + let createAndDeclareTreeTx: ethers.ContractTransactionResponse; + let currentBlockTimestamp: number; beforeEach(async () => { - currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp + currentBlockTimestamp = (await hre.ethers.provider.getBlock('latest'))!.timestamp; createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, transactionData: DecentHats__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", + 'createAndDeclareTree', [ { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", + topHatDetails: '', + topHatImageURI: '', decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], @@ -426,15 +424,15 @@ describe("DecentHats", () => { hats: [ { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [ { sablier: mockSablierAddress, sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("100"), + totalAmount: ethers.parseEther('100'), asset: mockERC20Address, cancelable: true, transferable: false, @@ -456,8 +454,8 @@ describe("DecentHats", () => { }, { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], @@ -474,86 +472,86 @@ describe("DecentHats", () => { hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, - ] + ], ), signers: [dao], - }) - }) + }); + }); - it("Emits an ExecutionSuccess event", async () => { - await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, "ExecutionSuccess") - }) + it('Emits an ExecutionSuccess event', async () => { + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, 'ExecutionSuccess'); + }); - it("Emits an ExecutionFromModuleSuccess event", async () => { + it('Emits an ExecutionFromModuleSuccess event', async () => { await expect(createAndDeclareTreeTx) - .to.emit(gnosisSafe, "ExecutionFromModuleSuccess") - .withArgs(decentHatsAddress) - }) + .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') + .withArgs(decentHatsAddress); + }); - it("Emits some hatsTreeId ValueUpdated events", async () => { + it('Emits some hatsTreeId ValueUpdated events', async () => { await expect(createAndDeclareTreeTx) - .to.emit(keyValuePairs, "ValueUpdated") - .withArgs(gnosisSafeAddress, "topHatId", "0") - }) + .to.emit(keyValuePairs, 'ValueUpdated') + .withArgs(gnosisSafeAddress, 'topHatId', '0'); + }); - it("Creates a Sablier stream for the hat with stream parameters", async () => { + it('Creates a Sablier stream for the hat with stream parameters', async () => { const streamCreatedEvents = await mockSablier.queryFilter( - mockSablier.filters.StreamCreated() - ) - expect(streamCreatedEvents.length).to.equal(1) + mockSablier.filters.StreamCreated(), + ); + expect(streamCreatedEvents.length).to.equal(1); - const event = streamCreatedEvents[0] - expect(event.args.sender).to.equal(gnosisSafeAddress) - expect(event.args.recipient).to.not.equal(ethers.ZeroAddress) - expect(event.args.totalAmount).to.equal(ethers.parseEther("100")) - }) + const event = streamCreatedEvents[0]; + expect(event.args.sender).to.equal(gnosisSafeAddress); + expect(event.args.recipient).to.not.equal(ethers.ZeroAddress); + expect(event.args.totalAmount).to.equal(ethers.parseEther('100')); + }); - it("Does not create a Sablier stream for hats without stream parameters", async () => { + it('Does not create a Sablier stream for hats without stream parameters', async () => { const streamCreatedEvents = await mockSablier.queryFilter( - mockSablier.filters.StreamCreated() - ) - expect(streamCreatedEvents.length).to.equal(1) // Only one stream should be created - }) + mockSablier.filters.StreamCreated(), + ); + expect(streamCreatedEvents.length).to.equal(1); // Only one stream should be created + }); - it("Creates a Sablier stream with correct timestamps", async () => { + it('Creates a Sablier stream with correct timestamps', async () => { const streamCreatedEvents = await mockSablier.queryFilter( - mockSablier.filters.StreamCreated() - ) - expect(streamCreatedEvents.length).to.equal(1) + mockSablier.filters.StreamCreated(), + ); + expect(streamCreatedEvents.length).to.equal(1); - const streamId = streamCreatedEvents[0].args.streamId - const stream = await mockSablier.getStream(streamId) + const streamId = streamCreatedEvents[0].args.streamId; + const stream = await mockSablier.getStream(streamId); - expect(stream.startTime).to.equal(currentBlockTimestamp) - expect(stream.endTime).to.equal(currentBlockTimestamp + 2592000) - }) - }) + expect(stream.startTime).to.equal(currentBlockTimestamp); + expect(stream.endTime).to.equal(currentBlockTimestamp + 2592000); + }); + }); - describe("Creating a new Top Hat and Tree with Multiple Sablier Streams per Hat", () => { - let currentBlockTimestamp: number + describe('Creating a new Top Hat and Tree with Multiple Sablier Streams per Hat', () => { + let currentBlockTimestamp: number; beforeEach(async () => { - currentBlockTimestamp = (await hre.ethers.provider.getBlock("latest"))!.timestamp + currentBlockTimestamp = (await hre.ethers.provider.getBlock('latest'))!.timestamp; await executeSafeTransaction({ safe: gnosisSafe, to: decentHatsAddress, transactionData: DecentHats__factory.createInterface().encodeFunctionData( - "createAndDeclareTree", + 'createAndDeclareTree', [ { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: "", - topHatImageURI: "", + topHatDetails: '', + topHatImageURI: '', decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], @@ -568,15 +566,15 @@ describe("DecentHats", () => { hats: [ { maxSupply: 1, - details: "", - imageURI: "", + details: '', + imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [ { sablier: mockSablierAddress, sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("100"), + totalAmount: ethers.parseEther('100'), asset: mockERC20Address, cancelable: true, transferable: false, @@ -590,7 +588,7 @@ describe("DecentHats", () => { { sablier: mockSablierAddress, sender: gnosisSafeAddress, - totalAmount: ethers.parseEther("50"), + totalAmount: ethers.parseEther('50'), asset: mockERC20Address, cancelable: false, transferable: true, @@ -615,58 +613,58 @@ describe("DecentHats", () => { hatsElectionEligibilityImplementation: mockHatsElectionEligibilityImplementationAddress, }, - ] + ], ), signers: [dao], - }) - }) + }); + }); - it("Creates multiple Sablier streams for a single hat", async () => { + it('Creates multiple Sablier streams for a single hat', async () => { const streamCreatedEvents = await mockSablier.queryFilter( - mockSablier.filters.StreamCreated() - ) - expect(streamCreatedEvents.length).to.equal(2) - - const event1 = streamCreatedEvents[0] - expect(event1.args.sender).to.equal(gnosisSafeAddress) - expect(event1.args.recipient).to.not.equal(ethers.ZeroAddress) - expect(event1.args.totalAmount).to.equal(ethers.parseEther("100")) - - const event2 = streamCreatedEvents[1] - expect(event2.args.sender).to.equal(gnosisSafeAddress) - expect(event2.args.recipient).to.equal(event1.args.recipient) - expect(event2.args.totalAmount).to.equal(ethers.parseEther("50")) - }) - - it("Creates streams with correct parameters", async () => { + mockSablier.filters.StreamCreated(), + ); + expect(streamCreatedEvents.length).to.equal(2); + + const event1 = streamCreatedEvents[0]; + expect(event1.args.sender).to.equal(gnosisSafeAddress); + expect(event1.args.recipient).to.not.equal(ethers.ZeroAddress); + expect(event1.args.totalAmount).to.equal(ethers.parseEther('100')); + + const event2 = streamCreatedEvents[1]; + expect(event2.args.sender).to.equal(gnosisSafeAddress); + expect(event2.args.recipient).to.equal(event1.args.recipient); + expect(event2.args.totalAmount).to.equal(ethers.parseEther('50')); + }); + + it('Creates streams with correct parameters', async () => { const streamCreatedEvents = await mockSablier.queryFilter( - mockSablier.filters.StreamCreated() - ) + mockSablier.filters.StreamCreated(), + ); - const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) - expect(stream1.cancelable).to.be.true - expect(stream1.transferable).to.be.false - expect(stream1.endTime - stream1.startTime).to.equal(2592000) + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId); + expect(stream1.cancelable === true); + expect(stream1.transferable === false); + expect(stream1.endTime - stream1.startTime).to.equal(2592000); - const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) - expect(stream2.cancelable).to.be.false - expect(stream2.transferable).to.be.true - expect(stream2.endTime - stream2.startTime).to.equal(1296000) - }) + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId); + expect(stream2.cancelable === false); + expect(stream2.transferable === true); + expect(stream2.endTime - stream2.startTime).to.equal(1296000); + }); - it("Creates streams with correct timestamps", async () => { + it('Creates streams with correct timestamps', async () => { const streamCreatedEvents = await mockSablier.queryFilter( - mockSablier.filters.StreamCreated() - ) - - const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId) - expect(stream1.startTime).to.equal(currentBlockTimestamp) - expect(stream1.endTime).to.equal(currentBlockTimestamp + 2592000) - - const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId) - expect(stream2.startTime).to.equal(currentBlockTimestamp) - expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000) - }) - }) - }) -}) + mockSablier.filters.StreamCreated(), + ); + + const stream1 = await mockSablier.getStream(streamCreatedEvents[0].args.streamId); + expect(stream1.startTime).to.equal(currentBlockTimestamp); + expect(stream1.endTime).to.equal(currentBlockTimestamp + 2592000); + + const stream2 = await mockSablier.getStream(streamCreatedEvents[1].args.streamId); + expect(stream2.startTime).to.equal(currentBlockTimestamp); + expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000); + }); + }); + }); +}); From faffebfaf6b4e07aa5e93148b741ef5b2d1fc68b Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:49:04 -0400 Subject: [PATCH 078/119] remove unused structand unsued comment area --- contracts/DecentAutonomousAdmin.sol | 4 ---- contracts/interfaces/IDecentAutonomousAdmin.sol | 5 ----- 2 files changed, 9 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 7d0fc5df..9a18997c 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -54,8 +54,4 @@ contract DecentAutonomousAdmin is interfaceId == type(IDecentAutonomousAdmin).interfaceId || super.supportsInterface(interfaceId); } - - // ////////////////////////////////////////////////////////////// - // Internal Functions - // ////////////////////////////////////////////////////////////// } diff --git a/contracts/interfaces/IDecentAutonomousAdmin.sol b/contracts/interfaces/IDecentAutonomousAdmin.sol index ae4a7e34..2bcc99b0 100644 --- a/contracts/interfaces/IDecentAutonomousAdmin.sol +++ b/contracts/interfaces/IDecentAutonomousAdmin.sol @@ -5,11 +5,6 @@ import {IHats} from "./hats/full/IHats.sol"; import {ISablierV2Lockup} from "./sablier/full/ISablierV2Lockup.sol"; interface IDecentAutonomousAdmin { - struct SablierStreamInfo { - uint256 streamId; - ISablierV2Lockup sablierV2Lockup; - } - struct TriggerStartArgs { address currentWearer; IHats userHatProtocol; From 6e63069babe92754d446d92d01bd8d905cba50fa Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 22 Oct 2024 23:24:19 -0400 Subject: [PATCH 079/119] remove unneeded conditionals --- contracts/DecentHats.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index fc73aa33..b1a43338 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -224,9 +224,7 @@ contract DecentHats { hatId ); - if (hat.wearer != address(0)) { - hatsProtocol.mintHat(hatId, hat.wearer); - } + hatsProtocol.mintHat(hatId, hat.wearer); for (uint256 i = 0; i < hat.sablierParams.length; ) { SablierStreamParams memory sablierParams = hat.sablierParams[i]; @@ -292,9 +290,7 @@ contract DecentHats { hat.termedParams[0].nominatedWearers ); - if (hat.wearer != address(0)) { - hatsProtocol.mintHat(hatId, hat.wearer); - } + hatsProtocol.mintHat(hatId, hat.termedParams[0].nominatedWearers[0]); for (uint256 i = 0; i < hat.sablierParams.length; ) { SablierStreamParams memory sablierParams = hat.sablierParams[i]; From c175538527c80dbb4e62dd2b4fee5dd8ac61e9fe Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 22 Oct 2024 23:24:38 -0400 Subject: [PATCH 080/119] add requires to check term param length and nominees length --- contracts/DecentHats.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index b1a43338..f7d7a6d9 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -277,6 +277,14 @@ contract DecentHats { uint256 adminHatId, Hat calldata hat ) internal { + require( + hat.termedParams.length == 1, + "DecentHats: termedParams length must be 1" + ); + require( + hat.termedParams[0].nominatedWearers.length == 1, + "DecentHats: nominatedWearers length must be 1" + ); uint256 hatId = _createHat( hatsProtocol, adminHatId, @@ -310,7 +318,7 @@ contract DecentHats { LockupLinear.CreateWithTimestamps memory params = LockupLinear .CreateWithTimestamps({ sender: sablierParams.sender, - recipient: hat.wearer, + recipient: hat.termedParams[0].nominatedWearers[0], totalAmount: sablierParams.totalAmount, asset: IERC20(sablierParams.asset), cancelable: sablierParams.cancelable, From 6e9e322726e328bfc852c93571effe5f0aad1cb8 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 12:50:08 -0400 Subject: [PATCH 081/119] update with public function to create new RoleHats --- contracts/DecentHats.sol | 50 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index f7d7a6d9..fc01f46d 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -64,6 +64,17 @@ contract DecentHats { /* ///////////////////////////////////////////////////////////////////////////// EXTERNAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ + /** + * For a safe without any roles previously created on it, this function should be called. It sets up the + * top hat and admin hat, as well as any other hats and their streams that are provided. + * + * This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. + * + * @dev In order for a Safe to seamlessly create roles even if it has never previously created a role and thus has + * no hat tree, we defer the creation of the hat tree and its setup to this contract. This way, in a single tx block, + * the resulting topHatId of the newly created hat can be used to create an admin hat and any other hats needed. + * We also make use of `KeyValuePairs` to associate the topHatId with the Safe. + */ function createAndDeclareTree(CreateTreeParams calldata params) public { (uint256 topHatId, address topHatAccount) = _createTopHatAndAccount( params.hatsProtocol, @@ -73,7 +84,7 @@ contract DecentHats { params.hatsAccountImplementation ); - _updateKeyValuePairs(params.keyValuePairs, topHatId); + _declareSafeHatTree(params.keyValuePairs, topHatId); (uint256 adminHatId, ) = _createAdminHatAndAccount( params.hatsProtocol, @@ -121,11 +132,46 @@ contract DecentHats { params.hatsProtocol.transferHat(topHatId, address(this), msg.sender); } + /** + * Creates a new role hat and any streams on it. + * + * This contract should be enabled a module on the Safe for which the role is to be created, and disable after. + * In order for the module to be able to create hats on behalf of the Safe, the Safe must first + * transfer its top hat to this contract. This function transfers the top hat back to the Safe after + * creating the role hat. + * + * The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. + * + * @dev Role hat creation, minting, smart account creation and stream creation are handled here in order + * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. + * See: https://github.com/decentdao/decent-interface/issues/2402 + */ + function createRoleHat( + IHats hatsProtocol, + IERC6551Registry registry, + address topHatAccount, + address hatsAccountImplementation, + uint256 adminHatId, + uint256 topHatId, + Hat calldata hat + ) public returns (uint256 hatId, address accountAddress) { + (hatId, accountAddress) = _createHatAndAccountAndMintAndStreams( + hatsProtocol, + registry, + topHatAccount, + hatsAccountImplementation, + adminHatId, + hat + ); + + hatsProtocol.transferHat(topHatId, address(this), msg.sender); + } + /* ///////////////////////////////////////////////////////////////////////////// INTERAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ - function _updateKeyValuePairs( + function _declareSafeHatTree( address _keyValuePairs, uint256 topHatId ) internal { From d0baf89bc7445a89f842aab8c17f9ee0900b5551 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:52:49 -0400 Subject: [PATCH 082/119] Add createTermRole external function - update params into structs and calldata --- contracts/DecentHats.sol | 89 ++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index fc01f46d..d0b01245 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -60,6 +60,27 @@ contract DecentHats { string topHatDetails; string topHatImageURI; } + struct CreateRoleHatParams { + IHats hatsProtocol; + IERC6551Registry registry; + address topHatAccount; + address hatsAccountImplementation; + uint256 adminHatId; + uint256 topHatId; + Hat hat; + } + + struct CreateTermedRoleHatParams { + IHats hatsProtocol; + IERC6551Registry registry; + IHatsModuleFactory hatsModuleFactory; + address topHatAccount; + address hatsAccountImplementation; + address hatsElectionEligibilityImplementation; + uint256 adminHatId; + uint256 topHatId; + Hat hat; + } /* ///////////////////////////////////////////////////////////////////////////// EXTERNAL FUNCTIONS @@ -146,25 +167,59 @@ contract DecentHats { * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. * See: https://github.com/decentdao/decent-interface/issues/2402 */ - function createRoleHat( - IHats hatsProtocol, - IERC6551Registry registry, - address topHatAccount, - address hatsAccountImplementation, - uint256 adminHatId, - uint256 topHatId, - Hat calldata hat - ) public returns (uint256 hatId, address accountAddress) { - (hatId, accountAddress) = _createHatAndAccountAndMintAndStreams( - hatsProtocol, - registry, - topHatAccount, - hatsAccountImplementation, - adminHatId, - hat + function createRoleHat(CreateRoleHatParams calldata params) external { + _createHatAndAccountAndMintAndStreams( + params.hatsProtocol, + params.registry, + params.topHatAccount, + params.hatsAccountImplementation, + params.adminHatId, + params.hat + ); + + params.hatsProtocol.transferHat( + params.topHatId, + address(this), + msg.sender ); + } - hatsProtocol.transferHat(topHatId, address(this), msg.sender); + /** + * Creates a new terned role hat and any streams on it. + * + * This contract should be enabled a module on the Safe for which the role is to be created, and disable after. + * In order for the module to be able to create hats on behalf of the Safe, the Safe must first + * transfer its top hat to this contract. This function transfers the top hat back to the Safe after + * creating the role hat. + * + * The function simply calls `createTermedHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. + * + * @dev Termed Role hat creation, minting, and stream creation are handled here in order + * to avoid a race condition where not more than one active proposal to create a new termed role can exist at a time. + * See: https://github.com/decentdao/decent-interface/issues/2402 + */ + function createTermedRoleHat( + CreateTermedRoleHatParams calldata params + ) external { + _createTermedHatAndAccountAndMintAndStreams( + params.hatsProtocol, + params.topHatAccount, + _createElectionEligiblityModule( + params.hatsModuleFactory, + params.hatsElectionEligibilityImplementation, + params.hatsProtocol.getNextId(params.adminHatId), + params.topHatId, + params.hat.termedParams[0] + ), + params.adminHatId, + params.hat + ); + + params.hatsProtocol.transferHat( + params.topHatId, + address(this), + msg.sender + ); } /* ///////////////////////////////////////////////////////////////////////////// From e84b682219f750e2e1724fd4bea3db936d2e05ea Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:53:40 -0400 Subject: [PATCH 083/119] switch createAndDeclareTree function to external --- contracts/DecentHats.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index d0b01245..33f9cd89 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -96,7 +96,7 @@ contract DecentHats { * the resulting topHatId of the newly created hat can be used to create an admin hat and any other hats needed. * We also make use of `KeyValuePairs` to associate the topHatId with the Safe. */ - function createAndDeclareTree(CreateTreeParams calldata params) public { + function createAndDeclareTree(CreateTreeParams calldata params) external { (uint256 topHatId, address topHatAccount) = _createTopHatAndAccount( params.hatsProtocol, params.topHatDetails, From c9a5009e73e16bead5c202a74868fb1cca997c13 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:54:23 -0400 Subject: [PATCH 084/119] move Create Sablier Stream logic to internal function --- contracts/DecentHats.sol | 118 +++++++++++++++------------------------ 1 file changed, 45 insertions(+), 73 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 33f9cd89..ee46ed63 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -60,6 +60,7 @@ contract DecentHats { string topHatDetails; string topHatImageURI; } + struct CreateRoleHatParams { IHats hatsProtocol; IERC6551Registry registry; @@ -328,43 +329,7 @@ contract DecentHats { hatsProtocol.mintHat(hatId, hat.wearer); for (uint256 i = 0; i < hat.sablierParams.length; ) { - SablierStreamParams memory sablierParams = hat.sablierParams[i]; - - // Approve tokens for Sablier - IAvatar(msg.sender).execTransactionFromModule( - sablierParams.asset, - 0, - abi.encodeWithSignature( - "approve(address,uint256)", - sablierParams.sablier, - sablierParams.totalAmount - ), - Enum.Operation.Call - ); - - LockupLinear.CreateWithTimestamps memory params = LockupLinear - .CreateWithTimestamps({ - sender: sablierParams.sender, - recipient: accountAddress, - totalAmount: sablierParams.totalAmount, - asset: IERC20(sablierParams.asset), - cancelable: sablierParams.cancelable, - transferable: sablierParams.transferable, - timestamps: sablierParams.timestamps, - broker: sablierParams.broker - }); - - // Proxy the Sablier call through IAvatar - IAvatar(msg.sender).execTransactionFromModule( - address(sablierParams.sablier), - 0, - abi.encodeWithSignature( - "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", - params - ), - Enum.Operation.Call - ); - + _createSablierStream(hat.sablierParams[i], hat.wearer); unchecked { ++i; } @@ -402,43 +367,10 @@ contract DecentHats { hatsProtocol.mintHat(hatId, hat.termedParams[0].nominatedWearers[0]); for (uint256 i = 0; i < hat.sablierParams.length; ) { - SablierStreamParams memory sablierParams = hat.sablierParams[i]; - - // Approve tokens for Sablier - IAvatar(msg.sender).execTransactionFromModule( - sablierParams.asset, - 0, - abi.encodeWithSignature( - "approve(address,uint256)", - sablierParams.sablier, - sablierParams.totalAmount - ), - Enum.Operation.Call - ); - - LockupLinear.CreateWithTimestamps memory params = LockupLinear - .CreateWithTimestamps({ - sender: sablierParams.sender, - recipient: hat.termedParams[0].nominatedWearers[0], - totalAmount: sablierParams.totalAmount, - asset: IERC20(sablierParams.asset), - cancelable: sablierParams.cancelable, - transferable: sablierParams.transferable, - timestamps: sablierParams.timestamps, - broker: sablierParams.broker - }); - - // Proxy the Sablier call through IAvatar - IAvatar(msg.sender).execTransactionFromModule( - address(sablierParams.sablier), - 0, - abi.encodeWithSignature( - "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", - params - ), - Enum.Operation.Call + _createSablierStream( + hat.sablierParams[i], + hat.termedParams[0].nominatedWearers[0] ); - unchecked { ++i; } @@ -495,4 +427,44 @@ contract DecentHats { uint256(SALT) ); } + + function _createSablierStream( + SablierStreamParams memory sablierParams, + address recipient + ) internal { + // Approve tokens for Sablier + IAvatar(msg.sender).execTransactionFromModule( + sablierParams.asset, + 0, + abi.encodeWithSignature( + "approve(address,uint256)", + sablierParams.sablier, + sablierParams.totalAmount + ), + Enum.Operation.Call + ); + + LockupLinear.CreateWithTimestamps memory params = LockupLinear + .CreateWithTimestamps({ + sender: sablierParams.sender, + recipient: recipient, + totalAmount: sablierParams.totalAmount, + asset: IERC20(sablierParams.asset), + cancelable: sablierParams.cancelable, + transferable: sablierParams.transferable, + timestamps: sablierParams.timestamps, + broker: sablierParams.broker + }); + + // Proxy the Sablier call through IAvatar + IAvatar(msg.sender).execTransactionFromModule( + address(sablierParams.sablier), + 0, + abi.encodeWithSignature( + "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", + params + ), + Enum.Operation.Call + ); + } } From d3c885d5f98897f787340831213d6b62c5928c31 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 15:16:33 -0400 Subject: [PATCH 085/119] fix sablier recipient for role hats --- contracts/DecentHats.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index ee46ed63..0ce8374a 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -329,7 +329,7 @@ contract DecentHats { hatsProtocol.mintHat(hatId, hat.wearer); for (uint256 i = 0; i < hat.sablierParams.length; ) { - _createSablierStream(hat.sablierParams[i], hat.wearer); + _createSablierStream(hat.sablierParams[i], accountAddress); unchecked { ++i; } From 22b492a39415e5761fdf555b7a2555811a3b1646 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 19:39:29 -0400 Subject: [PATCH 086/119] update tests to latest - matches DecentHats test to original contract - adds eslint ignore for ethers import for type-safety --- test/DecentHats.test.ts | 380 ++++++++++++++++++++-------------- test/DecentHats_0_1_0.test.ts | 4 +- test/helpers.ts | 4 +- 3 files changed, 229 insertions(+), 159 deletions(-) diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index 8063981a..b0fd8912 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -1,6 +1,9 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; -import hre, { ethers } from 'hardhat'; +/* eslint-disable-next-line import/no-extraneous-dependencies */ +import { ethers } from 'ethers'; +import hre from 'hardhat'; + import { GnosisSafeL2, GnosisSafeL2__factory, @@ -18,58 +21,16 @@ import { MockSablierV2LockupLinear, MockERC20__factory, MockERC20, - MockHatsModuleFactory__factory, + DecentAutonomousAdmin, + DecentAutonomousAdmin__factory, MockHatsElectionEligibility__factory, - ModuleProxyFactory__factory, + MockHatsModuleFactory__factory, ModuleProxyFactory, - DecentAutonomousAdmin__factory, - DecentAutonomousAdmin, + ModuleProxyFactory__factory, } from '../typechain-types'; import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from './GlobalSafeDeployments.test'; -import { - buildSafeTransaction, - buildSignatureBytes, - predictGnosisSafeAddress, - safeSignTypedData, -} from './helpers'; - -const executeSafeTransaction = async ({ - safe, - to, - transactionData, - signers, -}: { - safe: GnosisSafeL2; - to: string; - transactionData: string; - signers: SignerWithAddress[]; -}) => { - const safeTx = buildSafeTransaction({ - to, - data: transactionData, - nonce: await safe.nonce(), - }); - - const sigs = await Promise.all( - signers.map(async signer => safeSignTypedData(signer, safe, safeTx)), - ); - - const tx = await safe.execTransaction( - safeTx.to, - safeTx.value, - safeTx.data, - safeTx.operation, - safeTx.safeTxGas, - safeTx.baseGas, - safeTx.gasPrice, - safeTx.gasToken, - safeTx.refundReceiver, - buildSignatureBytes(sigs), - ); - - return tx; -}; +import { executeSafeTransaction, getHatAccount, predictGnosisSafeAddress } from './helpers'; describe('DecentHats', () => { let dao: SignerWithAddress; @@ -100,74 +61,75 @@ describe('DecentHats', () => { let moduleProxyFactory: ModuleProxyFactory; let decentAutonomousAdminMasterCopy: DecentAutonomousAdmin; - beforeEach(async () => { - const signers = await hre.ethers.getSigners(); - const [deployer] = signers; - [, dao] = signers; - - mockHats = await new MockHats__factory(deployer).deploy(); - mockHatsAddress = await mockHats.getAddress(); - const mockHatsElectionEligibilityImplementation = - await new MockHatsElectionEligibility__factory(deployer).deploy(); - mockHatsElectionEligibilityImplementationAddress = - await mockHatsElectionEligibilityImplementation.getAddress(); - const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy(); - mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress(); - keyValuePairs = await new KeyValuePairs__factory(deployer).deploy(); - erc6551Registry = await new ERC6551Registry__factory(deployer).deploy(); - mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy(); - mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress(); - decentHats = await new DecentHats__factory(deployer).deploy(); - decentHatsAddress = await decentHats.getAddress(); - - moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy(); - decentAutonomousAdminMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy(); - - const gnosisSafeProxyFactory = getGnosisSafeProxyFactory(); - const gnosisSafeL2Singleton = getGnosisSafeL2Singleton(); - const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress(); - - const createGnosisSetupCalldata = GnosisSafeL2__factory.createInterface().encodeFunctionData( - 'setup', - [ - [dao.address], - 1, - hre.ethers.ZeroAddress, - hre.ethers.ZeroHash, - hre.ethers.ZeroAddress, - hre.ethers.ZeroAddress, - 0, - hre.ethers.ZeroAddress, - ], - ); - - const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString('hex')}`); - - const predictedGnosisSafeAddress = await predictGnosisSafeAddress( - createGnosisSetupCalldata, - saltNum, - gnosisSafeL2SingletonAddress, - gnosisSafeProxyFactory, - ); - gnosisSafeAddress = predictedGnosisSafeAddress; - - await gnosisSafeProxyFactory.createProxyWithNonce( - gnosisSafeL2SingletonAddress, - createGnosisSetupCalldata, - saltNum, - ); - - gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer); - - // Deploy MockSablierV2LockupLinear - mockSablier = await new MockSablierV2LockupLinear__factory(deployer).deploy(); - mockSablierAddress = await mockSablier.getAddress(); - - mockERC20 = await new MockERC20__factory(deployer).deploy('MockERC20', 'MCK'); - mockERC20Address = await mockERC20.getAddress(); - - await mockERC20.mint(gnosisSafeAddress, ethers.parseEther('1000000')); + try { + const signers = await hre.ethers.getSigners(); + const [deployer] = signers; + [, dao] = signers; + + mockHats = await new MockHats__factory(deployer).deploy(); + mockHatsAddress = await mockHats.getAddress(); + const mockHatsElectionEligibilityImplementation = + await new MockHatsElectionEligibility__factory(deployer).deploy(); + mockHatsElectionEligibilityImplementationAddress = + await mockHatsElectionEligibilityImplementation.getAddress(); + const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy(); + mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress(); + keyValuePairs = await new KeyValuePairs__factory(deployer).deploy(); + erc6551Registry = await new ERC6551Registry__factory(deployer).deploy(); + mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy(); + mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress(); + decentHats = await new DecentHats__factory(deployer).deploy(); + decentHatsAddress = await decentHats.getAddress(); + moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy(); + decentAutonomousAdminMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy(); + + const gnosisSafeProxyFactory = getGnosisSafeProxyFactory(); + const gnosisSafeL2Singleton = getGnosisSafeL2Singleton(); + const gnosisSafeL2SingletonAddress = await gnosisSafeL2Singleton.getAddress(); + + const createGnosisSetupCalldata = GnosisSafeL2__factory.createInterface().encodeFunctionData( + 'setup', + [ + [dao.address], + 1, + hre.ethers.ZeroAddress, + hre.ethers.ZeroHash, + hre.ethers.ZeroAddress, + hre.ethers.ZeroAddress, + 0, + hre.ethers.ZeroAddress, + ], + ); + const saltNum = BigInt(`0x${Buffer.from(hre.ethers.randomBytes(32)).toString('hex')}`); + + const predictedGnosisSafeAddress = await predictGnosisSafeAddress( + createGnosisSetupCalldata, + saltNum, + gnosisSafeL2SingletonAddress, + gnosisSafeProxyFactory, + ); + gnosisSafeAddress = predictedGnosisSafeAddress; + + await gnosisSafeProxyFactory.createProxyWithNonce( + gnosisSafeL2SingletonAddress, + createGnosisSetupCalldata, + saltNum, + ); + + gnosisSafe = GnosisSafeL2__factory.connect(predictedGnosisSafeAddress, deployer); + + // Deploy MockSablierV2LockupLinear + mockSablier = await new MockSablierV2LockupLinear__factory(deployer).deploy(); + mockSablierAddress = await mockSablier.getAddress(); + + mockERC20 = await new MockERC20__factory(deployer).deploy('MockERC20', 'MCK'); + mockERC20Address = await mockERC20.getAddress(); + + await mockERC20.mint(gnosisSafeAddress, ethers.parseEther('1000000')); + } catch (e) { + console.error('AHHHHHH', e); + } }); describe('DecentHats as a Module', () => { @@ -220,12 +182,7 @@ describe('DecentHats', () => { wearer: ethers.ZeroAddress, sablierParams: [], isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParams: [], }, hats: [ { @@ -236,12 +193,7 @@ describe('DecentHats', () => { wearer: ethers.ZeroAddress, sablierParams: [], isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParams: [], }, { maxSupply: 1, @@ -251,12 +203,7 @@ describe('DecentHats', () => { wearer: ethers.ZeroAddress, sablierParams: [], isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParams: [], }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -349,34 +296,17 @@ describe('DecentHats', () => { }); describe('Creating Hats Accounts', () => { - let salt: string; - - beforeEach(async () => { - salt = await decentHats.SALT(); - }); - - const getHatAccount = async (hatId: bigint) => { - const hatAccountAddress = await erc6551Registry.account( - mockHatsAccountImplementationAddress, - salt, - await hre.getChainId(), - mockHatsAddress, - hatId, - ); - - const hatAccount = MockHatsAccount__factory.connect( - hatAccountAddress, - hre.ethers.provider, - ); - - return hatAccount; - }; - it('Generates the correct Addresses for the current Hats', async () => { const currentCount = await mockHats.count(); for (let i = 0n; i < currentCount; i++) { - const topHatAccount = await getHatAccount(i); + const topHatAccount = await getHatAccount( + i, + erc6551Registry, + mockHatsAccountImplementationAddress, + mockHatsAddress, + ); + expect(await topHatAccount.tokenId()).eq(i); expect(await topHatAccount.tokenImplementation()).eq(mockHatsAddress); } @@ -666,5 +596,141 @@ describe('DecentHats', () => { expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000); }); }); + + describe('Creating a new hat on existing Tree', () => { + let createRoleHatPromise: Promise; + const topHatId = 0; + + beforeEach(async () => { + try { + await executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats__factory.createInterface().encodeFunctionData( + 'createAndDeclareTree', + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: '', + topHatImageURI: '', + adminHat: { + maxSupply: 1, + details: '', + imageURI: '', + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [], + }, + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + moduleProxyFactory: await moduleProxyFactory.getAddress(), + decentAutonomousAdminMasterCopy: + await decentAutonomousAdminMasterCopy.getAddress(), + hats: [], + }, + ], + ), + signers: [dao], + }); + } catch (e) { + console.error('Error creating tree', e); + } + const currentBlockTimestamp = (await hre.ethers.provider.getBlock('latest'))!.timestamp; + + createRoleHatPromise = executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats__factory.createInterface().encodeFunctionData( + 'createRoleHat', + [ + { + hatsProtocol: mockHatsAddress, + registry: await erc6551Registry.getAddress(), + topHatAccount: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', + hatsAccountImplementation: mockHatsAccountImplementationAddress, + adminHatId: 1, + topHatId, + hat: { + maxSupply: 1, + details: '', + imageURI: '', + isMutable: true, + wearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', + isTermed: false, + termedParams: [], + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther('100'), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: currentBlockTimestamp + 86400, // 1 day cliff + end: currentBlockTimestamp + 2592000, // 30 days from now + }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + }, + }, + ], + ), + signers: [dao], + }); + }); + + it('Reverts if the top hat is not transferred to the DecentHats module first', async () => { + await expect(createRoleHatPromise).to.be.reverted; + }); + + it('Emits an ExecutionSuccess event', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await expect(await createRoleHatPromise).to.emit(gnosisSafe, 'ExecutionSuccess'); + }); + + it('Emits an ExecutionFromModuleSuccess event', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await expect(await createRoleHatPromise) + .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') + .withArgs(decentHatsAddress); + }); + + it('Transfers the top hat back to the Safe', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + + const isModuleWearerOfTopHat = await mockHats.isWearerOfHat(decentHatsAddress, topHatId); + expect(isModuleWearerOfTopHat).to.equal(true); + + await createRoleHatPromise; + + const isSafeWearerOfTopHat = await mockHats.isWearerOfHat(gnosisSafeAddress, topHatId); + expect(isSafeWearerOfTopHat).to.equal(true); + }); + + it('Actually creates the new hat', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + + const hatsCountBeforeCreate = await mockHats.count(); + expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat + + await createRoleHatPromise; + + const newHatId = await mockHats.count(); + expect(newHatId).to.equal(3); // + newly created hat + }); + }); }); }); diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index 7ffb285e..dd9a3654 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -1,6 +1,8 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; -import hre, { ethers } from 'hardhat'; +/* eslint-disable-next-line import/no-extraneous-dependencies */ +import { ethers } from 'ethers'; +import hre from 'hardhat'; import { GnosisSafeL2, GnosisSafeL2__factory, diff --git a/test/helpers.ts b/test/helpers.ts index 17826296..d8b42c2e 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -1,5 +1,7 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; -import hre, { ethers } from 'hardhat'; +/* eslint-disable-next-line import/no-extraneous-dependencies */ +import { ethers } from 'ethers'; +import hre from 'hardhat'; import { ERC6551Registry, GnosisSafeL2, From b0f9ec5289850d0ec4bcc905ff96a35653885449 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:24:46 -0400 Subject: [PATCH 087/119] update eligibility mock contract to more align with live contract --- .../mocks/MockHatsElectionEligibility.sol | 52 +++++++++---------- contracts/mocks/MockHatsModuleFactory.sol | 32 ++++-------- 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/contracts/mocks/MockHatsElectionEligibility.sol b/contracts/mocks/MockHatsElectionEligibility.sol index d9d8daab..5c9c4212 100644 --- a/contracts/mocks/MockHatsElectionEligibility.sol +++ b/contracts/mocks/MockHatsElectionEligibility.sol @@ -1,41 +1,37 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import {IHatsElectionEligibility} from "../interfaces/hats/full/IHatsElectionEligibility.sol"; -contract MockHatsElectionEligibility { - uint128 private _currentTermEnd; - uint128 private _nextTermEnd; - mapping(uint128 => bool) private _electionStatus; +contract MockHatsElectionEligibility is IHatsElectionEligibility { + function currentTermEnd() external view returns (uint128) {} - event ElectionOpened(uint128 nextTermEnd); - event ElectionCompleted(uint128 termEnd, address[] winners); - event NewTermStarted(uint128 termEnd); + function nextTermEnd() external view returns (uint128) {} - // Mock function to simulate starting the next term - function startNextTerm() external { - _currentTermEnd = _nextTermEnd; - _nextTermEnd = 0; + function electionStatus( + uint128 termEnd + ) external view returns (bool isElectionOpen) {} - emit NewTermStarted(_currentTermEnd); - } + function electionResults( + uint128 termEnd, + address candidate + ) external view returns (bool elected) {} - function currentTermEnd() external view returns (uint128) { - return _currentTermEnd; - } + function BALLOT_BOX_HAT() external pure returns (uint256) {} - function electionStatus(uint128 termEnd) external view returns (bool) { - return _electionStatus[termEnd]; - } + function ADMIN_HAT() external pure returns (uint256) {} - // Functions to set the mock data for testing - function setCurrentTermEnd(uint128 termEnd) external { - _currentTermEnd = termEnd; - } + function elect(uint128 _termEnd, address[] calldata _winners) external {} - function setNextTermEnd(uint128 termEnd) external { - _nextTermEnd = termEnd; - } + function recall(uint128 _termEnd, address[] calldata _recallees) external {} + + function setNextTerm(uint128 _newTermEnd) external {} + + function startNextTerm() external {} - function setElectionStatus(uint128 termEnd, bool status) external { - _electionStatus[termEnd] = status; + function getWearerStatus( + address, + uint256 + ) external pure returns (bool eligible, bool standing) { + return (true, true); } } diff --git a/contracts/mocks/MockHatsModuleFactory.sol b/contracts/mocks/MockHatsModuleFactory.sol index 944d3401..df5479d3 100644 --- a/contracts/mocks/MockHatsModuleFactory.sol +++ b/contracts/mocks/MockHatsModuleFactory.sol @@ -2,22 +2,19 @@ pragma solidity ^0.8.19; import {IHatsModuleFactory} from "../interfaces/hats/full/IHatsModuleFactory.sol"; +import {MockHatsElectionEligibility} from "./MockHatsElectionEligibility.sol"; contract MockHatsModuleFactory is IHatsModuleFactory { function createHatsModule( - address _implementation, - uint256 _hatId, - bytes calldata _otherImmutableArgs, - bytes calldata _initData, - uint256 _saltNonce - ) external pure returns (address _instance) { - // Silence unused variable warnings - _implementation; - _hatId; - _otherImmutableArgs; - _initData; - _saltNonce; - return address(0); + address, + uint256, + bytes calldata, + bytes calldata, + uint256 + ) external override returns (address _instance) { + // Deploy a new instance of MockHatsElectionEligibility + MockHatsElectionEligibility newModule = new MockHatsElectionEligibility(); + _instance = address(newModule); } function getHatsModuleAddress( @@ -25,14 +22,7 @@ contract MockHatsModuleFactory is IHatsModuleFactory { uint256 _hatId, bytes calldata _otherImmutableArgs, uint256 _saltNonce - ) external pure returns (address) { - // Silence unused variable warnings - _implementation; - _hatId; - _otherImmutableArgs; - _saltNonce; - return address(0); - } + ) external view returns (address) {} function HATS() external view override returns (address) {} From 44e1e2d8f050486108edf47ba963748aabd62905 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:24:54 -0400 Subject: [PATCH 088/119] add `getNextId` method --- contracts/mocks/MockHats.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/mocks/MockHats.sol b/contracts/mocks/MockHats.sol index a070189d..c7adc972 100644 --- a/contracts/mocks/MockHats.sol +++ b/contracts/mocks/MockHats.sol @@ -58,4 +58,8 @@ contract MockHats is IHats { function getHatEligibilityModule( uint256 _hatId ) external view returns (address eligibility) {} + + function getNextId(uint256) external view returns (uint256 nextId) { + nextId = count; + } } From 81b9ac08b3e589d31b9d19ec63b1ed97d582a261 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:25:18 -0400 Subject: [PATCH 089/119] add 'create tree with term roles' tests --- test/DecentHats.test.ts | 90 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index b0fd8912..166dca67 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -69,12 +69,15 @@ describe('DecentHats', () => { mockHats = await new MockHats__factory(deployer).deploy(); mockHatsAddress = await mockHats.getAddress(); + const mockHatsElectionEligibilityImplementation = await new MockHatsElectionEligibility__factory(deployer).deploy(); mockHatsElectionEligibilityImplementationAddress = await mockHatsElectionEligibilityImplementation.getAddress(); + const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy(); mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress(); + keyValuePairs = await new KeyValuePairs__factory(deployer).deploy(); erc6551Registry = await new ERC6551Registry__factory(deployer).deploy(); mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy(); @@ -313,6 +316,93 @@ describe('DecentHats', () => { }); }); }); + describe('Creating a new Top Hat and Tree with Termed Roles', () => { + let createAndDeclareTreeTx: ethers.ContractTransactionResponse; + + beforeEach(async () => { + createAndDeclareTreeTx = await executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats__factory.createInterface().encodeFunctionData( + 'createAndDeclareTree', + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: '', + topHatImageURI: '', + decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + moduleProxyFactory: await moduleProxyFactory.getAddress(), + adminHat: { + maxSupply: 1, + details: '', + imageURI: '', + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [], + }, + hats: [ + { + maxSupply: 1, + details: '', + imageURI: '', + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: true, + termedParams: [ + { + termEndDateTs: BigInt(Date.now() + 100000), + nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], + }, + ], + }, + { + maxSupply: 1, + details: '', + imageURI: '', + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: true, + termedParams: [ + { + termEndDateTs: BigInt(Date.now() + 100000), + nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], + }, + ], + }, + ], + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + }, + ], + ), + signers: [dao], + }); + }); + + it('Emits an ExecutionSuccess event', async () => { + await expect(createAndDeclareTreeTx).to.emit(gnosisSafe, 'ExecutionSuccess'); + }); + + it('Emits an ExecutionFromModuleSuccess event', async () => { + await expect(createAndDeclareTreeTx) + .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') + .withArgs(decentHatsAddress); + }); + + it('Emits some hatsTreeId ValueUpdated events', async () => { + await expect(createAndDeclareTreeTx) + .to.emit(keyValuePairs, 'ValueUpdated') + .withArgs(gnosisSafeAddress, 'topHatId', '0'); + }); + }); describe('Creating a new Top Hat and Tree with Sablier Streams', () => { let createAndDeclareTreeTx: ethers.ContractTransactionResponse; From d2b15f1714c17f6830efb06a0bf901b648e03cee Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Sun, 27 Oct 2024 11:28:42 -0400 Subject: [PATCH 090/119] eslint ignore ethers import --- test/DecentSablierStreamManagement.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/DecentSablierStreamManagement.test.ts b/test/DecentSablierStreamManagement.test.ts index de11ac61..360eaa4a 100644 --- a/test/DecentSablierStreamManagement.test.ts +++ b/test/DecentSablierStreamManagement.test.ts @@ -1,6 +1,8 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; -import hre, { ethers } from 'hardhat'; +/* eslint-disable-next-line import/no-extraneous-dependencies */ +import { ethers } from 'ethers'; +import hre from 'hardhat'; import { DecentHats_0_1_0, DecentHats_0_1_0__factory, From 866716c3fc418ab3ef67cb694e73326a657496e0 Mon Sep 17 00:00:00 2001 From: Kellar Date: Fri, 25 Oct 2024 18:15:19 +0100 Subject: [PATCH 091/119] Update natspec --- contracts/DecentHats_0_1_0.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index a44756ee..df2b9a4b 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -196,14 +196,14 @@ contract DecentHats_0_1_0 { } /** - * Creates a new role hat and any streams on it. + * @notice Creates a new role hat and any streams on it. * - * This contract should be enabled a module on the Safe for which the role is to be created, and disable after. - * In order for the module to be able to create hats on behalf of the Safe, the Safe must first - * transfer its top hat to this contract. This function transfers the top hat back to the Safe after + * @notice This contract should be enabled a module on the Safe for which the role is to be created, and disabled after. + * + * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first transfer its top hat to this contract. This function transfers the top hat back to the Safe after * creating the role hat. * - * The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. + * @dev The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. * * @dev Role hat creation, minting, smart account creation and stream creation are handled here in order * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. @@ -233,10 +233,10 @@ contract DecentHats_0_1_0 { } /** - * For a safe without any roles previously created on it, this function should be called. It sets up the + * @notice For a safe without any roles previously created on it, this function should be called. It sets up the * top hat and admin hat, as well as any other hats and their streams that are provided. * - * This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. + * @notice This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. * * @dev In order for a Safe to seamlessly create roles even if it has never previously created a role and thus has * no hat tree, we defer the creation of the hat tree and its setup to this contract. This way, in a single tx block, From 4d4edb8142ea35236988a74879a7cc18bb57b7fb Mon Sep 17 00:00:00 2001 From: Kellar Date: Mon, 28 Oct 2024 13:04:24 +0000 Subject: [PATCH 092/119] Put natspec update in the correct contract --- contracts/DecentHats.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 0ce8374a..7660c33c 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -87,10 +87,10 @@ contract DecentHats { EXTERNAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ /** - * For a safe without any roles previously created on it, this function should be called. It sets up the + * @notice For a safe without any roles previously created on it, this function should be called. It sets up the * top hat and admin hat, as well as any other hats and their streams that are provided. * - * This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. + * @notice This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. * * @dev In order for a Safe to seamlessly create roles even if it has never previously created a role and thus has * no hat tree, we defer the creation of the hat tree and its setup to this contract. This way, in a single tx block, @@ -155,14 +155,14 @@ contract DecentHats { } /** - * Creates a new role hat and any streams on it. + * @notice Creates a new role hat and any streams on it. * - * This contract should be enabled a module on the Safe for which the role is to be created, and disable after. - * In order for the module to be able to create hats on behalf of the Safe, the Safe must first - * transfer its top hat to this contract. This function transfers the top hat back to the Safe after + * @notice This contract should be enabled a module on the Safe for which the role is to be created, and disabled after. + * + * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first transfer its top hat to this contract. This function transfers the top hat back to the Safe after * creating the role hat. * - * The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. + * @dev The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. * * @dev Role hat creation, minting, smart account creation and stream creation are handled here in order * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. From c9cde2b092e617fd3cec4b33b5b0c80a75f40c41 Mon Sep 17 00:00:00 2001 From: Kellar Date: Mon, 28 Oct 2024 13:10:25 +0000 Subject: [PATCH 093/119] Cleanup natspec --- contracts/DecentHats.sol | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 7660c33c..cf1c520b 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -88,7 +88,8 @@ contract DecentHats { ///////////////////////////////////////////////////////////////////////////// */ /** * @notice For a safe without any roles previously created on it, this function should be called. It sets up the - * top hat and admin hat, as well as any other hats and their streams that are provided. + * top hat and admin hat, as well as any other hats and their streams that are provided, then transfers the top hat + * to the calling safe. * * @notice This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. * @@ -159,10 +160,11 @@ contract DecentHats { * * @notice This contract should be enabled a module on the Safe for which the role is to be created, and disabled after. * - * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first transfer its top hat to this contract. This function transfers the top hat back to the Safe after + * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first + * transfer its top hat to this contract. This function transfers the top hat back to the Safe after * creating the role hat. * - * @dev The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. + * @dev The function simply calls `_createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. * * @dev Role hat creation, minting, smart account creation and stream creation are handled here in order * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. @@ -186,14 +188,15 @@ contract DecentHats { } /** - * Creates a new terned role hat and any streams on it. + * @notice Creates a new termed role hat and any streams on it. + * + * @notice This contract should be enabled a module on the Safe for which the role is to be created, and disable after. * - * This contract should be enabled a module on the Safe for which the role is to be created, and disable after. - * In order for the module to be able to create hats on behalf of the Safe, the Safe must first + * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first * transfer its top hat to this contract. This function transfers the top hat back to the Safe after * creating the role hat. * - * The function simply calls `createTermedHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. + * @dev The function simply calls `_createTermedHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. * * @dev Termed Role hat creation, minting, and stream creation are handled here in order * to avoid a race condition where not more than one active proposal to create a new termed role can exist at a time. From 6b0f5c80e4cff688f6ccca11fa5bb82ac2174f0f Mon Sep 17 00:00:00 2001 From: Kellar Date: Mon, 28 Oct 2024 13:43:49 +0000 Subject: [PATCH 094/119] Add natspec clarifying stream recipient difference between termed and untermed roles --- contracts/DecentHats.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index cf1c520b..52159e22 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -93,6 +93,12 @@ contract DecentHats { * * @notice This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. * + * @dev For each hat that is included, if the hat is: + * - termed, its stream funds on are targeted directly at the nominated wearer. The wearer should directly call `withdraw-` + * on the Sablier contract. + * - untermed, its stream funds are targeted at the hat's smart account. In order to withdraw funds from the stream, the + * hat's smart account must be the one call to `withdraw-` on the Sablier contract, setting the recipient arg to its wearer. + * * @dev In order for a Safe to seamlessly create roles even if it has never previously created a role and thus has * no hat tree, we defer the creation of the hat tree and its setup to this contract. This way, in a single tx block, * the resulting topHatId of the newly created hat can be used to create an admin hat and any other hats needed. @@ -166,6 +172,9 @@ contract DecentHats { * * @dev The function simply calls `_createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. * + * @dev Stream funds on Roles are targeted at the hat's smart account. In order to withdraw funds from the stream, the + * hat's smart account must be the one call to `withdraw-` on the Sablier contract, setting the recipient arg to its wearer. + * * @dev Role hat creation, minting, smart account creation and stream creation are handled here in order * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. * See: https://github.com/decentdao/decent-interface/issues/2402 @@ -198,6 +207,9 @@ contract DecentHats { * * @dev The function simply calls `_createTermedHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. * + * @dev Stream funds on Termed Roles are targeted directly at the nominated wearer. + * The wearer should directly call `withdraw-` on the Sablier contract. + * * @dev Termed Role hat creation, minting, and stream creation are handled here in order * to avoid a race condition where not more than one active proposal to create a new termed role can exist at a time. * See: https://github.com/decentdao/decent-interface/issues/2402 From 2551fbf079de3c6176c4e8baafb9a57455a56027 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:15:14 -0400 Subject: [PATCH 095/119] fix typo --- test/DecentAutonomousAdmin.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index fa4a2c49..212c1d3d 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -97,7 +97,7 @@ describe('DecentAutonomousAdminHat', function () { sablierStreamInfo: [], // No Sablier stream info for this test }; - // Verify the hat is now worn by the current wearer + // revert if not the current wearer await expect(adminHat.connect(randomUser).triggerStartNextTerm(args)).to.be.revertedWith( 'Not current wearer', ); From b066efa43272ef31df176c15cf851eb54333f01e Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:19:13 -0400 Subject: [PATCH 096/119] expect the hat wearer to lose hat --- contracts/mocks/MockHatsAdmin.sol | 8 ++++++-- test/DecentAutonomousAdmin.test.ts | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/contracts/mocks/MockHatsAdmin.sol b/contracts/mocks/MockHatsAdmin.sol index 9fc250b4..bddb1368 100644 --- a/contracts/mocks/MockHatsAdmin.sol +++ b/contracts/mocks/MockHatsAdmin.sol @@ -159,8 +159,12 @@ contract MockHatsAutoAdmin is IHats { function checkHatWearerStatus( uint256 _hatId, - address _wearer - ) external override returns (bool updated) {} + address + ) external override returns (bool updated) { + // 'burns' the hat if the wearer is no longer eligible + wearer[_hatId] = address(0); + return true; + } function renounceHat(uint256 _hatId) external override {} diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index 212c1d3d..7061b1ac 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -87,6 +87,8 @@ describe('DecentAutonomousAdminHat', function () { // Verify the hat is now worn by the nominated wearer expect((await hatsProtocol.isWearerOfHat(nominatedWearer.address, userHatId)) === true); + + expect((await hatsProtocol.isWearerOfHat(currentWearer.address, userHatId)) === false); }); it('should correctly invalidate random address as current wearer', async function () { const args = { From a58bad4fd23c6a6191bf4bbefc7808c0dcec63f3 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:24:56 -0400 Subject: [PATCH 097/119] add test for `createTermedRoleHat` --- test/DecentHats.test.ts | 144 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index 166dca67..56fdcbe8 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -822,5 +822,149 @@ describe('DecentHats', () => { expect(newHatId).to.equal(3); // + newly created hat }); }); + + describe('Creating a new hat on existing Tree', () => { + let createRoleHatPromise: Promise; + const topHatId = 0; + + beforeEach(async () => { + try { + await executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats__factory.createInterface().encodeFunctionData( + 'createAndDeclareTree', + [ + { + hatsProtocol: mockHatsAddress, + hatsAccountImplementation: mockHatsAccountImplementationAddress, + registry: await erc6551Registry.getAddress(), + keyValuePairs: await keyValuePairs.getAddress(), + topHatDetails: '', + topHatImageURI: '', + adminHat: { + maxSupply: 1, + details: '', + imageURI: '', + isMutable: false, + wearer: ethers.ZeroAddress, + sablierParams: [], + isTermed: false, + termedParams: [], + }, + hatsModuleFactory: mockHatsModuleFactoryAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + moduleProxyFactory: await moduleProxyFactory.getAddress(), + decentAutonomousAdminMasterCopy: + await decentAutonomousAdminMasterCopy.getAddress(), + hats: [], + }, + ], + ), + signers: [dao], + }); + } catch (e) { + console.error('Error creating tree', e); + } + const currentBlockTimestamp = (await hre.ethers.provider.getBlock('latest'))!.timestamp; + + createRoleHatPromise = executeSafeTransaction({ + safe: gnosisSafe, + to: decentHatsAddress, + transactionData: DecentHats__factory.createInterface().encodeFunctionData( + 'createTermedRoleHat', + [ + { + hatsProtocol: mockHatsAddress, + registry: await erc6551Registry.getAddress(), + topHatAccount: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', + hatsAccountImplementation: mockHatsAccountImplementationAddress, + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + hatsModuleFactory: mockHatsModuleFactoryAddress, + adminHatId: 1, + topHatId, + hat: { + maxSupply: 1, + details: '', + imageURI: '', + isMutable: true, + wearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', + isTermed: true, + termedParams: [ + { + termEndDateTs: BigInt(Date.now() + 100000), + nominatedWearers: ['0xdce7ca0555101f97451926944f5ae3b7adb2f5ae'], + }, + ], + sablierParams: [ + { + sablier: mockSablierAddress, + sender: gnosisSafeAddress, + totalAmount: ethers.parseEther('100'), + asset: mockERC20Address, + cancelable: true, + transferable: false, + timestamps: { + start: currentBlockTimestamp, + cliff: currentBlockTimestamp + 86400, // 1 day cliff + end: currentBlockTimestamp + 2592000, // 30 days from now + }, + broker: { account: ethers.ZeroAddress, fee: 0 }, + }, + ], + }, + }, + ], + ), + signers: [dao], + }); + }); + + it('Reverts if the top hat is not transferred to the DecentHats module first', async () => { + await expect(createRoleHatPromise).to.be.reverted; + }); + + it('Emits an ExecutionSuccess event', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await expect(await createRoleHatPromise).to.emit(gnosisSafe, 'ExecutionSuccess'); + }); + + it('Emits an ExecutionFromModuleSuccess event', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await expect(await createRoleHatPromise) + .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') + .withArgs(decentHatsAddress); + }); + + it('Transfers the top hat back to the Safe', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + + const isModuleWearerOfTopHat = await mockHats.isWearerOfHat(decentHatsAddress, topHatId); + expect(isModuleWearerOfTopHat).to.equal(true); + + await createRoleHatPromise; + + const isSafeWearerOfTopHat = await mockHats.isWearerOfHat(gnosisSafeAddress, topHatId); + expect(isSafeWearerOfTopHat).to.equal(true); + }); + + it('Actually creates the new hat', async () => { + // First transfer the top hat to the Safe + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + + const hatsCountBeforeCreate = await mockHats.count(); + expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat + + await createRoleHatPromise; + + const newHatId = await mockHats.count(); + expect(newHatId).to.equal(3); // + newly created hat + }); + }); }); }); From 0df6616495b87f7f414ba4116edadd689dfcb2ff Mon Sep 17 00:00:00 2001 From: Kellar Date: Tue, 29 Oct 2024 15:09:19 +0000 Subject: [PATCH 098/119] Revert misplaced natspec update --- contracts/DecentHats_0_1_0.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/DecentHats_0_1_0.sol b/contracts/DecentHats_0_1_0.sol index df2b9a4b..a44756ee 100644 --- a/contracts/DecentHats_0_1_0.sol +++ b/contracts/DecentHats_0_1_0.sol @@ -196,14 +196,14 @@ contract DecentHats_0_1_0 { } /** - * @notice Creates a new role hat and any streams on it. + * Creates a new role hat and any streams on it. * - * @notice This contract should be enabled a module on the Safe for which the role is to be created, and disabled after. - * - * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first transfer its top hat to this contract. This function transfers the top hat back to the Safe after + * This contract should be enabled a module on the Safe for which the role is to be created, and disable after. + * In order for the module to be able to create hats on behalf of the Safe, the Safe must first + * transfer its top hat to this contract. This function transfers the top hat back to the Safe after * creating the role hat. * - * @dev The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. + * The function simply calls `createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. * * @dev Role hat creation, minting, smart account creation and stream creation are handled here in order * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. @@ -233,10 +233,10 @@ contract DecentHats_0_1_0 { } /** - * @notice For a safe without any roles previously created on it, this function should be called. It sets up the + * For a safe without any roles previously created on it, this function should be called. It sets up the * top hat and admin hat, as well as any other hats and their streams that are provided. * - * @notice This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. + * This contract should be enabled a module on the Safe for which the role(s) are to be created, and disabled after. * * @dev In order for a Safe to seamlessly create roles even if it has never previously created a role and thus has * no hat tree, we defer the creation of the hat tree and its setup to this contract. This way, in a single tx block, From 0f211e0d146d7f305ad788dd0c807f38ce33b9a0 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 29 Oct 2024 12:10:50 -0400 Subject: [PATCH 099/119] Remove unused import from interface --- contracts/interfaces/IDecentAutonomousAdmin.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/interfaces/IDecentAutonomousAdmin.sol b/contracts/interfaces/IDecentAutonomousAdmin.sol index 2bcc99b0..d0c55200 100644 --- a/contracts/interfaces/IDecentAutonomousAdmin.sol +++ b/contracts/interfaces/IDecentAutonomousAdmin.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.28; import {IHats} from "./hats/full/IHats.sol"; -import {ISablierV2Lockup} from "./sablier/full/ISablierV2Lockup.sol"; interface IDecentAutonomousAdmin { struct TriggerStartArgs { From 2ecdbdc2da004b0a7f04b06e1c28e149df3538f3 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 29 Oct 2024 13:05:23 -0400 Subject: [PATCH 100/119] Consolidate Mock Hats into a single contract --- contracts/DecentAutonomousAdmin.sol | 16 +- .../interfaces/IDecentAutonomousAdmin.sol | 4 +- contracts/mocks/MockHats.sol | 270 +++++++++++++++-- contracts/mocks/MockHatsAdmin.sol | 284 ------------------ test/DecentAutonomousAdmin.test.ts | 21 +- test/DecentHats.test.ts | 20 +- test/DecentHats_0_1_0.test.ts | 6 +- 7 files changed, 274 insertions(+), 347 deletions(-) delete mode 100644 contracts/mocks/MockHatsAdmin.sol diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 9a18997c..07c38c97 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -22,15 +22,12 @@ contract DecentAutonomousAdmin is // ////////////////////////////////////////////////////////////// function triggerStartNextTerm(TriggerStartArgs calldata args) public { require( - args.userHatProtocol.isWearerOfHat( - args.currentWearer, - args.userHatId - ), + args.hatsProtocol.isWearerOfHat(args.currentWearer, args.hatId), "Not current wearer" ); address hatsEligibilityModuleAddress = args - .userHatProtocol - .getHatEligibilityModule(args.userHatId); + .hatsProtocol + .getHatEligibilityModule(args.hatId); IHatsElectionEligibility hatsElectionModule = IHatsElectionEligibility( hatsEligibilityModuleAddress @@ -39,12 +36,9 @@ contract DecentAutonomousAdmin is hatsElectionModule.startNextTerm(); // This will burn the hat since wearer is no longer eligible - args.userHatProtocol.checkHatWearerStatus( - args.userHatId, - args.currentWearer - ); + args.hatsProtocol.checkHatWearerStatus(args.hatId, args.currentWearer); // This will mint the hat to the nominated wearer - args.userHatProtocol.mintHat(args.userHatId, args.nominatedWearer); + args.hatsProtocol.mintHat(args.hatId, args.nominatedWearer); } function supportsInterface( diff --git a/contracts/interfaces/IDecentAutonomousAdmin.sol b/contracts/interfaces/IDecentAutonomousAdmin.sol index d0c55200..affa38bf 100644 --- a/contracts/interfaces/IDecentAutonomousAdmin.sol +++ b/contracts/interfaces/IDecentAutonomousAdmin.sol @@ -6,8 +6,8 @@ import {IHats} from "./hats/full/IHats.sol"; interface IDecentAutonomousAdmin { struct TriggerStartArgs { address currentWearer; - IHats userHatProtocol; - uint256 userHatId; + IHats hatsProtocol; + uint256 hatId; address nominatedWearer; } diff --git a/contracts/mocks/MockHats.sol b/contracts/mocks/MockHats.sol index c7adc972..2a3b2b4a 100644 --- a/contracts/mocks/MockHats.sol +++ b/contracts/mocks/MockHats.sol @@ -1,65 +1,285 @@ // SPDX-License-Identifier: MIT pragma solidity =0.8.19; -import {IHats} from "../interfaces/hats/IHats.sol"; +import {IHats} from "../interfaces/hats/full/IHats.sol"; contract MockHats is IHats { - uint256 public count = 0; - mapping(uint256 => address) hatWearers; + uint256 public hatId = 0; + mapping(uint256 => address) public wearers; + mapping(uint256 => address) public eligibility; + + event HatCreated(uint256 hatId); function mintTopHat( address _target, string memory, string memory ) external returns (uint256 topHatId) { - topHatId = count; - count++; - hatWearers[topHatId] = _target; + topHatId = hatId; + hatId++; + wearers[topHatId] = _target; } function createHat( uint256, string calldata, uint32, - address, + address _eligibility, address, bool, string calldata ) external returns (uint256 newHatId) { - newHatId = count; - count++; + newHatId = hatId; + hatId++; + eligibility[hatId] = _eligibility; + emit HatCreated(hatId); } function mintHat( - uint256 hatId, - address wearer + uint256 _hatId, + address _wearer ) external returns (bool success) { success = true; - hatWearers[hatId] = wearer; + wearers[_hatId] = _wearer; } function transferHat(uint256 _hatId, address _from, address _to) external { - require( - hatWearers[_hatId] == _from, - "MockHats: Invalid current wearer" - ); - hatWearers[_hatId] = _to; + require(wearers[_hatId] == _from, "MockHats: Invalid current wearer"); + wearers[_hatId] = _to; } function isWearerOfHat( - address _user, + address _wearer, uint256 _hatId - ) external view returns (bool isWearer) { - isWearer = hatWearers[_hatId] == _user; + ) external view override returns (bool) { + return _wearer == wearers[_hatId]; } - function changeHatEligibility(uint256, address) external {} - function getHatEligibilityModule( uint256 _hatId - ) external view returns (address eligibility) {} + ) external view override returns (address) { + return eligibility[_hatId]; + } + + function changeHatEligibility( + uint256 _hatId, + address _newEligibility + ) external override { + eligibility[_hatId] = _newEligibility; + } + + function buildHatId( + uint256 _admin, + uint16 _newHat + ) external pure override returns (uint256 id) {} + + function getHatLevel( + uint256 _hatId + ) external view override returns (uint32 level) {} + + function getLocalHatLevel( + uint256 _hatId + ) external pure override returns (uint32 level) {} + + function isTopHat( + uint256 _hatId + ) external view override returns (bool _topHat) {} + + function isLocalTopHat( + uint256 _hatId + ) external pure override returns (bool _localTopHat) {} + + function isValidHatId( + uint256 _hatId + ) external view override returns (bool validHatId) {} + + function getAdminAtLevel( + uint256 _hatId, + uint32 _level + ) external view override returns (uint256 admin) {} + + function getAdminAtLocalLevel( + uint256 _hatId, + uint32 _level + ) external pure override returns (uint256 admin) {} + + function getTopHatDomain( + uint256 _hatId + ) external view override returns (uint32 domain) {} + + function getTippyTopHatDomain( + uint32 _topHatDomain + ) external view override returns (uint32 domain) {} + + function noCircularLinkage( + uint32 _topHatDomain, + uint256 _linkedAdmin + ) external view override returns (bool notCircular) {} + + function sameTippyTopHatDomain( + uint32 _topHatDomain, + uint256 _newAdminHat + ) external view override returns (bool sameDomain) {} + + function batchCreateHats( + uint256[] calldata _admins, + string[] calldata _details, + uint32[] calldata _maxSupplies, + address[] memory _eligibilityModules, + address[] memory _toggleModules, + bool[] calldata _mutables, + string[] calldata _imageURIs + ) external override returns (bool success) {} + + function getNextId( + uint256 + ) external view override returns (uint256 nextId) { + nextId = hatId; + } + + function batchMintHats( + uint256[] calldata _hatIds, + address[] calldata _wearers + ) external override returns (bool success) {} + + function setHatStatus( + uint256 _hatId, + bool _newStatus + ) external override returns (bool toggled) {} + + function checkHatStatus( + uint256 _hatId + ) external override returns (bool toggled) {} - function getNextId(uint256) external view returns (uint256 nextId) { - nextId = count; + function setHatWearerStatus( + uint256 _hatId, + address _wearer, + bool _eligible, + bool _standing + ) external override returns (bool updated) {} + + function checkHatWearerStatus( + uint256 _hatId, + address + ) external override returns (bool updated) { + // 'burns' the hat if the wearer is no longer eligible + wearers[_hatId] = address(0); + return true; } + + function renounceHat(uint256 _hatId) external override {} + + function makeHatImmutable(uint256 _hatId) external override {} + + function changeHatDetails( + uint256 _hatId, + string memory _newDetails + ) external override {} + + function changeHatToggle( + uint256 _hatId, + address _newToggle + ) external override {} + + function changeHatImageURI( + uint256 _hatId, + string memory _newImageURI + ) external override {} + + function changeHatMaxSupply( + uint256 _hatId, + uint32 _newMaxSupply + ) external override {} + + function requestLinkTopHatToTree( + uint32 _topHatId, + uint256 _newAdminHat + ) external override {} + + function approveLinkTopHatToTree( + uint32 _topHatId, + uint256 _newAdminHat, + address _eligibility, + address _toggle, + string calldata _details, + string calldata _imageURI + ) external override {} + + function unlinkTopHatFromTree( + uint32 _topHatId, + address _wearer + ) external override {} + + function relinkTopHatWithinTree( + uint32 _topHatDomain, + uint256 _newAdminHat, + address _eligibility, + address _toggle, + string calldata _details, + string calldata _imageURI + ) external override {} + + function viewHat( + uint256 _hatId + ) + external + view + override + returns ( + string memory _details, + uint32 _maxSupply, + uint32 _supply, + address _eligibility, + address _toggle, + string memory _imageURI, + uint16 _lastHatId, + bool _mutable, + bool _active + ) + {} + + function isAdminOfHat( + address _user, + uint256 _hatId + ) external view override returns (bool isAdmin) {} + + function isInGoodStanding( + address _wearer, + uint256 _hatId + ) external view override returns (bool standing) {} + + function isEligible( + address _wearer, + uint256 _hatId + ) external view override returns (bool eligible) {} + + function getHatToggleModule( + uint256 _hatId + ) external view override returns (address toggle) {} + + function getHatMaxSupply( + uint256 _hatId + ) external view override returns (uint32 maxSupply) {} + + function hatSupply( + uint256 _hatId + ) external view override returns (uint32 supply) {} + + function getImageURIForHat( + uint256 _hatId + ) external view override returns (string memory _uri) {} + + function balanceOf( + address _wearer, + uint256 _hatId + ) external view override returns (uint256 balance) {} + + function balanceOfBatch( + address[] calldata _wearers, + uint256[] calldata _hatIds + ) external view override returns (uint256[] memory) {} + + function uri( + uint256 id + ) external view override returns (string memory _uri) {} } diff --git a/contracts/mocks/MockHatsAdmin.sol b/contracts/mocks/MockHatsAdmin.sol deleted file mode 100644 index bddb1368..00000000 --- a/contracts/mocks/MockHatsAdmin.sol +++ /dev/null @@ -1,284 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.19; - -import {IHats} from "../interfaces/hats/full/IHats.sol"; - -contract MockHatsAutoAdmin is IHats { - uint256 hatId = 0; - mapping(uint256 => address) public wearer; - mapping(uint256 => address) public eligibility; - - event HatCreated(uint256 hatId); - - function mintTopHat( - address, - string memory, - string memory - ) external pure returns (uint256 topHatId) { - return 0; - } - - function createHat( - uint256, - string calldata, - uint32, - address _eligibility, - address, - bool, - string calldata - ) external returns (uint256 newHatId) { - hatId++; - eligibility[hatId] = _eligibility; - emit HatCreated(hatId); - return hatId; - } - - function mintHat( - uint256 _hatId, - address _wearer - ) external returns (bool success) { - wearer[_hatId] = _wearer; - return true; - } - - function isWearerOfHat( - address _wearer, - uint256 _hatId - ) external view override returns (bool) { - return _wearer == wearer[_hatId]; - } - - function getHatEligibilityModule( - uint256 _hatId - ) external view override returns (address) { - return eligibility[_hatId]; - } - - function transferHat( - uint256 _hatId, - address, - address to - ) external override { - wearer[_hatId] = to; - } - - function changeHatEligibility( - uint256 _hatId, - address _newEligibility - ) external override { - eligibility[_hatId] = _newEligibility; - } - - function buildHatId( - uint256 _admin, - uint16 _newHat - ) external pure override returns (uint256 id) {} - - function getHatLevel( - uint256 _hatId - ) external view override returns (uint32 level) {} - - function getLocalHatLevel( - uint256 _hatId - ) external pure override returns (uint32 level) {} - - function isTopHat( - uint256 _hatId - ) external view override returns (bool _topHat) {} - - function isLocalTopHat( - uint256 _hatId - ) external pure override returns (bool _localTopHat) {} - - function isValidHatId( - uint256 _hatId - ) external view override returns (bool validHatId) {} - - function getAdminAtLevel( - uint256 _hatId, - uint32 _level - ) external view override returns (uint256 admin) {} - - function getAdminAtLocalLevel( - uint256 _hatId, - uint32 _level - ) external pure override returns (uint256 admin) {} - - function getTopHatDomain( - uint256 _hatId - ) external view override returns (uint32 domain) {} - - function getTippyTopHatDomain( - uint32 _topHatDomain - ) external view override returns (uint32 domain) {} - - function noCircularLinkage( - uint32 _topHatDomain, - uint256 _linkedAdmin - ) external view override returns (bool notCircular) {} - - function sameTippyTopHatDomain( - uint32 _topHatDomain, - uint256 _newAdminHat - ) external view override returns (bool sameDomain) {} - - function batchCreateHats( - uint256[] calldata _admins, - string[] calldata _details, - uint32[] calldata _maxSupplies, - address[] memory _eligibilityModules, - address[] memory _toggleModules, - bool[] calldata _mutables, - string[] calldata _imageURIs - ) external override returns (bool success) {} - - function getNextId( - uint256 _admin - ) external view override returns (uint256 nextId) {} - - function batchMintHats( - uint256[] calldata _hatIds, - address[] calldata _wearers - ) external override returns (bool success) {} - - function setHatStatus( - uint256 _hatId, - bool _newStatus - ) external override returns (bool toggled) {} - - function checkHatStatus( - uint256 _hatId - ) external override returns (bool toggled) {} - - function setHatWearerStatus( - uint256 _hatId, - address _wearer, - bool _eligible, - bool _standing - ) external override returns (bool updated) {} - - function checkHatWearerStatus( - uint256 _hatId, - address - ) external override returns (bool updated) { - // 'burns' the hat if the wearer is no longer eligible - wearer[_hatId] = address(0); - return true; - } - - function renounceHat(uint256 _hatId) external override {} - - function makeHatImmutable(uint256 _hatId) external override {} - - function changeHatDetails( - uint256 _hatId, - string memory _newDetails - ) external override {} - - function changeHatToggle( - uint256 _hatId, - address _newToggle - ) external override {} - - function changeHatImageURI( - uint256 _hatId, - string memory _newImageURI - ) external override {} - - function changeHatMaxSupply( - uint256 _hatId, - uint32 _newMaxSupply - ) external override {} - - function requestLinkTopHatToTree( - uint32 _topHatId, - uint256 _newAdminHat - ) external override {} - - function approveLinkTopHatToTree( - uint32 _topHatId, - uint256 _newAdminHat, - address _eligibility, - address _toggle, - string calldata _details, - string calldata _imageURI - ) external override {} - - function unlinkTopHatFromTree( - uint32 _topHatId, - address _wearer - ) external override {} - - function relinkTopHatWithinTree( - uint32 _topHatDomain, - uint256 _newAdminHat, - address _eligibility, - address _toggle, - string calldata _details, - string calldata _imageURI - ) external override {} - - function viewHat( - uint256 _hatId - ) - external - view - override - returns ( - string memory _details, - uint32 _maxSupply, - uint32 _supply, - address _eligibility, - address _toggle, - string memory _imageURI, - uint16 _lastHatId, - bool _mutable, - bool _active - ) - {} - - function isAdminOfHat( - address _user, - uint256 _hatId - ) external view override returns (bool isAdmin) {} - - function isInGoodStanding( - address _wearer, - uint256 _hatId - ) external view override returns (bool standing) {} - - function isEligible( - address _wearer, - uint256 _hatId - ) external view override returns (bool eligible) {} - - function getHatToggleModule( - uint256 _hatId - ) external view override returns (address toggle) {} - - function getHatMaxSupply( - uint256 _hatId - ) external view override returns (uint32 maxSupply) {} - - function hatSupply( - uint256 _hatId - ) external view override returns (uint32 supply) {} - - function getImageURIForHat( - uint256 _hatId - ) external view override returns (string memory _uri) {} - - function balanceOf( - address _wearer, - uint256 _hatId - ) external view override returns (uint256 balance) {} - - function balanceOfBatch( - address[] calldata _wearers, - uint256[] calldata _hatIds - ) external view override returns (uint256[] memory) {} - - function uri( - uint256 id - ) external view override returns (string memory _uri) {} -} diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index 7061b1ac..c19a5db8 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -4,8 +4,8 @@ import hre from 'hardhat'; import { DecentAutonomousAdmin, DecentAutonomousAdmin__factory, - MockHatsAutoAdmin, - MockHatsAutoAdmin__factory, + MockHats, + MockHats__factory, MockHatsElectionEligibility, MockHatsElectionEligibility__factory, } from '../typechain-types'; @@ -18,7 +18,7 @@ describe('DecentAutonomousAdminHat', function () { let nominatedWearer: SignerWithAddress; // Contract instances - let hatsProtocol: MockHatsAutoAdmin; + let hatsProtocol: MockHats; let hatsElectionModule: MockHatsElectionEligibility; let adminHat: DecentAutonomousAdmin; @@ -30,14 +30,14 @@ describe('DecentAutonomousAdminHat', function () { [deployer, currentWearer, nominatedWearer, randomUser] = await hre.ethers.getSigners(); // Deploy MockHatsAutoAdmin (Mock Hats Protocol) - hatsProtocol = await new MockHatsAutoAdmin__factory(deployer).deploy(); + hatsProtocol = await new MockHats__factory(deployer).deploy(); // Deploy MockHatsElectionEligibility (Eligibility Module) hatsElectionModule = await new MockHatsElectionEligibility__factory(deployer).deploy(); // Create Admin Hat const createAdminTx = await hatsProtocol.createHat( - await hatsProtocol.getAddress(), // Admin address (self-administered) + hre.ethers.ZeroAddress, // Admin address (self-administered), currently unused 'Details', // Hat details 100, // Max supply hre.ethers.ZeroAddress, // Eligibility module (none) @@ -56,7 +56,7 @@ describe('DecentAutonomousAdminHat', function () { // Create User Hat under the admin hat const createUserTx = await hatsProtocol.createHat( - adminHatAddress, // Admin address (adminHat contract) + hre.ethers.ZeroAddress, // Admin address (adminHat contract), currently unused 'Details', // Hat details 100, // Max supply await hatsElectionModule.getAddress(), // Eligibility module (election module) @@ -76,10 +76,9 @@ describe('DecentAutonomousAdminHat', function () { it('should correctly validate current wearer and transfer', async function () { const args = { currentWearer: currentWearer.address, - userHatProtocol: await hatsProtocol.getAddress(), - userHatId: userHatId, + hatsProtocol: await hatsProtocol.getAddress(), + hatId: userHatId, nominatedWearer: nominatedWearer.address, - sablierStreamInfo: [], // No Sablier stream info for this test }; // Call triggerStartNextTerm on the adminHat contract @@ -93,8 +92,8 @@ describe('DecentAutonomousAdminHat', function () { it('should correctly invalidate random address as current wearer', async function () { const args = { currentWearer: randomUser.address, - userHatProtocol: await hatsProtocol.getAddress(), - userHatId: userHatId, + hatsProtocol: await hatsProtocol.getAddress(), + hatId: userHatId, nominatedWearer: nominatedWearer.address, sablierStreamInfo: [], // No Sablier stream info for this test }; diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index 56fdcbe8..c35f9568 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -10,13 +10,13 @@ import { DecentHats__factory, KeyValuePairs, KeyValuePairs__factory, - MockHats__factory, ERC6551Registry__factory, MockHatsAccount__factory, ERC6551Registry, DecentHats, MockHatsAccount, MockHats, + MockHats__factory, MockSablierV2LockupLinear__factory, MockSablierV2LockupLinear, MockERC20__factory, @@ -300,18 +300,16 @@ describe('DecentHats', () => { describe('Creating Hats Accounts', () => { it('Generates the correct Addresses for the current Hats', async () => { - const currentCount = await mockHats.count(); - + const currentCount = await mockHats.hatId(); for (let i = 0n; i < currentCount; i++) { - const topHatAccount = await getHatAccount( + const hatAccount = await getHatAccount( i, erc6551Registry, mockHatsAccountImplementationAddress, mockHatsAddress, ); - - expect(await topHatAccount.tokenId()).eq(i); - expect(await topHatAccount.tokenImplementation()).eq(mockHatsAddress); + expect(await hatAccount.tokenId()).eq(i); + expect(await hatAccount.tokenImplementation()).eq(mockHatsAddress); } }); }); @@ -813,12 +811,12 @@ describe('DecentHats', () => { // First transfer the top hat to the Safe await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); - const hatsCountBeforeCreate = await mockHats.count(); + const hatsCountBeforeCreate = await mockHats.hatId(); expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat await createRoleHatPromise; - const newHatId = await mockHats.count(); + const newHatId = await mockHats.hatId(); expect(newHatId).to.equal(3); // + newly created hat }); }); @@ -957,12 +955,12 @@ describe('DecentHats', () => { // First transfer the top hat to the Safe await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); - const hatsCountBeforeCreate = await mockHats.count(); + const hatsCountBeforeCreate = await mockHats.hatId(); expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat await createRoleHatPromise; - const newHatId = await mockHats.count(); + const newHatId = await mockHats.hatId(); expect(newHatId).to.equal(3); // + newly created hat }); }); diff --git a/test/DecentHats_0_1_0.test.ts b/test/DecentHats_0_1_0.test.ts index dd9a3654..194049ef 100644 --- a/test/DecentHats_0_1_0.test.ts +++ b/test/DecentHats_0_1_0.test.ts @@ -254,7 +254,7 @@ describe('DecentHats_0_1_0', () => { describe('Creating Hats Accounts', () => { it('Generates the correct Addresses for the current Hats', async () => { - const currentCount = await mockHats.count(); + const currentCount = await mockHats.hatId(); for (let i = 0n; i < currentCount; i++) { const topHatAccount = await getHatAccount( @@ -621,12 +621,12 @@ describe('DecentHats_0_1_0', () => { // First transfer the top hat to the Safe await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); - const hatsCountBeforeCreate = await mockHats.count(); + const hatsCountBeforeCreate = await mockHats.hatId(); expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat await createRoleHatPromise; - const newHatId = await mockHats.count(); + const newHatId = await mockHats.hatId(); expect(newHatId).to.equal(3); // + newly created hat }); }); From ba608297a2f98e52155b842aab939a6b6c727ec3 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 29 Oct 2024 13:07:22 -0400 Subject: [PATCH 101/119] Update test name --- test/DecentHats.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index c35f9568..15cdaad9 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -821,7 +821,7 @@ describe('DecentHats', () => { }); }); - describe('Creating a new hat on existing Tree', () => { + describe('Creating a new Termed hat on existing tree', () => { let createRoleHatPromise: Promise; const topHatId = 0; From 3d771f487726706a65cb77ba73312d0831412ad7 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:31:29 -0400 Subject: [PATCH 102/119] review feedback --- contracts/DecentHats.sol | 30 ++++---- test/DecentHats.test.ts | 146 ++++++++++++++++++--------------------- 2 files changed, 81 insertions(+), 95 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 52159e22..408ab7d0 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -30,7 +30,7 @@ contract DecentHats { bool transferable; } - struct TermedParams { + struct TermedParam { uint128 termEndDateTs; address[] nominatedWearers; } @@ -40,10 +40,9 @@ contract DecentHats { string details; string imageURI; SablierStreamParams[] sablierParams; - TermedParams[] termedParams; + TermedParam termedParam; uint32 maxSupply; bool isMutable; - bool isTermed; } struct CreateTreeParams { @@ -127,7 +126,8 @@ contract DecentHats { ); for (uint256 i = 0; i < params.hats.length; ) { - if (params.hats[i].isTermed) { + // {assuption} if 0 nominatedWearers, then it is not a termed role + if (params.hats[i].termedParam.nominatedWearers.length > 0) { // Create election module and set as eligiblity _createTermedHatAndAccountAndMintAndStreams( params.hatsProtocol, @@ -137,7 +137,7 @@ contract DecentHats { params.hatsElectionEligibilityImplementation, params.hatsProtocol.getNextId(adminHatId), topHatId, - params.hats[i].termedParams[0] + params.hats[i].termedParam ), adminHatId, params.hats[i] @@ -225,7 +225,7 @@ contract DecentHats { params.hatsElectionEligibilityImplementation, params.hatsProtocol.getNextId(params.adminHatId), params.topHatId, - params.hat.termedParams[0] + params.hat.termedParam ), params.adminHatId, params.hat @@ -359,11 +359,7 @@ contract DecentHats { Hat calldata hat ) internal { require( - hat.termedParams.length == 1, - "DecentHats: termedParams length must be 1" - ); - require( - hat.termedParams[0].nominatedWearers.length == 1, + hat.termedParam.nominatedWearers.length == 1, "DecentHats: nominatedWearers length must be 1" ); uint256 hatId = _createHat( @@ -375,16 +371,16 @@ contract DecentHats { ); IHatsElectionEligibility(eligibilityAddress).elect( - hat.termedParams[0].termEndDateTs, - hat.termedParams[0].nominatedWearers + hat.termedParam.termEndDateTs, + hat.termedParam.nominatedWearers ); - hatsProtocol.mintHat(hatId, hat.termedParams[0].nominatedWearers[0]); + hatsProtocol.mintHat(hatId, hat.termedParam.nominatedWearers[0]); for (uint256 i = 0; i < hat.sablierParams.length; ) { _createSablierStream( hat.sablierParams[i], - hat.termedParams[0].nominatedWearers[0] + hat.termedParam.nominatedWearers[0] ); unchecked { ++i; @@ -432,13 +428,13 @@ contract DecentHats { address hatsElectionEligibilityImplementation, uint256 hatId, uint256 topHatId, - TermedParams calldata termedParams + TermedParam calldata termedParam ) internal returns (address electionModuleAddress) { electionModuleAddress = hatsModuleFactory.createHatsModule( hatsElectionEligibilityImplementation, hatId, abi.encode(topHatId, uint256(0)), - abi.encode(termedParams.termEndDateTs), + abi.encode(termedParam.termEndDateTs), uint256(SALT) ); } diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index 15cdaad9..3cd4b795 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -184,8 +184,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, hats: [ { @@ -195,8 +197,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, { maxSupply: 1, @@ -205,8 +209,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -262,13 +268,11 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, hats: [], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -337,11 +341,13 @@ describe('DecentHats', () => { maxSupply: 1, details: '', imageURI: '', - isMutable: false, + isMutable: true, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, hats: [ { @@ -351,13 +357,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: true, - termedParams: [ - { - termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], - }, - ], + termedParam: { + termEndDateTs: BigInt(Date.now() + 100000), + nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], + }, }, { maxSupply: 1, @@ -366,13 +369,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: true, - termedParams: [ - { - termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], - }, - ], + termedParam: { + termEndDateTs: BigInt(Date.now() + 100000), + nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], + }, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -431,13 +431,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, hats: [ { @@ -462,13 +459,10 @@ describe('DecentHats', () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], - isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, { maxSupply: 1, @@ -477,13 +471,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -573,13 +564,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, hats: [ { @@ -618,13 +606,10 @@ describe('DecentHats', () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], - isTermed: false, - termedParams: [ - { - termEndDateTs: 0, - nominatedWearers: [], - }, - ], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -711,8 +696,10 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [], + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: @@ -750,8 +737,11 @@ describe('DecentHats', () => { imageURI: '', isMutable: true, wearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', - isTermed: false, - termedParams: [], + + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, sablierParams: [ { sablier: mockSablierAddress, @@ -847,8 +837,11 @@ describe('DecentHats', () => { isMutable: false, wearer: ethers.ZeroAddress, sablierParams: [], - isTermed: false, - termedParams: [], + + termedParam: { + termEndDateTs: 0, + nominatedWearers: [], + }, }, hatsModuleFactory: mockHatsModuleFactoryAddress, hatsElectionEligibilityImplementation: @@ -889,13 +882,10 @@ describe('DecentHats', () => { imageURI: '', isMutable: true, wearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', - isTermed: true, - termedParams: [ - { - termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearers: ['0xdce7ca0555101f97451926944f5ae3b7adb2f5ae'], - }, - ], + termedParam: { + termEndDateTs: BigInt(Date.now() + 100000), + nominatedWearers: ['0xdce7ca0555101f97451926944f5ae3b7adb2f5ae'], + }, sablierParams: [ { sablier: mockSablierAddress, From 4a93ed05a6422d4472b96eb46b3f241824c84134 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 29 Oct 2024 13:44:34 -0400 Subject: [PATCH 103/119] nominatedWearer now a single address to match business logic --- contracts/DecentHats.sol | 16 +++++++--------- test/DecentHats.test.ts | 32 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 408ab7d0..04d03b2d 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -32,7 +32,7 @@ contract DecentHats { struct TermedParam { uint128 termEndDateTs; - address[] nominatedWearers; + address nominatedWearer; } struct Hat { @@ -127,7 +127,7 @@ contract DecentHats { for (uint256 i = 0; i < params.hats.length; ) { // {assuption} if 0 nominatedWearers, then it is not a termed role - if (params.hats[i].termedParam.nominatedWearers.length > 0) { + if (params.hats[i].termedParam.nominatedWearer != address(0)) { // Create election module and set as eligiblity _createTermedHatAndAccountAndMintAndStreams( params.hatsProtocol, @@ -358,10 +358,6 @@ contract DecentHats { uint256 adminHatId, Hat calldata hat ) internal { - require( - hat.termedParam.nominatedWearers.length == 1, - "DecentHats: nominatedWearers length must be 1" - ); uint256 hatId = _createHat( hatsProtocol, adminHatId, @@ -370,17 +366,19 @@ contract DecentHats { eligibilityAddress ); + address[] memory nominatedWearers = new address[](1); + nominatedWearers[0] = hat.termedParam.nominatedWearer; IHatsElectionEligibility(eligibilityAddress).elect( hat.termedParam.termEndDateTs, - hat.termedParam.nominatedWearers + nominatedWearers ); - hatsProtocol.mintHat(hatId, hat.termedParam.nominatedWearers[0]); + hatsProtocol.mintHat(hatId, hat.termedParam.nominatedWearer); for (uint256 i = 0; i < hat.sablierParams.length; ) { _createSablierStream( hat.sablierParams[i], - hat.termedParam.nominatedWearers[0] + hat.termedParam.nominatedWearer ); unchecked { ++i; diff --git a/test/DecentHats.test.ts b/test/DecentHats.test.ts index 3cd4b795..802ca10c 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHats.test.ts @@ -186,7 +186,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, hats: [ @@ -199,7 +199,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, { @@ -211,7 +211,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, ], @@ -271,7 +271,7 @@ describe('DecentHats', () => { termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, hats: [], @@ -346,7 +346,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, hats: [ @@ -359,7 +359,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], + nominatedWearer: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', }, }, { @@ -371,7 +371,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearers: ['0x14dC79964da2C08b23698B3D3cc7Ca32193d9955'], + nominatedWearer: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', }, }, ], @@ -433,7 +433,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, hats: [ @@ -461,7 +461,7 @@ describe('DecentHats', () => { ], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, { @@ -473,7 +473,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, ], @@ -566,7 +566,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, hats: [ @@ -608,7 +608,7 @@ describe('DecentHats', () => { ], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, ], @@ -698,7 +698,7 @@ describe('DecentHats', () => { sablierParams: [], termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -740,7 +740,7 @@ describe('DecentHats', () => { termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, sablierParams: [ { @@ -840,7 +840,7 @@ describe('DecentHats', () => { termedParam: { termEndDateTs: 0, - nominatedWearers: [], + nominatedWearer: ethers.ZeroAddress, }, }, hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -884,7 +884,7 @@ describe('DecentHats', () => { wearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', termedParam: { termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearers: ['0xdce7ca0555101f97451926944f5ae3b7adb2f5ae'], + nominatedWearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', }, sablierParams: [ { From aa4d6a672efaacafc09c3a0b2e10487078423a85 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 29 Oct 2024 13:45:31 -0400 Subject: [PATCH 104/119] Remove NAME from contract --- contracts/DecentHats.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 04d03b2d..bdc89631 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -15,7 +15,6 @@ import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProx import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; contract DecentHats { - string public constant NAME = "DecentHats"; bytes32 public constant SALT = 0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072; From d18ad255d7dd314a2afebd840d78bf19f4a03ceb Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 29 Oct 2024 13:46:41 -0400 Subject: [PATCH 105/119] Remove unused import --- contracts/DecentHats.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index bdc89631..2912dada 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -8,7 +8,6 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; import {IHats} from "./interfaces/hats/full/IHats.sol"; import {LockupLinear, Broker} from "./interfaces/sablier/full/types/DataTypes.sol"; -import {DecentAutonomousAdmin} from "./DecentAutonomousAdmin.sol"; import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol"; import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; From 1c0c3b95b8bc50009927c607e9bd189d48d2a170 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:58:37 -0400 Subject: [PATCH 106/119] use error and revert --- contracts/DecentAutonomousAdmin.sol | 11 ++++------- contracts/interfaces/IDecentAutonomousAdmin.sol | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 07c38c97..37bf73fb 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -21,13 +21,10 @@ contract DecentAutonomousAdmin is // Public Functions // ////////////////////////////////////////////////////////////// function triggerStartNextTerm(TriggerStartArgs calldata args) public { - require( - args.hatsProtocol.isWearerOfHat(args.currentWearer, args.hatId), - "Not current wearer" - ); - address hatsEligibilityModuleAddress = args - .hatsProtocol - .getHatEligibilityModule(args.hatId); + if ( + args.hatsProtocol.isWearerOfHat(args.currentWearer, args.hatId) == + false + ) revert NotCurrentWearer(); IHatsElectionEligibility hatsElectionModule = IHatsElectionEligibility( hatsEligibilityModuleAddress diff --git a/contracts/interfaces/IDecentAutonomousAdmin.sol b/contracts/interfaces/IDecentAutonomousAdmin.sol index affa38bf..2591b02e 100644 --- a/contracts/interfaces/IDecentAutonomousAdmin.sol +++ b/contracts/interfaces/IDecentAutonomousAdmin.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.28; import {IHats} from "./hats/full/IHats.sol"; interface IDecentAutonomousAdmin { + error NotCurrentWearer(); struct TriggerStartArgs { address currentWearer; IHats hatsProtocol; From 4ffbbe7911ff8b852b94a9f869771248a080e875 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:58:59 -0400 Subject: [PATCH 107/119] remove declaration and pass directly --- contracts/DecentAutonomousAdmin.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 37bf73fb..36bdf039 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -27,7 +27,7 @@ contract DecentAutonomousAdmin is ) revert NotCurrentWearer(); IHatsElectionEligibility hatsElectionModule = IHatsElectionEligibility( - hatsEligibilityModuleAddress + args.hatsProtocol.getHatEligibilityModule(args.hatId) ); hatsElectionModule.startNextTerm(); From eaea6021ee65338121751386589b18d8e21deb16 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:34:56 -0400 Subject: [PATCH 108/119] remove _createHat internal func and call directly --- contracts/DecentHats.sol | 49 +++++++++++++++------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 2912dada..df2b3595 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -261,25 +261,6 @@ contract DecentHats { ); } - function _createHat( - IHats _hatsProtocol, - uint256 adminHatId, - Hat memory _hat, - address toggle, - address eligibility - ) internal returns (uint256) { - return - _hatsProtocol.createHat( - adminHatId, - _hat.details, - _hat.maxSupply, - eligibility, - toggle, - _hat.isMutable, - _hat.imageURI - ); - } - function _createAccount( IERC6551Registry _registry, address _hatsAccountImplementation, @@ -325,12 +306,14 @@ contract DecentHats { uint256 adminHatId, Hat calldata hat ) internal returns (uint256 hatId, address accountAddress) { - hatId = _createHat( - hatsProtocol, + hatId = hatsProtocol.createHat( adminHatId, - hat, + hat.details, + hat.maxSupply, topHatAccount, - topHatAccount + topHatAccount, + hat.isMutable, + hat.imageURI ); accountAddress = _createAccount( registry, @@ -356,12 +339,14 @@ contract DecentHats { uint256 adminHatId, Hat calldata hat ) internal { - uint256 hatId = _createHat( - hatsProtocol, + uint256 hatId = hatsProtocol.createHat( adminHatId, - hat, + hat.details, + hat.maxSupply, + eligibilityAddress, topHatAccount, - eligibilityAddress + hat.isMutable, + hat.imageURI ); address[] memory nominatedWearers = new address[](1); @@ -394,12 +379,14 @@ contract DecentHats { uint256 topHatId, Hat calldata hat ) internal returns (uint256 adminHatId, address accountAddress) { - adminHatId = _createHat( - hatsProtocol, + adminHatId = hatsProtocol.createHat( topHatId, - hat, + hat.details, + hat.maxSupply, + topHatAccount, topHatAccount, - topHatAccount + hat.isMutable, + hat.imageURI ); accountAddress = _createAccount( From 59e2c6bd261b170c442548ca96db5370f89569ab Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:41:18 -0400 Subject: [PATCH 109/119] remove _createElectionEligiblityModule and use function call directly --- contracts/DecentHats.sol | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index df2b3595..6dc8c833 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -130,12 +130,12 @@ contract DecentHats { _createTermedHatAndAccountAndMintAndStreams( params.hatsProtocol, topHatAccount, - _createElectionEligiblityModule( - params.hatsModuleFactory, + params.hatsModuleFactory.createHatsModule( params.hatsElectionEligibilityImplementation, params.hatsProtocol.getNextId(adminHatId), - topHatId, - params.hats[i].termedParam + abi.encode(topHatId, uint256(0)), + abi.encode(params.hats[i].termedParam.termEndDateTs), + uint256(SALT) ), adminHatId, params.hats[i] @@ -218,12 +218,12 @@ contract DecentHats { _createTermedHatAndAccountAndMintAndStreams( params.hatsProtocol, params.topHatAccount, - _createElectionEligiblityModule( - params.hatsModuleFactory, + params.hatsModuleFactory.createHatsModule( params.hatsElectionEligibilityImplementation, params.hatsProtocol.getNextId(params.adminHatId), - params.topHatId, - params.hat.termedParam + abi.encode(params.topHatId, uint256(0)), + abi.encode(params.hat.termedParam.termEndDateTs), + uint256(SALT) ), params.adminHatId, params.hat @@ -406,22 +406,6 @@ contract DecentHats { ); } - function _createElectionEligiblityModule( - IHatsModuleFactory hatsModuleFactory, - address hatsElectionEligibilityImplementation, - uint256 hatId, - uint256 topHatId, - TermedParam calldata termedParam - ) internal returns (address electionModuleAddress) { - electionModuleAddress = hatsModuleFactory.createHatsModule( - hatsElectionEligibilityImplementation, - hatId, - abi.encode(topHatId, uint256(0)), - abi.encode(termedParam.termEndDateTs), - uint256(SALT) - ); - } - function _createSablierStream( SablierStreamParams memory sablierParams, address recipient From 21fca7f0907498005d8cf0cacdd86144663c6589 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:43:55 -0400 Subject: [PATCH 110/119] remove _createAccount and use function call directly --- contracts/DecentHats.sol | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 6dc8c833..510061a3 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -261,22 +261,6 @@ contract DecentHats { ); } - function _createAccount( - IERC6551Registry _registry, - address _hatsAccountImplementation, - address protocolAddress, - uint256 hatId - ) internal returns (address) { - return - _registry.createAccount( - _hatsAccountImplementation, - SALT, - block.chainid, - protocolAddress, - hatId - ); - } - function _createTopHatAndAccount( IHats _hatsProtocol, string memory _topHatDetails, @@ -290,9 +274,10 @@ contract DecentHats { _topHatImageURI ); - topHatAccount = _createAccount( - _registry, + topHatAccount = _registry.createAccount( _hatsAccountImplementation, + SALT, + block.chainid, address(_hatsProtocol), topHatId ); @@ -315,9 +300,11 @@ contract DecentHats { hat.isMutable, hat.imageURI ); - accountAddress = _createAccount( - registry, + + accountAddress = registry.createAccount( hatsAccountImplementation, + SALT, + block.chainid, address(hatsProtocol), hatId ); @@ -389,9 +376,10 @@ contract DecentHats { hat.imageURI ); - accountAddress = _createAccount( - registry, + accountAddress = registry.createAccount( hatsAccountImplementation, + SALT, + block.chainid, address(hatsProtocol), adminHatId ); From 622cb0980402d57c0276bb35765b82992c9c7276 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:46:58 -0400 Subject: [PATCH 111/119] add comments --- contracts/DecentHats.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/DecentHats.sol b/contracts/DecentHats.sol index 510061a3..5d133793 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHats.sol @@ -130,10 +130,10 @@ contract DecentHats { _createTermedHatAndAccountAndMintAndStreams( params.hatsProtocol, topHatAccount, - params.hatsModuleFactory.createHatsModule( + params.hatsModuleFactory.createHatsModule( // Create election module and set as eligibility params.hatsElectionEligibilityImplementation, params.hatsProtocol.getNextId(adminHatId), - abi.encode(topHatId, uint256(0)), + abi.encode(topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] abi.encode(params.hats[i].termedParam.termEndDateTs), uint256(SALT) ), @@ -218,10 +218,10 @@ contract DecentHats { _createTermedHatAndAccountAndMintAndStreams( params.hatsProtocol, params.topHatAccount, - params.hatsModuleFactory.createHatsModule( + params.hatsModuleFactory.createHatsModule( // Create election module and set as eligibility params.hatsElectionEligibilityImplementation, params.hatsProtocol.getNextId(params.adminHatId), - abi.encode(params.topHatId, uint256(0)), + abi.encode(params.topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] abi.encode(params.hat.termedParam.termEndDateTs), uint256(SALT) ), From b02cbe20834cb7de04abae1ccc8af8343ed3175f Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:52:42 -0400 Subject: [PATCH 112/119] update to customError for test --- test/DecentAutonomousAdmin.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index c19a5db8..c29abb52 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -99,9 +99,9 @@ describe('DecentAutonomousAdminHat', function () { }; // revert if not the current wearer - await expect(adminHat.connect(randomUser).triggerStartNextTerm(args)).to.be.revertedWith( - 'Not current wearer', - ); + await expect( + adminHat.connect(randomUser).triggerStartNextTerm(args), + ).to.be.revertedWithCustomError(adminHat, 'NotCurrentWearer'); }); }); }); From 1dbf281b46a4f6619160451ee05a8aaa1513309c Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:53:57 -0400 Subject: [PATCH 113/119] update name to make sense --- test/DecentAutonomousAdmin.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index c29abb52..a20b00a0 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -20,7 +20,7 @@ describe('DecentAutonomousAdminHat', function () { // Contract instances let hatsProtocol: MockHats; let hatsElectionModule: MockHatsElectionEligibility; - let adminHat: DecentAutonomousAdmin; + let decentAutonomousAdminInstance: DecentAutonomousAdmin; // Variables let userHatId: bigint; @@ -49,14 +49,14 @@ describe('DecentAutonomousAdminHat', function () { const adminHatId = createAdminTxReceipt?.toJSON().logs[0].args[0]; // Deploy DecentAutonomousAdminHat contract with the admin hat ID - adminHat = await new DecentAutonomousAdmin__factory(deployer).deploy(); - const adminHatAddress = await adminHat.getAddress(); + decentAutonomousAdminInstance = await new DecentAutonomousAdmin__factory(deployer).deploy(); + const adminHatAddress = await decentAutonomousAdminInstance.getAddress(); // Mint the admin hat to adminHatWearer await hatsProtocol.mintHat(adminHatId, adminHatAddress); // Create User Hat under the admin hat const createUserTx = await hatsProtocol.createHat( - hre.ethers.ZeroAddress, // Admin address (adminHat contract), currently unused + hre.ethers.ZeroAddress, // Admin address (decentAutonomousAdminInstance contract), currently unused 'Details', // Hat details 100, // Max supply await hatsElectionModule.getAddress(), // Eligibility module (election module) @@ -81,8 +81,8 @@ describe('DecentAutonomousAdminHat', function () { nominatedWearer: nominatedWearer.address, }; - // Call triggerStartNextTerm on the adminHat contract - await adminHat.triggerStartNextTerm(args); + // Call triggerStartNextTerm on the decentAutonomousAdminInstance contract + await decentAutonomousAdminInstance.triggerStartNextTerm(args); // Verify the hat is now worn by the nominated wearer expect((await hatsProtocol.isWearerOfHat(nominatedWearer.address, userHatId)) === true); @@ -100,8 +100,8 @@ describe('DecentAutonomousAdminHat', function () { // revert if not the current wearer await expect( - adminHat.connect(randomUser).triggerStartNextTerm(args), - ).to.be.revertedWithCustomError(adminHat, 'NotCurrentWearer'); + decentAutonomousAdminInstance.connect(randomUser).triggerStartNextTerm(args), + ).to.be.revertedWithCustomError(decentAutonomousAdminInstance, 'NotCurrentWearer'); }); }); }); From 68f6730d7d75ce3e56ef31884f381254da1637b2 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 30 Oct 2024 10:06:39 -0400 Subject: [PATCH 114/119] Rename contract to DecentHatsCreationModule --- ...tHats.sol => DecentHatsCreationModule.sol} | 2 +- ...st.ts => DecentHatsCreationModule.test.ts} | 92 ++++++++++--------- 2 files changed, 51 insertions(+), 43 deletions(-) rename contracts/{DecentHats.sol => DecentHatsCreationModule.sol} (99%) rename test/{DecentHats.test.ts => DecentHatsCreationModule.test.ts} (93%) diff --git a/contracts/DecentHats.sol b/contracts/DecentHatsCreationModule.sol similarity index 99% rename from contracts/DecentHats.sol rename to contracts/DecentHatsCreationModule.sol index 5d133793..ad3fa4a3 100644 --- a/contracts/DecentHats.sol +++ b/contracts/DecentHatsCreationModule.sol @@ -13,7 +13,7 @@ import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionElig import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; -contract DecentHats { +contract DecentHatsCreationModule { bytes32 public constant SALT = 0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072; diff --git a/test/DecentHats.test.ts b/test/DecentHatsCreationModule.test.ts similarity index 93% rename from test/DecentHats.test.ts rename to test/DecentHatsCreationModule.test.ts index 802ca10c..2f6d5956 100644 --- a/test/DecentHats.test.ts +++ b/test/DecentHatsCreationModule.test.ts @@ -7,13 +7,13 @@ import hre from 'hardhat'; import { GnosisSafeL2, GnosisSafeL2__factory, - DecentHats__factory, + DecentHatsCreationModule__factory, KeyValuePairs, KeyValuePairs__factory, ERC6551Registry__factory, MockHatsAccount__factory, ERC6551Registry, - DecentHats, + DecentHatsCreationModule, MockHatsAccount, MockHats, MockHats__factory, @@ -41,8 +41,8 @@ describe('DecentHats', () => { let keyValuePairs: KeyValuePairs; let gnosisSafe: GnosisSafeL2; - let decentHats: DecentHats; - let decentHatsAddress: string; + let decentHatsCreationModule: DecentHatsCreationModule; + let decentHatsCreationModuleAddress: string; let gnosisSafeAddress: string; let erc6551Registry: ERC6551Registry; @@ -82,8 +82,8 @@ describe('DecentHats', () => { erc6551Registry = await new ERC6551Registry__factory(deployer).deploy(); mockHatsAccountImplementation = await new MockHatsAccount__factory(deployer).deploy(); mockHatsAccountImplementationAddress = await mockHatsAccountImplementation.getAddress(); - decentHats = await new DecentHats__factory(deployer).deploy(); - decentHatsAddress = await decentHats.getAddress(); + decentHatsCreationModule = await new DecentHatsCreationModule__factory(deployer).deploy(); + decentHatsCreationModuleAddress = await decentHatsCreationModule.getAddress(); moduleProxyFactory = await new ModuleProxyFactory__factory(deployer).deploy(); decentAutonomousAdminMasterCopy = await new DecentAutonomousAdmin__factory(deployer).deploy(); @@ -144,7 +144,7 @@ describe('DecentHats', () => { to: gnosisSafeAddress, transactionData: GnosisSafeL2__factory.createInterface().encodeFunctionData( 'enableModule', - [decentHatsAddress], + [decentHatsCreationModuleAddress], ), signers: [dao], }); @@ -155,7 +155,9 @@ describe('DecentHats', () => { }); it('Emits an EnabledModule event', async () => { - await expect(enableModuleTx).to.emit(gnosisSafe, 'EnabledModule').withArgs(decentHatsAddress); + await expect(enableModuleTx) + .to.emit(gnosisSafe, 'EnabledModule') + .withArgs(decentHatsCreationModuleAddress); }); describe('Creating a new Top Hat and Tree', () => { @@ -164,8 +166,8 @@ describe('DecentHats', () => { beforeEach(async () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createAndDeclareTree', [ { @@ -232,7 +234,7 @@ describe('DecentHats', () => { it('Emits an ExecutionFromModuleSuccess event', async () => { await expect(createAndDeclareTreeTx) .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsAddress); + .withArgs(decentHatsCreationModuleAddress); }); it('Emits some hatsTreeId ValueUpdated events', async () => { @@ -247,8 +249,8 @@ describe('DecentHats', () => { beforeEach(async () => { createAndDeclareTreeTx2 = await executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createAndDeclareTree', [ { @@ -292,7 +294,7 @@ describe('DecentHats', () => { it('Emits an ExecutionFromModuleSuccess event', async () => { await expect(createAndDeclareTreeTx2) .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsAddress); + .withArgs(decentHatsCreationModuleAddress); }); it('Creates Top Hats with sequential IDs', async () => { @@ -324,8 +326,8 @@ describe('DecentHats', () => { beforeEach(async () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createAndDeclareTree', [ { @@ -392,7 +394,7 @@ describe('DecentHats', () => { it('Emits an ExecutionFromModuleSuccess event', async () => { await expect(createAndDeclareTreeTx) .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsAddress); + .withArgs(decentHatsCreationModuleAddress); }); it('Emits some hatsTreeId ValueUpdated events', async () => { @@ -411,8 +413,8 @@ describe('DecentHats', () => { createAndDeclareTreeTx = await executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createAndDeclareTree', [ { @@ -494,7 +496,7 @@ describe('DecentHats', () => { it('Emits an ExecutionFromModuleSuccess event', async () => { await expect(createAndDeclareTreeTx) .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsAddress); + .withArgs(decentHatsCreationModuleAddress); }); it('Emits some hatsTreeId ValueUpdated events', async () => { @@ -544,8 +546,8 @@ describe('DecentHats', () => { await executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createAndDeclareTree', [ { @@ -678,8 +680,8 @@ describe('DecentHats', () => { try { await executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createAndDeclareTree', [ { @@ -720,8 +722,8 @@ describe('DecentHats', () => { createRoleHatPromise = executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createRoleHat', [ { @@ -772,23 +774,26 @@ describe('DecentHats', () => { it('Emits an ExecutionSuccess event', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); await expect(await createRoleHatPromise).to.emit(gnosisSafe, 'ExecutionSuccess'); }); it('Emits an ExecutionFromModuleSuccess event', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); await expect(await createRoleHatPromise) .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsAddress); + .withArgs(decentHatsCreationModuleAddress); }); it('Transfers the top hat back to the Safe', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - const isModuleWearerOfTopHat = await mockHats.isWearerOfHat(decentHatsAddress, topHatId); + const isModuleWearerOfTopHat = await mockHats.isWearerOfHat( + decentHatsCreationModuleAddress, + topHatId, + ); expect(isModuleWearerOfTopHat).to.equal(true); await createRoleHatPromise; @@ -799,7 +804,7 @@ describe('DecentHats', () => { it('Actually creates the new hat', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); const hatsCountBeforeCreate = await mockHats.hatId(); expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat @@ -819,8 +824,8 @@ describe('DecentHats', () => { try { await executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createAndDeclareTree', [ { @@ -862,8 +867,8 @@ describe('DecentHats', () => { createRoleHatPromise = executeSafeTransaction({ safe: gnosisSafe, - to: decentHatsAddress, - transactionData: DecentHats__factory.createInterface().encodeFunctionData( + to: decentHatsCreationModuleAddress, + transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( 'createTermedRoleHat', [ { @@ -916,23 +921,26 @@ describe('DecentHats', () => { it('Emits an ExecutionSuccess event', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); await expect(await createRoleHatPromise).to.emit(gnosisSafe, 'ExecutionSuccess'); }); it('Emits an ExecutionFromModuleSuccess event', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); await expect(await createRoleHatPromise) .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsAddress); + .withArgs(decentHatsCreationModuleAddress); }); it('Transfers the top hat back to the Safe', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - const isModuleWearerOfTopHat = await mockHats.isWearerOfHat(decentHatsAddress, topHatId); + const isModuleWearerOfTopHat = await mockHats.isWearerOfHat( + decentHatsCreationModuleAddress, + topHatId, + ); expect(isModuleWearerOfTopHat).to.equal(true); await createRoleHatPromise; @@ -943,7 +951,7 @@ describe('DecentHats', () => { it('Actually creates the new hat', async () => { // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsAddress); + await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); const hatsCountBeforeCreate = await mockHats.hatId(); expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat From 0c51fb075e1b9495c63264f3dcb585fe7702332d Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 30 Oct 2024 10:08:04 -0400 Subject: [PATCH 115/119] Remove code for modifying an existing hats tree (adding a hat) --- contracts/DecentHatsCreationModule.sol | 77 ------- test/DecentHatsCreationModule.test.ts | 291 ------------------------- 2 files changed, 368 deletions(-) diff --git a/contracts/DecentHatsCreationModule.sol b/contracts/DecentHatsCreationModule.sol index ad3fa4a3..d269a008 100644 --- a/contracts/DecentHatsCreationModule.sol +++ b/contracts/DecentHatsCreationModule.sol @@ -159,83 +159,6 @@ contract DecentHatsCreationModule { params.hatsProtocol.transferHat(topHatId, address(this), msg.sender); } - /** - * @notice Creates a new role hat and any streams on it. - * - * @notice This contract should be enabled a module on the Safe for which the role is to be created, and disabled after. - * - * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first - * transfer its top hat to this contract. This function transfers the top hat back to the Safe after - * creating the role hat. - * - * @dev The function simply calls `_createHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. - * - * @dev Stream funds on Roles are targeted at the hat's smart account. In order to withdraw funds from the stream, the - * hat's smart account must be the one call to `withdraw-` on the Sablier contract, setting the recipient arg to its wearer. - * - * @dev Role hat creation, minting, smart account creation and stream creation are handled here in order - * to avoid a race condition where not more than one active proposal to create a new role can exist at a time. - * See: https://github.com/decentdao/decent-interface/issues/2402 - */ - function createRoleHat(CreateRoleHatParams calldata params) external { - _createHatAndAccountAndMintAndStreams( - params.hatsProtocol, - params.registry, - params.topHatAccount, - params.hatsAccountImplementation, - params.adminHatId, - params.hat - ); - - params.hatsProtocol.transferHat( - params.topHatId, - address(this), - msg.sender - ); - } - - /** - * @notice Creates a new termed role hat and any streams on it. - * - * @notice This contract should be enabled a module on the Safe for which the role is to be created, and disable after. - * - * @dev In order for the module to be able to create hats on behalf of the Safe, the Safe must first - * transfer its top hat to this contract. This function transfers the top hat back to the Safe after - * creating the role hat. - * - * @dev The function simply calls `_createTermedHatAndAccountAndMintAndStreams` and then transfers the top hat back to the Safe. - * - * @dev Stream funds on Termed Roles are targeted directly at the nominated wearer. - * The wearer should directly call `withdraw-` on the Sablier contract. - * - * @dev Termed Role hat creation, minting, and stream creation are handled here in order - * to avoid a race condition where not more than one active proposal to create a new termed role can exist at a time. - * See: https://github.com/decentdao/decent-interface/issues/2402 - */ - function createTermedRoleHat( - CreateTermedRoleHatParams calldata params - ) external { - _createTermedHatAndAccountAndMintAndStreams( - params.hatsProtocol, - params.topHatAccount, - params.hatsModuleFactory.createHatsModule( // Create election module and set as eligibility - params.hatsElectionEligibilityImplementation, - params.hatsProtocol.getNextId(params.adminHatId), - abi.encode(params.topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] - abi.encode(params.hat.termedParam.termEndDateTs), - uint256(SALT) - ), - params.adminHatId, - params.hat - ); - - params.hatsProtocol.transferHat( - params.topHatId, - address(this), - msg.sender - ); - } - /* ///////////////////////////////////////////////////////////////////////////// INTERAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ diff --git a/test/DecentHatsCreationModule.test.ts b/test/DecentHatsCreationModule.test.ts index 2f6d5956..be6ebd33 100644 --- a/test/DecentHatsCreationModule.test.ts +++ b/test/DecentHatsCreationModule.test.ts @@ -671,296 +671,5 @@ describe('DecentHats', () => { expect(stream2.endTime).to.equal(currentBlockTimestamp + 1296000); }); }); - - describe('Creating a new hat on existing Tree', () => { - let createRoleHatPromise: Promise; - const topHatId = 0; - - beforeEach(async () => { - try { - await executeSafeTransaction({ - safe: gnosisSafe, - to: decentHatsCreationModuleAddress, - transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( - 'createAndDeclareTree', - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: '', - topHatImageURI: '', - adminHat: { - maxSupply: 1, - details: '', - imageURI: '', - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, - }, - hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, - moduleProxyFactory: await moduleProxyFactory.getAddress(), - decentAutonomousAdminMasterCopy: - await decentAutonomousAdminMasterCopy.getAddress(), - hats: [], - }, - ], - ), - signers: [dao], - }); - } catch (e) { - console.error('Error creating tree', e); - } - const currentBlockTimestamp = (await hre.ethers.provider.getBlock('latest'))!.timestamp; - - createRoleHatPromise = executeSafeTransaction({ - safe: gnosisSafe, - to: decentHatsCreationModuleAddress, - transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( - 'createRoleHat', - [ - { - hatsProtocol: mockHatsAddress, - registry: await erc6551Registry.getAddress(), - topHatAccount: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', - hatsAccountImplementation: mockHatsAccountImplementationAddress, - adminHatId: 1, - topHatId, - hat: { - maxSupply: 1, - details: '', - imageURI: '', - isMutable: true, - wearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', - - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, - sablierParams: [ - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther('100'), - asset: mockERC20Address, - cancelable: true, - transferable: false, - timestamps: { - start: currentBlockTimestamp, - cliff: currentBlockTimestamp + 86400, // 1 day cliff - end: currentBlockTimestamp + 2592000, // 30 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, - }, - ], - }, - }, - ], - ), - signers: [dao], - }); - }); - - it('Reverts if the top hat is not transferred to the DecentHats module first', async () => { - await expect(createRoleHatPromise).to.be.reverted; - }); - - it('Emits an ExecutionSuccess event', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - await expect(await createRoleHatPromise).to.emit(gnosisSafe, 'ExecutionSuccess'); - }); - - it('Emits an ExecutionFromModuleSuccess event', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - await expect(await createRoleHatPromise) - .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsCreationModuleAddress); - }); - - it('Transfers the top hat back to the Safe', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - - const isModuleWearerOfTopHat = await mockHats.isWearerOfHat( - decentHatsCreationModuleAddress, - topHatId, - ); - expect(isModuleWearerOfTopHat).to.equal(true); - - await createRoleHatPromise; - - const isSafeWearerOfTopHat = await mockHats.isWearerOfHat(gnosisSafeAddress, topHatId); - expect(isSafeWearerOfTopHat).to.equal(true); - }); - - it('Actually creates the new hat', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - - const hatsCountBeforeCreate = await mockHats.hatId(); - expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat - - await createRoleHatPromise; - - const newHatId = await mockHats.hatId(); - expect(newHatId).to.equal(3); // + newly created hat - }); - }); - - describe('Creating a new Termed hat on existing tree', () => { - let createRoleHatPromise: Promise; - const topHatId = 0; - - beforeEach(async () => { - try { - await executeSafeTransaction({ - safe: gnosisSafe, - to: decentHatsCreationModuleAddress, - transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( - 'createAndDeclareTree', - [ - { - hatsProtocol: mockHatsAddress, - hatsAccountImplementation: mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: '', - topHatImageURI: '', - adminHat: { - maxSupply: 1, - details: '', - imageURI: '', - isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, - }, - hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, - moduleProxyFactory: await moduleProxyFactory.getAddress(), - decentAutonomousAdminMasterCopy: - await decentAutonomousAdminMasterCopy.getAddress(), - hats: [], - }, - ], - ), - signers: [dao], - }); - } catch (e) { - console.error('Error creating tree', e); - } - const currentBlockTimestamp = (await hre.ethers.provider.getBlock('latest'))!.timestamp; - - createRoleHatPromise = executeSafeTransaction({ - safe: gnosisSafe, - to: decentHatsCreationModuleAddress, - transactionData: DecentHatsCreationModule__factory.createInterface().encodeFunctionData( - 'createTermedRoleHat', - [ - { - hatsProtocol: mockHatsAddress, - registry: await erc6551Registry.getAddress(), - topHatAccount: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', - hatsAccountImplementation: mockHatsAccountImplementationAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, - hatsModuleFactory: mockHatsModuleFactoryAddress, - adminHatId: 1, - topHatId, - hat: { - maxSupply: 1, - details: '', - imageURI: '', - isMutable: true, - wearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', - termedParam: { - termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearer: '0xdce7ca0555101f97451926944f5ae3b7adb2f5ae', - }, - sablierParams: [ - { - sablier: mockSablierAddress, - sender: gnosisSafeAddress, - totalAmount: ethers.parseEther('100'), - asset: mockERC20Address, - cancelable: true, - transferable: false, - timestamps: { - start: currentBlockTimestamp, - cliff: currentBlockTimestamp + 86400, // 1 day cliff - end: currentBlockTimestamp + 2592000, // 30 days from now - }, - broker: { account: ethers.ZeroAddress, fee: 0 }, - }, - ], - }, - }, - ], - ), - signers: [dao], - }); - }); - - it('Reverts if the top hat is not transferred to the DecentHats module first', async () => { - await expect(createRoleHatPromise).to.be.reverted; - }); - - it('Emits an ExecutionSuccess event', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - await expect(await createRoleHatPromise).to.emit(gnosisSafe, 'ExecutionSuccess'); - }); - - it('Emits an ExecutionFromModuleSuccess event', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - await expect(await createRoleHatPromise) - .to.emit(gnosisSafe, 'ExecutionFromModuleSuccess') - .withArgs(decentHatsCreationModuleAddress); - }); - - it('Transfers the top hat back to the Safe', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - - const isModuleWearerOfTopHat = await mockHats.isWearerOfHat( - decentHatsCreationModuleAddress, - topHatId, - ); - expect(isModuleWearerOfTopHat).to.equal(true); - - await createRoleHatPromise; - - const isSafeWearerOfTopHat = await mockHats.isWearerOfHat(gnosisSafeAddress, topHatId); - expect(isSafeWearerOfTopHat).to.equal(true); - }); - - it('Actually creates the new hat', async () => { - // First transfer the top hat to the Safe - await mockHats.transferHat(topHatId, gnosisSafeAddress, decentHatsCreationModuleAddress); - - const hatsCountBeforeCreate = await mockHats.hatId(); - expect(hatsCountBeforeCreate).to.equal(2); // Top hat + admin hat - - await createRoleHatPromise; - - const newHatId = await mockHats.hatId(); - expect(newHatId).to.equal(3); // + newly created hat - }); - }); }); }); From 2e2fa29304ad600b99666d02a95f7c48bd07047e Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 30 Oct 2024 12:32:06 -0400 Subject: [PATCH 116/119] Remove "modifiying tree" (adding hats) external functions from contract, and do some light refactoring --- contracts/DecentHatsCreationModule.sol | 442 ++++++++++++++----------- test/DecentHatsCreationModule.test.ts | 140 +++----- 2 files changed, 289 insertions(+), 293 deletions(-) diff --git a/contracts/DecentHatsCreationModule.sol b/contracts/DecentHatsCreationModule.sol index d269a008..dfb19611 100644 --- a/contracts/DecentHatsCreationModule.sol +++ b/contracts/DecentHatsCreationModule.sol @@ -17,6 +17,22 @@ contract DecentHatsCreationModule { bytes32 public constant SALT = 0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072; + struct TopHatInfo { + uint256 topHatId; + address topHatAccount; + } + + struct TopHat { + string details; + string imageURI; + } + + struct AdminHat { + string details; + string imageURI; + bool isMutable; + } + struct SablierStreamParams { ISablierV2LockupLinear sablier; address sender; @@ -28,19 +44,14 @@ contract DecentHatsCreationModule { bool transferable; } - struct TermedParam { - uint128 termEndDateTs; - address nominatedWearer; - } - struct Hat { address wearer; string details; string imageURI; - SablierStreamParams[] sablierParams; - TermedParam termedParam; uint32 maxSupply; bool isMutable; + uint128 termEndDateTs; + SablierStreamParams[] sablierStreamsParams; } struct CreateTreeParams { @@ -52,32 +63,9 @@ contract DecentHatsCreationModule { address hatsAccountImplementation; address keyValuePairs; address hatsElectionEligibilityImplementation; - Hat adminHat; + TopHat topHat; + AdminHat adminHat; Hat[] hats; - string topHatDetails; - string topHatImageURI; - } - - struct CreateRoleHatParams { - IHats hatsProtocol; - IERC6551Registry registry; - address topHatAccount; - address hatsAccountImplementation; - uint256 adminHatId; - uint256 topHatId; - Hat hat; - } - - struct CreateTermedRoleHatParams { - IHats hatsProtocol; - IERC6551Registry registry; - IHatsModuleFactory hatsModuleFactory; - address topHatAccount; - address hatsAccountImplementation; - address hatsElectionEligibilityImplementation; - uint256 adminHatId; - uint256 topHatId; - Hat hat; } /* ///////////////////////////////////////////////////////////////////////////// @@ -102,78 +90,88 @@ contract DecentHatsCreationModule { * We also make use of `KeyValuePairs` to associate the topHatId with the Safe. */ function createAndDeclareTree(CreateTreeParams calldata params) external { - (uint256 topHatId, address topHatAccount) = _createTopHatAndAccount( - params.hatsProtocol, - params.topHatDetails, - params.topHatImageURI, - params.registry, - params.hatsAccountImplementation + IHats hatsProtocol = params.hatsProtocol; + address hatsAccountImplementation = params.hatsAccountImplementation; + IERC6551Registry registry = params.registry; + + // Create Top Hat + TopHatInfo memory topHatInfo = processTopHat( + hatsProtocol, + registry, + hatsAccountImplementation, + params.keyValuePairs, + params.topHat ); - _declareSafeHatTree(params.keyValuePairs, topHatId); - - (uint256 adminHatId, ) = _createAdminHatAndAccount( - params.hatsProtocol, - params.registry, + // Create Admin Hat + uint256 adminHatId = processAdminHat( + hatsProtocol, + registry, + hatsAccountImplementation, + topHatInfo, params.moduleProxyFactory, params.decentAutonomousAdminMasterCopy, - params.hatsAccountImplementation, - topHatAccount, - topHatId, params.adminHat ); for (uint256 i = 0; i < params.hats.length; ) { - // {assuption} if 0 nominatedWearers, then it is not a termed role - if (params.hats[i].termedParam.nominatedWearer != address(0)) { - // Create election module and set as eligiblity - _createTermedHatAndAccountAndMintAndStreams( - params.hatsProtocol, - topHatAccount, - params.hatsModuleFactory.createHatsModule( // Create election module and set as eligibility - params.hatsElectionEligibilityImplementation, - params.hatsProtocol.getNextId(adminHatId), - abi.encode(topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] - abi.encode(params.hats[i].termedParam.termEndDateTs), - uint256(SALT) - ), - adminHatId, - params.hats[i] - ); - } else { - _createHatAndAccountAndMintAndStreams( - params.hatsProtocol, - params.registry, - topHatAccount, - params.hatsAccountImplementation, - adminHatId, - params.hats[i] - ); - } + processHat( + hatsProtocol, + registry, + hatsAccountImplementation, + topHatInfo, + params.hatsModuleFactory, + params.hatsElectionEligibilityImplementation, + adminHatId, + params.hats[i] + ); unchecked { ++i; } } - params.hatsProtocol.transferHat(topHatId, address(this), msg.sender); + hatsProtocol.transferHat( + topHatInfo.topHatId, + address(this), + msg.sender + ); } /* ///////////////////////////////////////////////////////////////////////////// - INTERAL FUNCTIONS + INTERNAL FUNCTIONS ///////////////////////////////////////////////////////////////////////////// */ - function _declareSafeHatTree( - address _keyValuePairs, - uint256 topHatId - ) internal { + function processTopHat( + IHats hatsProtocol, + IERC6551Registry registry, + address hatsAccountImplementation, + address keyValuePairs, + TopHat memory topHat + ) internal returns (TopHatInfo memory topHatInfo) { + // Mint top hat + topHatInfo.topHatId = hatsProtocol.mintTopHat( + address(this), + topHat.details, + topHat.imageURI + ); + + // Create top hat account + topHatInfo.topHatAccount = registry.createAccount( + hatsAccountImplementation, + SALT, + block.chainid, + address(hatsProtocol), + topHatInfo.topHatId + ); + + // Declare Top Hat ID to Safe via KeyValuePairs string[] memory keys = new string[](1); string[] memory values = new string[](1); keys[0] = "topHatId"; - values[0] = Strings.toString(topHatId); - + values[0] = Strings.toString(topHatInfo.topHatId); IAvatar(msg.sender).execTransactionFromModule( - _keyValuePairs, + keyValuePairs, 0, abi.encodeWithSignature( "updateValues(string[],string[])", @@ -184,71 +182,82 @@ contract DecentHatsCreationModule { ); } - function _createTopHatAndAccount( - IHats _hatsProtocol, - string memory _topHatDetails, - string memory _topHatImageURI, - IERC6551Registry _registry, - address _hatsAccountImplementation - ) internal returns (uint256 topHatId, address topHatAccount) { - topHatId = _hatsProtocol.mintTopHat( - address(this), - _topHatDetails, - _topHatImageURI - ); - - topHatAccount = _registry.createAccount( - _hatsAccountImplementation, - SALT, - block.chainid, - address(_hatsProtocol), - topHatId - ); - } - - function _createHatAndAccountAndMintAndStreams( + function processAdminHat( IHats hatsProtocol, IERC6551Registry registry, - address topHatAccount, address hatsAccountImplementation, - uint256 adminHatId, - Hat calldata hat - ) internal returns (uint256 hatId, address accountAddress) { - hatId = hatsProtocol.createHat( - adminHatId, - hat.details, - hat.maxSupply, - topHatAccount, - topHatAccount, - hat.isMutable, - hat.imageURI + TopHatInfo memory topHatInfo, + ModuleProxyFactory moduleProxyFactory, + address decentAutonomousAdminMasterCopy, + AdminHat memory adminHat + ) internal returns (uint256 adminHatId) { + // Create Admin Hat + adminHatId = hatsProtocol.createHat( + topHatInfo.topHatId, + adminHat.details, + 1, // only one Admin Hat + topHatInfo.topHatAccount, + topHatInfo.topHatAccount, + adminHat.isMutable, + adminHat.imageURI ); - accountAddress = registry.createAccount( + // Create Admin Hat's ERC6551 Account + registry.createAccount( hatsAccountImplementation, SALT, block.chainid, address(hatsProtocol), - hatId + adminHatId ); - hatsProtocol.mintHat(hatId, hat.wearer); + // Deploy Decent Autonomous Admin Module, which will wear the Admin Hat + address autonomousAdminModule = moduleProxyFactory.deployModule( + decentAutonomousAdminMasterCopy, + abi.encodeWithSignature("setUp(bytes)", bytes("")), + uint256( + keccak256( + abi.encodePacked( + // for the salt, we'll concatenate our static salt with the id of the Admin Hat + SALT, + adminHatId + ) + ) + ) + ); - for (uint256 i = 0; i < hat.sablierParams.length; ) { - _createSablierStream(hat.sablierParams[i], accountAddress); - unchecked { - ++i; - } + // Mint Hat to the Decent Autonomous Admin Module + hatsProtocol.mintHat(adminHatId, autonomousAdminModule); + } + + function createEligibilityModule( + IHats hatsProtocol, + IHatsModuleFactory hatsModuleFactory, + address hatsElectionEligibilityImplementation, + TopHatInfo memory topHatInfo, + uint256 adminHatId, + uint128 termEndDateTs + ) internal returns (address) { + if (termEndDateTs != 0) { + return + hatsModuleFactory.createHatsModule( + hatsElectionEligibilityImplementation, + hatsProtocol.getNextId(adminHatId), + abi.encode(topHatInfo.topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] + abi.encode(termEndDateTs), + uint256(SALT) + ); } + return topHatInfo.topHatAccount; } - function _createTermedHatAndAccountAndMintAndStreams( + function createAndMintHat( IHats hatsProtocol, - address topHatAccount, - address eligibilityAddress, uint256 adminHatId, - Hat calldata hat - ) internal { + Hat memory hat, + address eligibilityAddress, + address topHatAccount + ) internal returns (uint256) { uint256 hatId = hatsProtocol.createHat( adminHatId, hat.details, @@ -259,101 +268,134 @@ contract DecentHatsCreationModule { hat.imageURI ); - address[] memory nominatedWearers = new address[](1); - nominatedWearers[0] = hat.termedParam.nominatedWearer; - IHatsElectionEligibility(eligibilityAddress).elect( - hat.termedParam.termEndDateTs, - nominatedWearers - ); - - hatsProtocol.mintHat(hatId, hat.termedParam.nominatedWearer); - - for (uint256 i = 0; i < hat.sablierParams.length; ) { - _createSablierStream( - hat.sablierParams[i], - hat.termedParam.nominatedWearer + // If the hat is termed, nominate the wearer as the eligible member + if (hat.termEndDateTs != 0) { + address[] memory nominatedWearers = new address[](1); + nominatedWearers[0] = hat.wearer; + IHatsElectionEligibility(eligibilityAddress).elect( + hat.termEndDateTs, + nominatedWearers ); - unchecked { - ++i; - } } + + hatsProtocol.mintHat(hatId, hat.wearer); + return hatId; } - function _createAdminHatAndAccount( - IHats hatsProtocol, + function setupStreamRecipient( IERC6551Registry registry, - ModuleProxyFactory moduleProxyFactory, - address decentAutonomousAdminMasterCopy, address hatsAccountImplementation, - address topHatAccount, - uint256 topHatId, - Hat calldata hat - ) internal returns (uint256 adminHatId, address accountAddress) { - adminHatId = hatsProtocol.createHat( - topHatId, - hat.details, - hat.maxSupply, - topHatAccount, - topHatAccount, - hat.isMutable, - hat.imageURI - ); - - accountAddress = registry.createAccount( - hatsAccountImplementation, - SALT, - block.chainid, - address(hatsProtocol), - adminHatId - ); + address hatsProtocol, + uint128 termEndDateTs, + address wearer, + uint256 hatId + ) internal returns (address) { + // If the hat is termed, the wearer is the stream recipient + if (termEndDateTs != 0) { + return wearer; + } - hatsProtocol.mintHat( - adminHatId, - moduleProxyFactory.deployModule( - decentAutonomousAdminMasterCopy, - abi.encodeWithSignature("setUp(bytes)", bytes("")), - uint256(keccak256(abi.encodePacked(SALT, adminHatId))) - ) - ); + // Otherwise, the Hat's smart account is the stream recipient + return + registry.createAccount( + hatsAccountImplementation, + SALT, + block.chainid, + hatsProtocol, + hatId + ); } - function _createSablierStream( - SablierStreamParams memory sablierParams, - address recipient + function processSablierStream( + SablierStreamParams memory streamParams, + address streamRecipient ) internal { - // Approve tokens for Sablier + // Approve tokens for Sablier via a proxy call through the Safe IAvatar(msg.sender).execTransactionFromModule( - sablierParams.asset, + streamParams.asset, 0, abi.encodeWithSignature( "approve(address,uint256)", - sablierParams.sablier, - sablierParams.totalAmount + streamParams.sablier, + streamParams.totalAmount ), Enum.Operation.Call ); - LockupLinear.CreateWithTimestamps memory params = LockupLinear - .CreateWithTimestamps({ - sender: sablierParams.sender, - recipient: recipient, - totalAmount: sablierParams.totalAmount, - asset: IERC20(sablierParams.asset), - cancelable: sablierParams.cancelable, - transferable: sablierParams.transferable, - timestamps: sablierParams.timestamps, - broker: sablierParams.broker - }); - - // Proxy the Sablier call through IAvatar + // Proxy the Sablier call through the Safe IAvatar(msg.sender).execTransactionFromModule( - address(sablierParams.sablier), + address(streamParams.sablier), 0, abi.encodeWithSignature( "createWithTimestamps((address,address,uint128,address,bool,bool,(uint40,uint40,uint40),(address,uint256)))", - params + LockupLinear.CreateWithTimestamps({ + sender: streamParams.sender, + recipient: streamRecipient, + totalAmount: streamParams.totalAmount, + asset: IERC20(streamParams.asset), + cancelable: streamParams.cancelable, + transferable: streamParams.transferable, + timestamps: streamParams.timestamps, + broker: streamParams.broker + }) ), Enum.Operation.Call ); } + + function createSablierStreams( + SablierStreamParams[] memory streamParams, + address streamRecipient + ) internal { + for (uint256 i = 0; i < streamParams.length; ) { + processSablierStream(streamParams[i], streamRecipient); + + unchecked { + ++i; + } + } + } + + function processHat( + IHats hatsProtocol, + IERC6551Registry registry, + address hatsAccountImplementation, + TopHatInfo memory topHatInfo, + IHatsModuleFactory hatsModuleFactory, + address hatsElectionEligibilityImplementation, + uint256 adminHatId, + Hat memory hat + ) internal { + // Create eligibility module if needed + address eligibilityAddress = createEligibilityModule( + hatsProtocol, + hatsModuleFactory, + hatsElectionEligibilityImplementation, + topHatInfo, + adminHatId, + hat.termEndDateTs + ); + + // Create and Mint the Role Hat + uint256 hatId = createAndMintHat( + hatsProtocol, + adminHatId, + hat, + eligibilityAddress, + topHatInfo.topHatAccount + ); + + // Get the stream recipient (based on termed or not) + address streamRecipient = setupStreamRecipient( + registry, + hatsAccountImplementation, + address(hatsProtocol), + hat.termEndDateTs, + hat.wearer, + hatId + ); + + // Create streams + createSablierStreams(hat.sablierStreamsParams, streamRecipient); + } } diff --git a/test/DecentHatsCreationModule.test.ts b/test/DecentHatsCreationModule.test.ts index be6ebd33..86586281 100644 --- a/test/DecentHatsCreationModule.test.ts +++ b/test/DecentHatsCreationModule.test.ts @@ -172,54 +172,44 @@ describe('DecentHats', () => { [ { hatsProtocol: mockHatsAddress, - hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), - keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: '', - topHatImageURI: '', - decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + hatsModuleFactory: mockHatsModuleFactoryAddress, moduleProxyFactory: await moduleProxyFactory.getAddress(), + decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), + hatsAccountImplementation: mockHatsAccountImplementationAddress, + keyValuePairs: await keyValuePairs.getAddress(), + hatsElectionEligibilityImplementation: + mockHatsElectionEligibilityImplementationAddress, + + topHat: { + details: '', + imageURI: '', + }, adminHat: { - maxSupply: 1, details: '', imageURI: '', isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, }, hats: [ { - maxSupply: 1, + wearer: ethers.ZeroAddress, details: '', imageURI: '', + maxSupply: 1, isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, + termEndDateTs: 0, + sablierStreamsParams: [], }, { - maxSupply: 1, + wearer: ethers.ZeroAddress, details: '', imageURI: '', + maxSupply: 1, isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, + termEndDateTs: 0, + sablierStreamsParams: [], }, ], - hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, }, ], ), @@ -258,23 +248,17 @@ describe('DecentHats', () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: '', - topHatImageURI: '', + topHat: { + details: '', + imageURI: '', + }, decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { - maxSupply: 1, details: '', imageURI: '', isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, }, hats: [], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -335,21 +319,16 @@ describe('DecentHats', () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: '', - topHatImageURI: '', + topHat: { + details: '', + imageURI: '', + }, decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { - maxSupply: 1, details: '', imageURI: '', isMutable: true, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, }, hats: [ { @@ -357,24 +336,18 @@ describe('DecentHats', () => { details: '', imageURI: '', isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearer: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', - }, + wearer: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', + sablierStreamsParams: [], + termEndDateTs: BigInt(Date.now() + 100000), }, { maxSupply: 1, details: '', imageURI: '', isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: BigInt(Date.now() + 100000), - nominatedWearer: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', - }, + wearer: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', + sablierStreamsParams: [], + termEndDateTs: BigInt(Date.now() + 100000), }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -422,21 +395,16 @@ describe('DecentHats', () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: '', - topHatImageURI: '', + topHat: { + details: '', + imageURI: '', + }, decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { - maxSupply: 1, details: '', imageURI: '', isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, }, hats: [ { @@ -445,7 +413,7 @@ describe('DecentHats', () => { imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, - sablierParams: [ + sablierStreamsParams: [ { sablier: mockSablierAddress, sender: gnosisSafeAddress, @@ -461,10 +429,7 @@ describe('DecentHats', () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, + termEndDateTs: 0, }, { maxSupply: 1, @@ -472,11 +437,8 @@ describe('DecentHats', () => { imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, + sablierStreamsParams: [], + termEndDateTs: 0, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, @@ -555,21 +517,16 @@ describe('DecentHats', () => { hatsAccountImplementation: mockHatsAccountImplementationAddress, registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), - topHatDetails: '', - topHatImageURI: '', + topHat: { + details: '', + imageURI: '', + }, decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), moduleProxyFactory: await moduleProxyFactory.getAddress(), adminHat: { - maxSupply: 1, details: '', imageURI: '', isMutable: false, - wearer: ethers.ZeroAddress, - sablierParams: [], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, }, hats: [ { @@ -578,7 +535,7 @@ describe('DecentHats', () => { imageURI: '', isMutable: false, wearer: ethers.ZeroAddress, - sablierParams: [ + sablierStreamsParams: [ { sablier: mockSablierAddress, sender: gnosisSafeAddress, @@ -608,10 +565,7 @@ describe('DecentHats', () => { broker: { account: ethers.ZeroAddress, fee: 0 }, }, ], - termedParam: { - termEndDateTs: 0, - nominatedWearer: ethers.ZeroAddress, - }, + termEndDateTs: 0, }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, From ca2be1f9cc6e09db73894f24403df96dab1d5d99 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 30 Oct 2024 12:34:30 -0400 Subject: [PATCH 117/119] Move functions around, add some comments --- contracts/DecentHatsCreationModule.sol | 91 ++++++++++++++------------ 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/contracts/DecentHatsCreationModule.sol b/contracts/DecentHatsCreationModule.sol index dfb19611..e818c6f3 100644 --- a/contracts/DecentHatsCreationModule.sol +++ b/contracts/DecentHatsCreationModule.sol @@ -230,6 +230,50 @@ contract DecentHatsCreationModule { hatsProtocol.mintHat(adminHatId, autonomousAdminModule); } + function processHat( + IHats hatsProtocol, + IERC6551Registry registry, + address hatsAccountImplementation, + TopHatInfo memory topHatInfo, + IHatsModuleFactory hatsModuleFactory, + address hatsElectionEligibilityImplementation, + uint256 adminHatId, + Hat memory hat + ) internal { + // Create eligibility module if needed + address eligibilityAddress = createEligibilityModule( + hatsProtocol, + hatsModuleFactory, + hatsElectionEligibilityImplementation, + topHatInfo, + adminHatId, + hat.termEndDateTs + ); + + // Create and Mint the Role Hat + uint256 hatId = createAndMintHat( + hatsProtocol, + adminHatId, + hat, + eligibilityAddress, + topHatInfo.topHatAccount + ); + + // Get the stream recipient (based on termed or not) + address streamRecipient = setupStreamRecipient( + registry, + hatsAccountImplementation, + address(hatsProtocol), + hat.termEndDateTs, + hat.wearer, + hatId + ); + + // Create streams + createSablierStreams(hat.sablierStreamsParams, streamRecipient); + } + + // Exists to avoid stack too deep errors function createEligibilityModule( IHats hatsProtocol, IHatsModuleFactory hatsModuleFactory, @@ -251,6 +295,7 @@ contract DecentHatsCreationModule { return topHatInfo.topHatAccount; } + // Exists to avoid stack too deep errors function createAndMintHat( IHats hatsProtocol, uint256 adminHatId, @@ -282,6 +327,7 @@ contract DecentHatsCreationModule { return hatId; } + // Exists to avoid stack too deep errors function setupStreamRecipient( IERC6551Registry registry, address hatsAccountImplementation, @@ -306,6 +352,7 @@ contract DecentHatsCreationModule { ); } + // Exists to avoid stack too deep errors function processSablierStream( SablierStreamParams memory streamParams, address streamRecipient @@ -343,6 +390,7 @@ contract DecentHatsCreationModule { ); } + // Exists to avoid stack too deep errors function createSablierStreams( SablierStreamParams[] memory streamParams, address streamRecipient @@ -355,47 +403,4 @@ contract DecentHatsCreationModule { } } } - - function processHat( - IHats hatsProtocol, - IERC6551Registry registry, - address hatsAccountImplementation, - TopHatInfo memory topHatInfo, - IHatsModuleFactory hatsModuleFactory, - address hatsElectionEligibilityImplementation, - uint256 adminHatId, - Hat memory hat - ) internal { - // Create eligibility module if needed - address eligibilityAddress = createEligibilityModule( - hatsProtocol, - hatsModuleFactory, - hatsElectionEligibilityImplementation, - topHatInfo, - adminHatId, - hat.termEndDateTs - ); - - // Create and Mint the Role Hat - uint256 hatId = createAndMintHat( - hatsProtocol, - adminHatId, - hat, - eligibilityAddress, - topHatInfo.topHatAccount - ); - - // Get the stream recipient (based on termed or not) - address streamRecipient = setupStreamRecipient( - registry, - hatsAccountImplementation, - address(hatsProtocol), - hat.termEndDateTs, - hat.wearer, - hatId - ); - - // Create streams - createSablierStreams(hat.sablierStreamsParams, streamRecipient); - } } From 971ba590802220ae5774c7dfcfadce46a7b5d3fe Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 30 Oct 2024 12:38:40 -0400 Subject: [PATCH 118/119] Remove unneeded struct via refactoring --- contracts/DecentHatsCreationModule.sol | 76 +++++++++++++------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/contracts/DecentHatsCreationModule.sol b/contracts/DecentHatsCreationModule.sol index e818c6f3..3e036838 100644 --- a/contracts/DecentHatsCreationModule.sol +++ b/contracts/DecentHatsCreationModule.sol @@ -17,17 +17,12 @@ contract DecentHatsCreationModule { bytes32 public constant SALT = 0x5d0e6ce4fd951366cc55da93f6e79d8b81483109d79676a04bcc2bed6a4b5072; - struct TopHatInfo { - uint256 topHatId; - address topHatAccount; - } - - struct TopHat { + struct TopHatParams { string details; string imageURI; } - struct AdminHat { + struct AdminHatParams { string details; string imageURI; bool isMutable; @@ -44,7 +39,7 @@ contract DecentHatsCreationModule { bool transferable; } - struct Hat { + struct HatParams { address wearer; string details; string imageURI; @@ -63,9 +58,9 @@ contract DecentHatsCreationModule { address hatsAccountImplementation; address keyValuePairs; address hatsElectionEligibilityImplementation; - TopHat topHat; - AdminHat adminHat; - Hat[] hats; + TopHatParams topHat; + AdminHatParams adminHat; + HatParams[] hats; } /* ///////////////////////////////////////////////////////////////////////////// @@ -95,7 +90,7 @@ contract DecentHatsCreationModule { IERC6551Registry registry = params.registry; // Create Top Hat - TopHatInfo memory topHatInfo = processTopHat( + (uint256 topHatId, address topHatAccount) = processTopHat( hatsProtocol, registry, hatsAccountImplementation, @@ -108,22 +103,25 @@ contract DecentHatsCreationModule { hatsProtocol, registry, hatsAccountImplementation, - topHatInfo, + topHatId, + topHatAccount, params.moduleProxyFactory, params.decentAutonomousAdminMasterCopy, params.adminHat ); for (uint256 i = 0; i < params.hats.length; ) { + HatParams memory hat = params.hats[i]; processHat( hatsProtocol, registry, hatsAccountImplementation, - topHatInfo, + topHatId, + topHatAccount, params.hatsModuleFactory, params.hatsElectionEligibilityImplementation, adminHatId, - params.hats[i] + hat ); unchecked { @@ -131,11 +129,7 @@ contract DecentHatsCreationModule { } } - hatsProtocol.transferHat( - topHatInfo.topHatId, - address(this), - msg.sender - ); + hatsProtocol.transferHat(topHatId, address(this), msg.sender); } /* ///////////////////////////////////////////////////////////////////////////// @@ -147,29 +141,29 @@ contract DecentHatsCreationModule { IERC6551Registry registry, address hatsAccountImplementation, address keyValuePairs, - TopHat memory topHat - ) internal returns (TopHatInfo memory topHatInfo) { + TopHatParams memory topHat + ) internal returns (uint256 topHatId, address topHatAccount) { // Mint top hat - topHatInfo.topHatId = hatsProtocol.mintTopHat( + topHatId = hatsProtocol.mintTopHat( address(this), topHat.details, topHat.imageURI ); // Create top hat account - topHatInfo.topHatAccount = registry.createAccount( + topHatAccount = registry.createAccount( hatsAccountImplementation, SALT, block.chainid, address(hatsProtocol), - topHatInfo.topHatId + topHatId ); // Declare Top Hat ID to Safe via KeyValuePairs string[] memory keys = new string[](1); string[] memory values = new string[](1); keys[0] = "topHatId"; - values[0] = Strings.toString(topHatInfo.topHatId); + values[0] = Strings.toString(topHatId); IAvatar(msg.sender).execTransactionFromModule( keyValuePairs, 0, @@ -186,18 +180,19 @@ contract DecentHatsCreationModule { IHats hatsProtocol, IERC6551Registry registry, address hatsAccountImplementation, - TopHatInfo memory topHatInfo, + uint256 topHatId, + address topHatAccount, ModuleProxyFactory moduleProxyFactory, address decentAutonomousAdminMasterCopy, - AdminHat memory adminHat + AdminHatParams memory adminHat ) internal returns (uint256 adminHatId) { // Create Admin Hat adminHatId = hatsProtocol.createHat( - topHatInfo.topHatId, + topHatId, adminHat.details, 1, // only one Admin Hat - topHatInfo.topHatAccount, - topHatInfo.topHatAccount, + topHatAccount, + topHatAccount, adminHat.isMutable, adminHat.imageURI ); @@ -234,18 +229,20 @@ contract DecentHatsCreationModule { IHats hatsProtocol, IERC6551Registry registry, address hatsAccountImplementation, - TopHatInfo memory topHatInfo, + uint256 topHatId, + address topHatAccount, IHatsModuleFactory hatsModuleFactory, address hatsElectionEligibilityImplementation, uint256 adminHatId, - Hat memory hat + HatParams memory hat ) internal { // Create eligibility module if needed address eligibilityAddress = createEligibilityModule( hatsProtocol, hatsModuleFactory, hatsElectionEligibilityImplementation, - topHatInfo, + topHatId, + topHatAccount, adminHatId, hat.termEndDateTs ); @@ -256,7 +253,7 @@ contract DecentHatsCreationModule { adminHatId, hat, eligibilityAddress, - topHatInfo.topHatAccount + topHatAccount ); // Get the stream recipient (based on termed or not) @@ -278,7 +275,8 @@ contract DecentHatsCreationModule { IHats hatsProtocol, IHatsModuleFactory hatsModuleFactory, address hatsElectionEligibilityImplementation, - TopHatInfo memory topHatInfo, + uint256 topHatId, + address topHatAccount, uint256 adminHatId, uint128 termEndDateTs ) internal returns (address) { @@ -287,19 +285,19 @@ contract DecentHatsCreationModule { hatsModuleFactory.createHatsModule( hatsElectionEligibilityImplementation, hatsProtocol.getNextId(adminHatId), - abi.encode(topHatInfo.topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] + abi.encode(topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] abi.encode(termEndDateTs), uint256(SALT) ); } - return topHatInfo.topHatAccount; + return topHatAccount; } // Exists to avoid stack too deep errors function createAndMintHat( IHats hatsProtocol, uint256 adminHatId, - Hat memory hat, + HatParams memory hat, address eligibilityAddress, address topHatAccount ) internal returns (uint256) { From 302c147d80fb50e3cc14c39da20d28ee1a901eef Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 30 Oct 2024 12:49:26 -0400 Subject: [PATCH 119/119] Rename "election eligibility" to "elections eligibility", and "registry" to "erc6551Registry" --- contracts/DecentAutonomousAdmin.sol | 8 ++-- contracts/DecentHatsCreationModule.sol | 22 ++++----- .../IHatsElectionsEligibility.sol} | 2 +- .../mocks/MockHatsElectionEligibility.sol | 4 +- contracts/mocks/MockHatsModuleFactory.sol | 4 +- test/DecentAutonomousAdmin.test.ts | 8 ++-- test/DecentHatsCreationModule.test.ts | 45 +++++++++---------- 7 files changed, 46 insertions(+), 47 deletions(-) rename contracts/interfaces/hats/full/{IHatsElectionEligibility.sol => modules/IHatsElectionsEligibility.sol} (98%) diff --git a/contracts/DecentAutonomousAdmin.sol b/contracts/DecentAutonomousAdmin.sol index 36bdf039..feb879b9 100644 --- a/contracts/DecentAutonomousAdmin.sol +++ b/contracts/DecentAutonomousAdmin.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.28; import {IHats} from "./interfaces/hats/full/IHats.sol"; -import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; +import {IHatsElectionsEligibility} from "./interfaces/hats/full/modules/IHatsElectionsEligibility.sol"; import {FactoryFriendly} from "@gnosis.pm/zodiac/contracts/factory/FactoryFriendly.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IDecentAutonomousAdmin} from "./interfaces/IDecentAutonomousAdmin.sol"; @@ -26,9 +26,9 @@ contract DecentAutonomousAdmin is false ) revert NotCurrentWearer(); - IHatsElectionEligibility hatsElectionModule = IHatsElectionEligibility( - args.hatsProtocol.getHatEligibilityModule(args.hatId) - ); + IHatsElectionsEligibility hatsElectionModule = IHatsElectionsEligibility( + args.hatsProtocol.getHatEligibilityModule(args.hatId) + ); hatsElectionModule.startNextTerm(); diff --git a/contracts/DecentHatsCreationModule.sol b/contracts/DecentHatsCreationModule.sol index 3e036838..34bf0600 100644 --- a/contracts/DecentHatsCreationModule.sol +++ b/contracts/DecentHatsCreationModule.sol @@ -9,7 +9,7 @@ import {IERC6551Registry} from "./interfaces/IERC6551Registry.sol"; import {IHats} from "./interfaces/hats/full/IHats.sol"; import {LockupLinear, Broker} from "./interfaces/sablier/full/types/DataTypes.sol"; import {IHatsModuleFactory} from "./interfaces/hats/full/IHatsModuleFactory.sol"; -import {IHatsElectionEligibility} from "./interfaces/hats/full/IHatsElectionEligibility.sol"; +import {IHatsElectionsEligibility} from "./interfaces/hats/full/modules/IHatsElectionsEligibility.sol"; import {ModuleProxyFactory} from "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol"; import {ISablierV2LockupLinear} from "./interfaces/sablier/ISablierV2LockupLinear.sol"; @@ -51,13 +51,13 @@ contract DecentHatsCreationModule { struct CreateTreeParams { IHats hatsProtocol; - IERC6551Registry registry; + IERC6551Registry erc6551Registry; IHatsModuleFactory hatsModuleFactory; ModuleProxyFactory moduleProxyFactory; + address keyValuePairs; address decentAutonomousAdminMasterCopy; address hatsAccountImplementation; - address keyValuePairs; - address hatsElectionEligibilityImplementation; + address hatsElectionsEligibilityImplementation; TopHatParams topHat; AdminHatParams adminHat; HatParams[] hats; @@ -87,7 +87,7 @@ contract DecentHatsCreationModule { function createAndDeclareTree(CreateTreeParams calldata params) external { IHats hatsProtocol = params.hatsProtocol; address hatsAccountImplementation = params.hatsAccountImplementation; - IERC6551Registry registry = params.registry; + IERC6551Registry registry = params.erc6551Registry; // Create Top Hat (uint256 topHatId, address topHatAccount) = processTopHat( @@ -119,7 +119,7 @@ contract DecentHatsCreationModule { topHatId, topHatAccount, params.hatsModuleFactory, - params.hatsElectionEligibilityImplementation, + params.hatsElectionsEligibilityImplementation, adminHatId, hat ); @@ -232,7 +232,7 @@ contract DecentHatsCreationModule { uint256 topHatId, address topHatAccount, IHatsModuleFactory hatsModuleFactory, - address hatsElectionEligibilityImplementation, + address hatsElectionsEligibilityImplementation, uint256 adminHatId, HatParams memory hat ) internal { @@ -240,7 +240,7 @@ contract DecentHatsCreationModule { address eligibilityAddress = createEligibilityModule( hatsProtocol, hatsModuleFactory, - hatsElectionEligibilityImplementation, + hatsElectionsEligibilityImplementation, topHatId, topHatAccount, adminHatId, @@ -274,7 +274,7 @@ contract DecentHatsCreationModule { function createEligibilityModule( IHats hatsProtocol, IHatsModuleFactory hatsModuleFactory, - address hatsElectionEligibilityImplementation, + address hatsElectionsEligibilityImplementation, uint256 topHatId, address topHatAccount, uint256 adminHatId, @@ -283,7 +283,7 @@ contract DecentHatsCreationModule { if (termEndDateTs != 0) { return hatsModuleFactory.createHatsModule( - hatsElectionEligibilityImplementation, + hatsElectionsEligibilityImplementation, hatsProtocol.getNextId(adminHatId), abi.encode(topHatId, uint256(0)), // [BALLOT_BOX_ID, ADMIN_HAT_ID] abi.encode(termEndDateTs), @@ -315,7 +315,7 @@ contract DecentHatsCreationModule { if (hat.termEndDateTs != 0) { address[] memory nominatedWearers = new address[](1); nominatedWearers[0] = hat.wearer; - IHatsElectionEligibility(eligibilityAddress).elect( + IHatsElectionsEligibility(eligibilityAddress).elect( hat.termEndDateTs, nominatedWearers ); diff --git a/contracts/interfaces/hats/full/IHatsElectionEligibility.sol b/contracts/interfaces/hats/full/modules/IHatsElectionsEligibility.sol similarity index 98% rename from contracts/interfaces/hats/full/IHatsElectionEligibility.sol rename to contracts/interfaces/hats/full/modules/IHatsElectionsEligibility.sol index 3b0c7c67..8d2fb835 100644 --- a/contracts/interfaces/hats/full/IHatsElectionEligibility.sol +++ b/contracts/interfaces/hats/full/modules/IHatsElectionsEligibility.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.13; -interface IHatsElectionEligibility { +interface IHatsElectionsEligibility { event ElectionOpened(uint128 nextTermEnd); event ElectionCompleted(uint128 termEnd, address[] winners); event NewTermStarted(uint128 termEnd); diff --git a/contracts/mocks/MockHatsElectionEligibility.sol b/contracts/mocks/MockHatsElectionEligibility.sol index 5c9c4212..6dec84c8 100644 --- a/contracts/mocks/MockHatsElectionEligibility.sol +++ b/contracts/mocks/MockHatsElectionEligibility.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IHatsElectionEligibility} from "../interfaces/hats/full/IHatsElectionEligibility.sol"; +import {IHatsElectionsEligibility} from "../interfaces/hats/full/modules/IHatsElectionsEligibility.sol"; -contract MockHatsElectionEligibility is IHatsElectionEligibility { +contract MockHatsElectionsEligibility is IHatsElectionsEligibility { function currentTermEnd() external view returns (uint128) {} function nextTermEnd() external view returns (uint128) {} diff --git a/contracts/mocks/MockHatsModuleFactory.sol b/contracts/mocks/MockHatsModuleFactory.sol index df5479d3..57ae687b 100644 --- a/contracts/mocks/MockHatsModuleFactory.sol +++ b/contracts/mocks/MockHatsModuleFactory.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {IHatsModuleFactory} from "../interfaces/hats/full/IHatsModuleFactory.sol"; -import {MockHatsElectionEligibility} from "./MockHatsElectionEligibility.sol"; +import {MockHatsElectionsEligibility} from "./MockHatsElectionEligibility.sol"; contract MockHatsModuleFactory is IHatsModuleFactory { function createHatsModule( @@ -13,7 +13,7 @@ contract MockHatsModuleFactory is IHatsModuleFactory { uint256 ) external override returns (address _instance) { // Deploy a new instance of MockHatsElectionEligibility - MockHatsElectionEligibility newModule = new MockHatsElectionEligibility(); + MockHatsElectionsEligibility newModule = new MockHatsElectionsEligibility(); _instance = address(newModule); } diff --git a/test/DecentAutonomousAdmin.test.ts b/test/DecentAutonomousAdmin.test.ts index a20b00a0..5f009833 100644 --- a/test/DecentAutonomousAdmin.test.ts +++ b/test/DecentAutonomousAdmin.test.ts @@ -6,8 +6,8 @@ import { DecentAutonomousAdmin__factory, MockHats, MockHats__factory, - MockHatsElectionEligibility, - MockHatsElectionEligibility__factory, + MockHatsElectionsEligibility, + MockHatsElectionsEligibility__factory, } from '../typechain-types'; describe('DecentAutonomousAdminHat', function () { @@ -19,7 +19,7 @@ describe('DecentAutonomousAdminHat', function () { // Contract instances let hatsProtocol: MockHats; - let hatsElectionModule: MockHatsElectionEligibility; + let hatsElectionModule: MockHatsElectionsEligibility; let decentAutonomousAdminInstance: DecentAutonomousAdmin; // Variables @@ -33,7 +33,7 @@ describe('DecentAutonomousAdminHat', function () { hatsProtocol = await new MockHats__factory(deployer).deploy(); // Deploy MockHatsElectionEligibility (Eligibility Module) - hatsElectionModule = await new MockHatsElectionEligibility__factory(deployer).deploy(); + hatsElectionModule = await new MockHatsElectionsEligibility__factory(deployer).deploy(); // Create Admin Hat const createAdminTx = await hatsProtocol.createHat( diff --git a/test/DecentHatsCreationModule.test.ts b/test/DecentHatsCreationModule.test.ts index 86586281..982ca014 100644 --- a/test/DecentHatsCreationModule.test.ts +++ b/test/DecentHatsCreationModule.test.ts @@ -23,7 +23,7 @@ import { MockERC20, DecentAutonomousAdmin, DecentAutonomousAdmin__factory, - MockHatsElectionEligibility__factory, + MockHatsElectionsEligibility__factory, MockHatsModuleFactory__factory, ModuleProxyFactory, ModuleProxyFactory__factory, @@ -32,7 +32,7 @@ import { import { getGnosisSafeL2Singleton, getGnosisSafeProxyFactory } from './GlobalSafeDeployments.test'; import { executeSafeTransaction, getHatAccount, predictGnosisSafeAddress } from './helpers'; -describe('DecentHats', () => { +describe('DecentHatsCreationModule', () => { let dao: SignerWithAddress; let mockHats: MockHats; @@ -56,7 +56,7 @@ describe('DecentHats', () => { let mockERC20: MockERC20; let mockERC20Address: string; - let mockHatsElectionEligibilityImplementationAddress: string; + let mockHatsElectionsEligibilityImplementationAddress: string; let mockHatsModuleFactoryAddress: string; let moduleProxyFactory: ModuleProxyFactory; @@ -70,10 +70,10 @@ describe('DecentHats', () => { mockHats = await new MockHats__factory(deployer).deploy(); mockHatsAddress = await mockHats.getAddress(); - const mockHatsElectionEligibilityImplementation = - await new MockHatsElectionEligibility__factory(deployer).deploy(); - mockHatsElectionEligibilityImplementationAddress = - await mockHatsElectionEligibilityImplementation.getAddress(); + const mockHatsElectionsEligibilityImplementation = + await new MockHatsElectionsEligibility__factory(deployer).deploy(); + mockHatsElectionsEligibilityImplementationAddress = + await mockHatsElectionsEligibilityImplementation.getAddress(); const mockHatsModuleFactory = await new MockHatsModuleFactory__factory(deployer).deploy(); mockHatsModuleFactoryAddress = await mockHatsModuleFactory.getAddress(); @@ -172,15 +172,14 @@ describe('DecentHats', () => { [ { hatsProtocol: mockHatsAddress, - registry: await erc6551Registry.getAddress(), + erc6551Registry: await erc6551Registry.getAddress(), hatsModuleFactory: mockHatsModuleFactoryAddress, moduleProxyFactory: await moduleProxyFactory.getAddress(), decentAutonomousAdminMasterCopy: await decentAutonomousAdminMasterCopy.getAddress(), hatsAccountImplementation: mockHatsAccountImplementationAddress, keyValuePairs: await keyValuePairs.getAddress(), - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, - + hatsElectionsEligibilityImplementation: + mockHatsElectionsEligibilityImplementationAddress, topHat: { details: '', imageURI: '', @@ -246,7 +245,7 @@ describe('DecentHats', () => { { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), + erc6551Registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), topHat: { details: '', @@ -262,8 +261,8 @@ describe('DecentHats', () => { }, hats: [], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, + hatsElectionsEligibilityImplementation: + mockHatsElectionsEligibilityImplementationAddress, }, ], ), @@ -317,7 +316,7 @@ describe('DecentHats', () => { { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), + erc6551Registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), topHat: { details: '', @@ -351,8 +350,8 @@ describe('DecentHats', () => { }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, + hatsElectionsEligibilityImplementation: + mockHatsElectionsEligibilityImplementationAddress, }, ], ), @@ -393,7 +392,7 @@ describe('DecentHats', () => { { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), + erc6551Registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), topHat: { details: '', @@ -442,8 +441,8 @@ describe('DecentHats', () => { }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, + hatsElectionsEligibilityImplementation: + mockHatsElectionsEligibilityImplementationAddress, }, ], ), @@ -515,7 +514,7 @@ describe('DecentHats', () => { { hatsProtocol: mockHatsAddress, hatsAccountImplementation: mockHatsAccountImplementationAddress, - registry: await erc6551Registry.getAddress(), + erc6551Registry: await erc6551Registry.getAddress(), keyValuePairs: await keyValuePairs.getAddress(), topHat: { details: '', @@ -569,8 +568,8 @@ describe('DecentHats', () => { }, ], hatsModuleFactory: mockHatsModuleFactoryAddress, - hatsElectionEligibilityImplementation: - mockHatsElectionEligibilityImplementationAddress, + hatsElectionsEligibilityImplementation: + mockHatsElectionsEligibilityImplementationAddress, }, ], ),