diff --git a/pkgs/contract/contracts/splits/splitters/SplitFactoryV2.sol b/pkgs/contract/contracts/splits/splitters/SplitFactoryV2.sol index 616f4ce..cd4eb17 100644 --- a/pkgs/contract/contracts/splits/splitters/SplitFactoryV2.sol +++ b/pkgs/contract/contracts/splits/splitters/SplitFactoryV2.sol @@ -7,173 +7,183 @@ import { SplitV2Lib } from "../libraries/SplitV2.sol"; import { Nonces } from "../utils/Nonces.sol"; import { SplitWalletV2 } from "./SplitWalletV2.sol"; +import "hardhat/console.sol"; + /** * @title SplitFactoryV2 * @author Splits * @notice Minimal smart wallet clone-factory for v2 splitters. */ abstract contract SplitFactoryV2 is Nonces { - /* -------------------------------------------------------------------------- */ - /* EVENTS */ - /* -------------------------------------------------------------------------- */ - - event SplitCreated(address indexed split, SplitV2Lib.Split splitParams, address owner, address creator); - - /* -------------------------------------------------------------------------- */ - /* STORAGE */ - /* -------------------------------------------------------------------------- */ - - /// @notice address of Split Wallet V2 implementation. - address public immutable SPLIT_WALLET_IMPLEMENTATION; - - /* -------------------------------------------------------------------------- */ - /* EXTERNAL FUNCTIONS */ - /* -------------------------------------------------------------------------- */ - - /** - * @notice Create a new split using create2. - * @dev if integrating, please make sure you understand how to handle greifing - * properly to avoid potential issues with frontrunning. See docs for more information. - * @param _splitParams Params to create split with. - * @param _owner Owner of created split. - * @param _creator Creator of created split. - * @param _salt Salt for create2. - */ - function createSplitDeterministic( - SplitV2Lib.Split calldata _splitParams, - address _owner, - address _creator, - bytes32 _salt - ) - external - returns (address split) - { - split = Clone.cloneDeterministic({ - _implementation: SPLIT_WALLET_IMPLEMENTATION, - _salt: _getSalt({ _splitParams: _splitParams, _owner: _owner, _salt: _salt }) - }); - - SplitWalletV2(split).initialize(_splitParams, _owner); - - emit SplitCreated({ split: split, splitParams: _splitParams, owner: _owner, creator: _creator }); - } - - /** - * @notice Create a new split with params and owner. - * @dev Uses a hash-based incrementing nonce over params and owner. - * @dev designed to be used with integrating contracts to avoid salt management and needing to handle the potential - * for griefing via front-running. See docs for more information. - * @param _splitParams Params to create split with. - * @param _owner Owner of created split. - * @param _creator Creator of created split. - */ - function createSplit( - SplitV2Lib.Split calldata _splitParams, - address _owner, - address _creator - ) - external - returns (address split) - { - bytes32 hash = keccak256(abi.encode(_splitParams, _owner)); - - split = Clone.cloneDeterministic({ - _implementation: SPLIT_WALLET_IMPLEMENTATION, - _salt: keccak256(bytes.concat(hash, abi.encode(useNonce(hash)))) - }); - - SplitWalletV2(split).initialize(_splitParams, _owner); - - emit SplitCreated({ split: split, splitParams: _splitParams, owner: _owner, creator: _creator }); - } - - /** - * @notice Predict the address of a new split based on split params, owner, and salt. - * @param _splitParams Params to create split with - * @param _owner Owner of created split - * @param _salt Salt for create2 - */ - function predictDeterministicAddress( - SplitV2Lib.Split calldata _splitParams, - address _owner, - bytes32 _salt - ) - external - view - returns (address) - { - return _predictDeterministicAddress({ _splitParams: _splitParams, _owner: _owner, _salt: _salt }); - } - - /** - * @notice Predict the address of a new split based on the nonce of the hash of the params and owner. - * @param _splitParams Params to create split with. - * @param _owner Owner of created split. - */ - function predictDeterministicAddress( - SplitV2Lib.Split calldata _splitParams, - address _owner - ) - external - view - returns (address) - { - bytes32 hash = keccak256(abi.encode(_splitParams, _owner)); - return Clone.predictDeterministicAddress({ - _implementation: SPLIT_WALLET_IMPLEMENTATION, - _salt: keccak256(bytes.concat(hash, abi.encode(nonces(hash)))), - _deployer: address(this) - }); - } - - /** - * @notice Predict the address of a new split and check if it is deployed. - * @param _splitParams Params to create split with. - * @param _owner Owner of created split. - * @param _salt Salt for create2. - */ - function isDeployed( - SplitV2Lib.Split calldata _splitParams, - address _owner, - bytes32 _salt - ) - external - view - returns (address split, bool exists) - { - split = _predictDeterministicAddress({ _splitParams: _splitParams, _owner: _owner, _salt: _salt }); - exists = split.code.length > 0; - } - - /* -------------------------------------------------------------------------- */ - /* PRIVATE/INTERNAL FUNCTIONS */ - /* -------------------------------------------------------------------------- */ - - function _getSalt( - SplitV2Lib.Split calldata _splitParams, - address _owner, - bytes32 _salt - ) - internal - pure - returns (bytes32) - { - return keccak256(bytes.concat(abi.encode(_splitParams, _owner), _salt)); - } - - function _predictDeterministicAddress( - SplitV2Lib.Split calldata _splitParams, - address _owner, - bytes32 _salt - ) - internal - view - returns (address) - { - return Clone.predictDeterministicAddress({ - _implementation: SPLIT_WALLET_IMPLEMENTATION, - _salt: _getSalt({ _splitParams: _splitParams, _owner: _owner, _salt: _salt }), - _deployer: address(this) - }); - } + /* -------------------------------------------------------------------------- */ + /* EVENTS */ + /* -------------------------------------------------------------------------- */ + + event SplitCreated( + address indexed split, + SplitV2Lib.Split splitParams, + address owner, + address creator + ); + + /* -------------------------------------------------------------------------- */ + /* STORAGE */ + /* -------------------------------------------------------------------------- */ + + /// @notice address of Split Wallet V2 implementation. + address public immutable SPLIT_WALLET_IMPLEMENTATION; + + /* -------------------------------------------------------------------------- */ + /* EXTERNAL FUNCTIONS */ + /* -------------------------------------------------------------------------- */ + + /** + * @notice Create a new split using create2. + * @dev if integrating, please make sure you understand how to handle greifing + * properly to avoid potential issues with frontrunning. See docs for more information. + * @param _splitParams Params to create split with. + * @param _owner Owner of created split. + * @param _creator Creator of created split. + * @param _salt Salt for create2. + */ + function createSplitDeterministic( + SplitV2Lib.Split calldata _splitParams, + address _owner, + address _creator, + bytes32 _salt + ) external returns (address split) { + split = Clone.cloneDeterministic({ + _implementation: SPLIT_WALLET_IMPLEMENTATION, + _salt: _getSalt({ + _splitParams: _splitParams, + _owner: _owner, + _salt: _salt + }) + }); + + SplitWalletV2(split).initialize(_splitParams, _owner); + + emit SplitCreated({ + split: split, + splitParams: _splitParams, + owner: _owner, + creator: _creator + }); + } + + /** + * @notice Create a new split with params and owner. + * @dev Uses a hash-based incrementing nonce over params and owner. + * @dev designed to be used with integrating contracts to avoid salt management and needing to handle the potential + * for griefing via front-running. See docs for more information. + * @param _splitParams Params to create split with. + * @param _owner Owner of created split. + * @param _creator Creator of created split. + */ + function createSplit( + SplitV2Lib.Split calldata _splitParams, + address _owner, + address _creator + ) external returns (address split) { + bytes32 hash = keccak256(abi.encode(_splitParams, _owner)); + + split = Clone.cloneDeterministic({ + _implementation: SPLIT_WALLET_IMPLEMENTATION, + _salt: keccak256(bytes.concat(hash, abi.encode(useNonce(hash)))) + }); + + SplitWalletV2(split).initialize(_splitParams, _owner); + + emit SplitCreated({ + split: split, + splitParams: _splitParams, + owner: _owner, + creator: _creator + }); + } + + /** + * @notice Predict the address of a new split based on split params, owner, and salt. + * @param _splitParams Params to create split with + * @param _owner Owner of created split + * @param _salt Salt for create2 + */ + function predictDeterministicAddress( + SplitV2Lib.Split calldata _splitParams, + address _owner, + bytes32 _salt + ) external view returns (address) { + return + _predictDeterministicAddress({ + _splitParams: _splitParams, + _owner: _owner, + _salt: _salt + }); + } + + /** + * @notice Predict the address of a new split based on the nonce of the hash of the params and owner. + * @param _splitParams Params to create split with. + * @param _owner Owner of created split. + */ + function predictDeterministicAddress( + SplitV2Lib.Split calldata _splitParams, + address _owner + ) external view returns (address) { + bytes32 hash = keccak256(abi.encode(_splitParams, _owner)); + return + Clone.predictDeterministicAddress({ + _implementation: SPLIT_WALLET_IMPLEMENTATION, + _salt: keccak256(bytes.concat(hash, abi.encode(nonces(hash)))), + _deployer: address(this) + }); + } + + /** + * @notice Predict the address of a new split and check if it is deployed. + * @param _splitParams Params to create split with. + * @param _owner Owner of created split. + * @param _salt Salt for create2. + */ + function isDeployed( + SplitV2Lib.Split calldata _splitParams, + address _owner, + bytes32 _salt + ) external view returns (address split, bool exists) { + split = _predictDeterministicAddress({ + _splitParams: _splitParams, + _owner: _owner, + _salt: _salt + }); + exists = split.code.length > 0; + } + + /* -------------------------------------------------------------------------- */ + /* PRIVATE/INTERNAL FUNCTIONS */ + /* -------------------------------------------------------------------------- */ + + function _getSalt( + SplitV2Lib.Split calldata _splitParams, + address _owner, + bytes32 _salt + ) internal pure returns (bytes32) { + return keccak256(bytes.concat(abi.encode(_splitParams, _owner), _salt)); + } + + function _predictDeterministicAddress( + SplitV2Lib.Split calldata _splitParams, + address _owner, + bytes32 _salt + ) internal view returns (address) { + return + Clone.predictDeterministicAddress({ + _implementation: SPLIT_WALLET_IMPLEMENTATION, + _salt: _getSalt({ + _splitParams: _splitParams, + _owner: _owner, + _salt: _salt + }), + _deployer: address(this) + }); + } } diff --git a/pkgs/contract/contracts/splitscreator/SplitsCreator.sol b/pkgs/contract/contracts/splitscreator/SplitsCreator.sol index 135bfb3..6dde319 100644 --- a/pkgs/contract/contracts/splitscreator/SplitsCreator.sol +++ b/pkgs/contract/contracts/splitscreator/SplitsCreator.sol @@ -6,36 +6,46 @@ import { ISplitsCreator } from "./ISplitsCreator.sol"; import { ISplitFactoryV2 } from "../splits/interfaces/ISplitFactoryV2.sol"; import { SplitV2Lib } from "../splits/libraries/SplitV2.sol"; import { IFractionToken } from "../fractiontoken/IFractionToken.sol"; +import { IHatsTimeFrameModule } from "../timeframe/IHatsTimeFrameModule.sol"; import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +import { Clone } from "solady/src/utils/Clone.sol"; -contract SplitsCreator is ISplitsCreator, ERC2771Context { - ISplitFactoryV2 splitFactoryV2; +import "hardhat/console.sol"; - IFractionToken fractionToken; +contract SplitsCreator is ISplitsCreator, Clone { + function TRUSTED_FORWARDER() public pure returns (address) { + return _getArgAddress(12); + } + + function SPLIT_FACTORY_V2() public pure returns (ISplitFactoryV2) { + return ISplitFactoryV2(_getArgAddress(44)); + } + + function HATS_TIME_FRAME_MODULE() + public + pure + returns (IHatsTimeFrameModule) + { + return IHatsTimeFrameModule(_getArgAddress(76)); + } - constructor( - address _splitFactoryV2, - address _fractionToken, - address _trustedForwarder - ) ERC2771Context(_trustedForwarder) { - splitFactoryV2 = ISplitFactoryV2(_splitFactoryV2); - fractionToken = IFractionToken(_fractionToken); + function FRACTION_TOKEN() public pure returns (IFractionToken) { + return IFractionToken(_getArgAddress(108)); } function create( - SplitsInfo[] memory _splitInfos + SplitsInfo[] memory _splitsInfo ) external returns (address) { uint256 numOfShareHolders = 0; - for (uint256 i = 0; i < _splitInfos.length; i++) { - SplitsInfo memory _splitInfo = _splitInfos[i]; - for (uint256 si = 0; si < _splitInfo.wearers.length; si++) { - uint256 tokenId = fractionToken.getTokenId( + for (uint i = 0; i < _splitsInfo.length; i++) { + SplitsInfo memory _splitInfo = _splitsInfo[i]; + for (uint si = 0; si < _splitInfo.wearers.length; si++) { + uint256 tokenId = FRACTION_TOKEN().getTokenId( _splitInfo.hatId, _splitInfo.wearers[si] ); - address[] memory recepients = fractionToken.getTokenRecipients( - tokenId - ); + address[] memory recepients = FRACTION_TOKEN() + .getTokenRecipients(tokenId); numOfShareHolders += recepients.length + 1; } } @@ -45,52 +55,68 @@ contract SplitsCreator is ISplitsCreator, ERC2771Context { uint256[] memory hatIdsOfShareHolders = new uint256[]( numOfShareHolders ); - uint256[] memory multipliersOfShareHolders = new uint256[]( + uint256[] memory roleMultipliersOfShareHolders = new uint256[]( + numOfShareHolders + ); + uint256[] memory hatsTimeFrameMultipliersOfShareHolders = new uint256[]( numOfShareHolders ); uint256 shareHolderIndex = 0; - for (uint256 i = 0; i < _splitInfos.length; i++) { - SplitsInfo memory _splitInfo = _splitInfos[i]; - for (uint256 si = 0; si < _splitInfo.wearers.length; si++) { - uint256 tokenId = fractionToken.getTokenId( + for (uint i = 0; i < _splitsInfo.length; i++) { + SplitsInfo memory _splitInfo = _splitsInfo[i]; + for (uint si = 0; si < _splitInfo.wearers.length; si++) { + uint256 tokenId = FRACTION_TOKEN().getTokenId( _splitInfo.hatId, _splitInfo.wearers[si] ); - uint256 multiplier = _splitInfo.multiplierTop / + uint256 roleMultiplier = _splitInfo.multiplierTop / _splitInfo.multiplierBottom; + uint256 hatsTimeFrameMultiplier = _getHatsTimeFrameMultiplier( + _splitInfo.wearers[si], + _splitInfo.hatId + ); + // ロール保持者に対する分配の計算 shareHolders[shareHolderIndex] = _splitInfo.wearers[si]; warers[shareHolderIndex] = _splitInfo.wearers[si]; hatIdsOfShareHolders[shareHolderIndex] = _splitInfo.hatId; - multipliersOfShareHolders[shareHolderIndex] = multiplier; + roleMultipliersOfShareHolders[ + shareHolderIndex + ] = roleMultiplier; + hatsTimeFrameMultipliersOfShareHolders[ + shareHolderIndex + ] = hatsTimeFrameMultiplier; shareHolderIndex++; - address[] memory recipients = fractionToken.getTokenRecipients( - tokenId - ); - for (uint256 j = 0; j < recipients.length; j++) { + // FractionTokenのホルダーに対する分配の計算 + address[] memory recipients = FRACTION_TOKEN() + .getTokenRecipients(tokenId); + for (uint j = 0; j < recipients.length; j++) { shareHolders[shareHolderIndex] = recipients[j]; warers[shareHolderIndex] = _splitInfo.wearers[si]; hatIdsOfShareHolders[shareHolderIndex] = _splitInfo.hatId; - multipliersOfShareHolders[shareHolderIndex] = multiplier; + roleMultipliersOfShareHolders[ + shareHolderIndex + ] = roleMultiplier; + hatsTimeFrameMultipliersOfShareHolders[ + shareHolderIndex + ] = hatsTimeFrameMultiplier; shareHolderIndex++; } } } - uint256[] memory balanceOfShareHolders = fractionToken.balanceOfBatch( - shareHolders, - warers, - hatIdsOfShareHolders - ); + uint256[] memory balanceOfShareHolders = FRACTION_TOKEN() + .balanceOfBatch(shareHolders, warers, hatIdsOfShareHolders); uint256 totalAllocation = 0; uint256[] memory allocations = new uint256[](shareHolderIndex); for (uint256 i = 0; i < shareHolderIndex; i++) { uint256 share = balanceOfShareHolders[i] * - multipliersOfShareHolders[i]; + roleMultipliersOfShareHolders[i] * + hatsTimeFrameMultipliersOfShareHolders[i]; totalAllocation += share; allocations[i] = share; } @@ -102,14 +128,46 @@ contract SplitsCreator is ISplitsCreator, ERC2771Context { distributionIncentive: 0 }); - address split = splitFactoryV2.createSplit( + address split = SPLIT_FACTORY_V2().createSplitDeterministic( _splitParams, address(this), - msg.sender + msg.sender, + _generateSalt(_splitsInfo) ); emit SplitsCreated(split); return split; } + + function _getHatsTimeFrameMultiplier( + address _wearer, + uint256 _hatId + ) internal view returns (uint256) { + if (address(HATS_TIME_FRAME_MODULE()) == address(0)) return 1; + return + _sqrt( + HATS_TIME_FRAME_MODULE().getWearingElapsedTime(_wearer, _hatId) + ); + } + + function _sqrt(uint256 y) internal pure returns (uint256 z) { + if (y > 3) { + z = y; + uint256 x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + // else z = 0 (default value) + } + + function _generateSalt( + SplitsInfo[] memory splitsInfo + ) internal pure returns (bytes32) { + return keccak256(abi.encode(splitsInfo)); + } } diff --git a/pkgs/contract/contracts/splitscreator/SplitsCreatorFactory.sol b/pkgs/contract/contracts/splitscreator/SplitsCreatorFactory.sol new file mode 100644 index 0000000..8e26dc4 --- /dev/null +++ b/pkgs/contract/contracts/splitscreator/SplitsCreatorFactory.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import { LibClone } from "solady/src/utils/LibClone.sol"; +import { SplitsCreator } from "./SplitsCreator.sol"; +import { ISplitsCreator } from "./ISplitsCreator.sol"; + +import "hardhat/console.sol"; + +contract SplitsCreatorFactory { + event SplitCreatorCreated( + address indexed creator, + address indexed splitCreator, + uint256 topHatId, + address trustedForwarder, + address splitFactoryV2, + address hatsTimeFrameModule, + address fractionToken + ); + + address public immutable SPLITS_CREATOR_IMPLEMENTATION; + + constructor(address _splitsCreatorImplementation) { + SPLITS_CREATOR_IMPLEMENTATION = _splitsCreatorImplementation; + } + + function createSplitCreatorDeterministic( + uint256 _topHatId, + address _trustedForwarder, + address _splitFactoryV2, + address _hatsTimeFrameModule, + address _fractionToken, + bytes32 _salt + ) external returns (address splitCreator) { + splitCreator = LibClone.cloneDeterministic( + SPLITS_CREATOR_IMPLEMENTATION, + abi.encode( + _trustedForwarder, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken + ), + _getSalt( + _topHatId, + _trustedForwarder, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken, + _salt + ) + ); + + emit SplitCreatorCreated( + msg.sender, + splitCreator, + _topHatId, + _trustedForwarder, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken + ); + } + + function predictDeterministicAddress( + uint256 _topHatId, + address _trustedForwarder, + address _splitFactoryV2, + address _hatsTimeFrameModule, + address _fractionToken, + bytes32 _salt + ) external view returns (address) { + return + LibClone.predictDeterministicAddress( + SPLITS_CREATOR_IMPLEMENTATION, + abi.encode( + _trustedForwarder, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken + ), + _getSalt( + _topHatId, + _trustedForwarder, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken, + _salt + ), + address(this) + ); + } + + function _getSalt( + uint256 _topHatId, + address _trustedForwarder, + address _splitFactoryV2, + address _hatsTimeFrameModule, + address _fractionToken, + bytes32 _salt + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + _topHatId, + _trustedForwarder, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken, + _salt + ) + ); + } +} diff --git a/pkgs/contract/helpers/deploy/Splits.ts b/pkgs/contract/helpers/deploy/Splits.ts index 1fdd65e..586ddbc 100644 --- a/pkgs/contract/helpers/deploy/Splits.ts +++ b/pkgs/contract/helpers/deploy/Splits.ts @@ -13,6 +13,10 @@ export type PushSplitsFactory = Awaited< ReturnType >["PushSplitsFactory"]; +export type SplitsCreatorFactory = Awaited< + ReturnType +>["SplitsCreatorFactory"]; + export type SplitsCreator = Awaited< ReturnType >["SplitsCreator"]; @@ -38,16 +42,19 @@ export const deploySplitsProtocol = async () => { }; }; -export const deploySplitsCreator = async ( - splitFactoryAddress: Address, - fractionTokenAddress: Address, - forwarderAddress: Address +export const deploySplitsCreatorFactory = async ( + splitsCreatorImpl: Address ) => { - const SplitsCreator = await viem.deployContract("SplitsCreator", [ - splitFactoryAddress, - fractionTokenAddress, - forwarderAddress, - ]); + const SplitsCreatorFactory = await viem.deployContract( + "SplitsCreatorFactory", + [splitsCreatorImpl] + ); + + return { SplitsCreatorFactory }; +}; + +export const deploySplitsCreator = async () => { + const SplitsCreator = await viem.deployContract("SplitsCreator"); return { SplitsCreator }; }; diff --git a/pkgs/contract/package.json b/pkgs/contract/package.json index 2624c22..8824750 100644 --- a/pkgs/contract/package.json +++ b/pkgs/contract/package.json @@ -29,7 +29,7 @@ "chai": "^4.2.0", "hardhat": "^2.22.9", "hardhat-gas-reporter": "^1.0.8", - "solady": "^0.0.123", + "solady": "^0.0.201", "solidity-coverage": "^0.8.1", "ts-node": "^10.9.2", "viem": "^2.20.1", diff --git a/pkgs/contract/test/SplitsCreator.ts b/pkgs/contract/test/SplitsCreator.ts index 35502d8..8f43176 100644 --- a/pkgs/contract/test/SplitsCreator.ts +++ b/pkgs/contract/test/SplitsCreator.ts @@ -2,6 +2,7 @@ import { Address, decodeEventLog, formatEther, + keccak256, parseEther, PublicClient, WalletClient, @@ -11,23 +12,172 @@ import { deployFractionToken, FractionToken, } from "../helpers/deploy/FractionToken"; -import { deployHatsProtocol, Hats } from "../helpers/deploy/Hats"; +import { + deployHatsModuleFactory, + deployHatsProtocol, + deployHatsTimeFrameModule, + Hats, + HatsModuleFactory, + HatsTimeFrameModule, +} from "../helpers/deploy/Hats"; import { deploySplitsCreator, + deploySplitsCreatorFactory, deploySplitsProtocol, PullSplitsFactory, PushSplitsFactory, SplitsCreator, + SplitsCreatorFactory, SplitsWarehouse, } from "../helpers/deploy/Splits"; import { viem } from "hardhat"; import { expect } from "chai"; +import { time } from "@nomicfoundation/hardhat-network-helpers"; + +describe("SplitsCreator Factory", () => { + let Hats: Hats; + let HatsModuleFactory: HatsModuleFactory; + let HatsTimeFrameModule_IMPL: HatsTimeFrameModule; + let HatsTimeFrameModule: HatsTimeFrameModule; + let SplitsWarehouse: SplitsWarehouse; + let PullSplitsFactory: PullSplitsFactory; + let PushSplitsFactory: PushSplitsFactory; + let SplitsCreatorFactory: SplitsCreatorFactory; + let SplitsCreator_IMPL: SplitsCreator; + let SplitsCreator: SplitsCreator; + let FractionToken: FractionToken; + + let address1: WalletClient; + + let topHatId: bigint; + + before(async () => { + const { Hats: _Hats } = await deployHatsProtocol(); + Hats = _Hats; + + const { HatsModuleFactory: _HatsModuleFactory } = + await deployHatsModuleFactory(Hats.address); + HatsModuleFactory = _HatsModuleFactory; + + const { HatsTimeFrameModule: _HatsTimeFrameModule } = + await deployHatsTimeFrameModule(); + HatsTimeFrameModule_IMPL = _HatsTimeFrameModule; + + const { + SplitsWarehouse: _SplitsWarehouse, + PullSplitsFactory: _PullSplitsFactory, + PushSplitsFactory: _PushSplitsFactory, + } = await deploySplitsProtocol(); + + SplitsWarehouse = _SplitsWarehouse; + PullSplitsFactory = _PullSplitsFactory; + PushSplitsFactory = _PushSplitsFactory; + + const { FractionToken: _FractionToken } = await deployFractionToken( + "", + 10000n, + Hats.address, + zeroAddress + ); + FractionToken = _FractionToken; + + const { SplitsCreator: _SplitsCreator } = await deploySplitsCreator(); + SplitsCreator_IMPL = _SplitsCreator; + + [address1] = await viem.getWalletClients(); + + await Hats.write.mintTopHat([ + address1.account?.address!, + "Description", + "https://test.com/tophat.png", + ]); + + topHatId = BigInt( + "0x0000000100000000000000000000000000000000000000000000000000000000" + ); + + await HatsModuleFactory.write.createHatsModule([ + HatsTimeFrameModule_IMPL.address, + topHatId, + "0x", + "0x", + BigInt(0), + ]); + + const hatsTimeFrameModuleAddress = + await HatsModuleFactory.read.getHatsModuleAddress([ + HatsTimeFrameModule_IMPL.address, + topHatId, + "0x", + BigInt(0), + ]); + + HatsTimeFrameModule = await viem.getContractAt( + "HatsTimeFrameModule", + hatsTimeFrameModuleAddress + ); + }); + + it("Should deploy SplitsCreatorFactory", async () => { + const { SplitsCreatorFactory: _SplitsCreatorFactory } = + await deploySplitsCreatorFactory(SplitsCreator_IMPL.address); + + SplitsCreatorFactory = _SplitsCreatorFactory; + + expect(SplitsCreatorFactory.address).to.be.a("string"); + expect( + await SplitsCreatorFactory.read.predictDeterministicAddress([ + topHatId, + zeroAddress, + PullSplitsFactory.address, + HatsTimeFrameModule.address, + FractionToken.address, + keccak256("0x1234"), + ]) + ).to.be.a("string"); + }); + + it("Should deploy SplitsCreator", async () => { + const predictedAddress = + await SplitsCreatorFactory.read.predictDeterministicAddress([ + topHatId, + address1.account?.address!, + PullSplitsFactory.address, + HatsTimeFrameModule.address, + FractionToken.address, + keccak256("0x1234"), + ]); + + await SplitsCreatorFactory.write.createSplitCreatorDeterministic([ + topHatId, + address1.account?.address!, + PullSplitsFactory.address, + HatsTimeFrameModule.address, + FractionToken.address, + keccak256("0x1234"), + ]); + + SplitsCreator = await viem.getContractAt("SplitsCreator", predictedAddress); + + expect( + (await SplitsCreator.read.HATS_TIME_FRAME_MODULE()).toLowerCase() + ).equal(HatsTimeFrameModule.address.toLowerCase()); + expect((await SplitsCreator.read.FRACTION_TOKEN()).toLowerCase()).equal( + FractionToken.address.toLowerCase() + ); + }); +}); describe("CreateSplit", () => { let Hats: Hats; + let HatsModuleFactory: HatsModuleFactory; + let HatsTimeFrameModule_IMPL: HatsTimeFrameModule; + let HatsTimeFrameModule: HatsTimeFrameModule; let SplitsWarehouse: SplitsWarehouse; let PullSplitsFactory: PullSplitsFactory; let PushSplitsFactory: PushSplitsFactory; + let SplitsCreatorFactory: SplitsCreatorFactory; + let SplitsCreator_IMPL: SplitsCreator; let SplitsCreator: SplitsCreator; let FractionToken: FractionToken; @@ -35,6 +185,8 @@ describe("CreateSplit", () => { let address2: WalletClient; let address3: WalletClient; + let topHatId: bigint; + let hatterHatId: bigint; let hat1_id: bigint; let hat2_id: bigint; @@ -44,6 +196,14 @@ describe("CreateSplit", () => { const { Hats: _Hats } = await deployHatsProtocol(); Hats = _Hats; + const { HatsModuleFactory: _HatsModuleFactory } = + await deployHatsModuleFactory(Hats.address); + HatsModuleFactory = _HatsModuleFactory; + + const { HatsTimeFrameModule: _HatsTimeFrameModule } = + await deployHatsTimeFrameModule(); + HatsTimeFrameModule_IMPL = _HatsTimeFrameModule; + const { SplitsWarehouse: _SplitsWarehouse, PullSplitsFactory: _PullSplitsFactory, @@ -62,12 +222,8 @@ describe("CreateSplit", () => { ); FractionToken = _FractionToken; - const { SplitsCreator: _SplitsCreator } = await deploySplitsCreator( - PullSplitsFactory.address, - FractionToken.address, - zeroAddress - ); - SplitsCreator = _SplitsCreator; + const { SplitsCreator: _SplitsCreator } = await deploySplitsCreator(); + SplitsCreator_IMPL = _SplitsCreator; [address1, address2, address3] = await viem.getWalletClients(); @@ -79,10 +235,94 @@ describe("CreateSplit", () => { "https://test.com/tophat.png", ]); - let txHash = await Hats.write.createHat([ - BigInt( - "0x0000000100000000000000000000000000000000000000000000000000000000" - ), + topHatId = BigInt( + "0x0000000100000000000000000000000000000000000000000000000000000000" + ); + + await HatsModuleFactory.write.createHatsModule([ + HatsTimeFrameModule_IMPL.address, + topHatId, + "0x", + "0x", + BigInt(0), + ]); + + const hatsTimeFrameModuleAddress = + await HatsModuleFactory.read.getHatsModuleAddress([ + HatsTimeFrameModule_IMPL.address, + topHatId, + "0x", + BigInt(0), + ]); + + HatsTimeFrameModule = await viem.getContractAt( + "HatsTimeFrameModule", + hatsTimeFrameModuleAddress + ); + + const { SplitsCreatorFactory: _SplitsCreatorFactory } = + await deploySplitsCreatorFactory(SplitsCreator_IMPL.address); + + SplitsCreatorFactory = _SplitsCreatorFactory; + + let txHash = + await SplitsCreatorFactory.write.createSplitCreatorDeterministic([ + topHatId, + address1.account?.address!, + PullSplitsFactory.address, + HatsTimeFrameModule.address, + FractionToken.address, + keccak256("0x1234"), + ]); + + let receipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + for (const log of receipt.logs) { + try { + const decodedLog = decodeEventLog({ + abi: SplitsCreatorFactory.abi, + data: log.data, + topics: log.topics, + }); + if (decodedLog.eventName == "SplitCreatorCreated") { + SplitsCreator = await viem.getContractAt( + "SplitsCreator", + decodedLog.args.splitCreator + ); + } + } catch (error) {} + } + + txHash = await Hats.write.createHat([ + topHatId, + "hatterHat", + 3, + "0x0000000000000000000000000000000000004a75", + "0x0000000000000000000000000000000000004a75", + true, + "https://test.com/hat_image.png", + ]); + + receipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + for (const log of receipt.logs) { + try { + const decodedLog = decodeEventLog({ + abi: Hats.abi, + data: log.data, + topics: log.topics, + }); + if (decodedLog.eventName == "HatCreated") + hatterHatId = decodedLog.args.id; + } catch (error) {} + } + + txHash = await Hats.write.createHat([ + hatterHatId, "role1", 10, "0x0000000000000000000000000000000000004a75", @@ -91,7 +331,7 @@ describe("CreateSplit", () => { "https://test.com/hat_image.png", ]); - let receipt = await publicClient.waitForTransactionReceipt({ + receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, }); @@ -107,9 +347,7 @@ describe("CreateSplit", () => { } txHash = await Hats.write.createHat([ - BigInt( - "0x0000000100000000000000000000000000000000000000000000000000000000" - ), + hatterHatId, "role2", 10, "0x0000000000000000000000000000000000004a75", @@ -133,47 +371,39 @@ describe("CreateSplit", () => { } catch (error) {} } - // address1とaddress2にHatをmint - await Hats.write.mintHat([hat1_id, address1.account?.address!]); - await Hats.write.mintHat([hat2_id, address2.account?.address!]); + await Hats.write.mintHat([hatterHatId, HatsTimeFrameModule.address]); - // address1とaddress2にFractionTokenをmint - await FractionToken.write.mint([hat1_id, address1.account?.address!]); - await FractionToken.write.mint([hat2_id, address2.account?.address!]); + await HatsTimeFrameModule.write.mintHat([ + hat1_id, + address1.account?.address!, + ]); - const tokenId = await FractionToken.read.getTokenId([ - hat2_id, + await time.increase(500000); + + await HatsTimeFrameModule.write.mintHat([ + hat1_id, address2.account?.address!, ]); - - await FractionToken.write.safeTransferFrom( - [ - address2.account?.address!, - address3.account?.address!, - tokenId, - 5000n, - "0x", - ], - { - account: address2.account!, - } - ); + await HatsTimeFrameModule.write.mintHat([ + hat2_id, + address3.account?.address!, + ]); }); it("should create a split", async () => { const txHash = await SplitsCreator.write.create([ [ { - hatId: hat2_id, - wearers: [address2.account?.address!], + hatId: hat1_id, + wearers: [address1.account?.address!, address2.account?.address!], multiplierBottom: 1n, multiplierTop: 1n, }, { - hatId: hat1_id, - wearers: [address1.account?.address!], + hatId: hat2_id, + wearers: [address3.account?.address!], multiplierBottom: 1n, - multiplierTop: 1n, + multiplierTop: 2n, }, ], ]); @@ -217,12 +447,12 @@ describe("CreateSplit", () => { await Split.write.distribute([ { recipients: [ + address1.account?.address!, address2.account?.address!, address3.account?.address!, - address1.account?.address!, ], - allocations: [5000, 5000, 10000], - totalAllocation: 20000, + allocations: [7070000, 10000, 20000], + totalAllocation: 7100000, distributionIncentive: 0, }, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", diff --git a/yarn.lock b/yarn.lock index 40681c6..150175b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5097,10 +5097,10 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -solady@^0.0.123: - version "0.0.123" - resolved "https://registry.yarnpkg.com/solady/-/solady-0.0.123.tgz#7ef95767c1570e3efde7550da2a8b439b2f413d2" - integrity sha512-F/B8OMCplGsS4FrdPrnEG0xdg8HKede5PwC+Rum8soj/LWxfKckA0p+Uwnlbgey2iI82IHvmSOCNhsdbA+lUrw== +solady@^0.0.201: + version "0.0.201" + resolved "https://registry.yarnpkg.com/solady/-/solady-0.0.201.tgz#278bcf859bc2bafe02c571c8fd4c4dededae836f" + integrity sha512-5K0abYDz4y/XwylslIZ1yEeh2xDsFfgmF3sPNhT8z4cQcX/ucz0ImyRfQHnc31J1j1MGuuQTdtSxz3Sfbz9FDQ== solc@0.8.26: version "0.8.26" @@ -5227,7 +5227,7 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5244,6 +5244,15 @@ string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -5329,7 +5338,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5343,6 +5352,13 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -5836,7 +5852,16 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==