Skip to content

Commit

Permalink
Merge pull request #101 from decentdao/streams-on-hats-tree-creation
Browse files Browse the repository at this point in the history
Streams on hats tree creation
  • Loading branch information
adamgall committed Sep 12, 2024
1 parent 781da1b commit afe6b43
Show file tree
Hide file tree
Showing 23 changed files with 2,072 additions and 485 deletions.
179 changes: 112 additions & 67 deletions contracts/DecentHats_0_1_0.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,33 @@ 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";

contract DecentHats_0_1_0 {
string public constant NAME = "DecentHats_0_1_0";

struct SablierStreamParams {
ISablierV2LockupLinear sablier;
address sender;
uint128 totalAmount;
address asset;
bool cancelable;
bool transferable;
LockupLinear.Timestamps timestamps;
LockupLinear.Broker broker;
}

struct Hat {
uint32 maxSupply;
string details;
string imageURI;
bool isMutable;
address wearer;
SablierStreamParams[] sablierParams; // Optional Sablier stream parameters
}

struct CreateTreeParams {
Expand Down Expand Up @@ -44,36 +59,6 @@ contract DecentHats_0_1_0 {
salt = keccak256(concatenatedSaltInput);
}

function createTopHat(
IHats _hatsProtocol,
string memory _topHatDetails,
string memory _topHatImageURI
) internal returns (uint256) {
return
_hatsProtocol.mintTopHat(
address(this),
_topHatDetails,
_topHatImageURI
);
}

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 updateKeyValuePairs(
address _keyValuePairs,
uint256 topHatId
Expand Down Expand Up @@ -113,7 +98,47 @@ contract DecentHats_0_1_0 {
);
}

function createHatAccountMint(
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,
uint256 adminHatId,
Hat calldata hat,
Expand All @@ -135,26 +160,43 @@ contract DecentHats_0_1_0 {
if (hat.wearer != address(0)) {
hatsProtocol.mintHat(hatId, hat.wearer);
}
}

function handleHats(
IHats _hatsProtocol,
IERC6551Registry _registry,
address _hatsAccountImplementation,
bytes32 salt,
address topHatAccount,
uint256 adminHatId,
Hat[] calldata _hats
) internal {
for (uint256 i = 0; i < _hats.length; ) {
createHatAccountMint(
_hatsProtocol,
adminHatId,
_hats[i],
topHatAccount,
_registry,
_hatsAccountImplementation,
salt
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 {
Expand All @@ -165,22 +207,19 @@ contract DecentHats_0_1_0 {

function createAndDeclareTree(CreateTreeParams calldata params) public {
bytes32 salt = getSalt();
uint256 topHatId = createTopHat(

(uint256 topHatId, address topHatAccount) = createTopHatAndAccount(
params.hatsProtocol,
params.topHatDetails,
params.topHatImageURI
);
address topHatAccount = createAccount(
params.topHatImageURI,
params.registry,
params.hatsAccountImplementation,
salt,
address(params.hatsProtocol),
topHatId
salt
);

updateKeyValuePairs(params.keyValuePairs, topHatId);

(uint256 adminHatId, ) = createHatAccountMint(
(uint256 adminHatId, ) = createHatAndAccountAndMintAndStreams(
params.hatsProtocol,
topHatId,
params.adminHat,
Expand All @@ -190,15 +229,21 @@ contract DecentHats_0_1_0 {
salt
);

handleHats(
params.hatsProtocol,
params.registry,
params.hatsAccountImplementation,
salt,
topHatAccount,
adminHatId,
params.hats
);
for (uint256 i = 0; i < params.hats.length; ) {
createHatAndAccountAndMintAndStreams(
params.hatsProtocol,
adminHatId,
params.hats[i],
topHatAccount,
params.registry,
params.hatsAccountImplementation,
salt
);

unchecked {
++i;
}
}

params.hatsProtocol.transferHat(topHatId, address(this), msg.sender);
}
Expand Down
11 changes: 11 additions & 0 deletions contracts/interfaces/sablier/ISablierV2LockupLinear.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {LockupLinear} from "./LockupLinear.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface ISablierV2LockupLinear {
function createWithTimestamps(
LockupLinear.CreateWithTimestamps calldata params
) external returns (uint256 streamId);
}
28 changes: 28 additions & 0 deletions contracts/interfaces/sablier/LockupLinear.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

library LockupLinear {
struct CreateWithTimestamps {
address sender;
address recipient;
uint128 totalAmount;
IERC20 asset;
bool cancelable;
bool transferable;
Timestamps timestamps;
Broker broker;
}

struct Timestamps {
uint40 start;
uint40 cliff;
uint40 end;
}

struct Broker {
address account;
uint256 fee;
}
}
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions contracts/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit afe6b43

Please sign in to comment.