Skip to content

Commit

Permalink
tipping (#1598)
Browse files Browse the repository at this point in the history
  • Loading branch information
giuseppecrj authored Dec 6, 2024
1 parent 6e40348 commit 4028e76
Show file tree
Hide file tree
Showing 7 changed files with 490 additions and 5 deletions.
7 changes: 5 additions & 2 deletions contracts/scripts/deployments/diamonds/DeploySpace.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {DeployPrepayFacet} from "contracts/scripts/deployments/facets/DeployPrep
import {DeployReferrals} from "contracts/scripts/deployments/facets/DeployReferrals.s.sol";
import {DeployMembershipToken} from "contracts/scripts/deployments/facets/DeployMembershipToken.s.sol";
import {DeploySpaceEntitlementGated} from "contracts/scripts/deployments/facets/DeploySpaceEntitlementGated.s.sol";
import {DeployTipping} from "contracts/scripts/deployments/facets/DeployTipping.s.sol";
import {DeployMultiInit} from "contracts/scripts/deployments/utils/DeployMultiInit.s.sol";

contract DeploySpace is DiamondHelper, Deployer {
Expand Down Expand Up @@ -63,7 +64,7 @@ contract DeploySpace is DiamondHelper, Deployer {
DeploySpaceEntitlementGated entitlementGatedHelper =
new DeploySpaceEntitlementGated();
DeployMultiInit deployMultiInit = new DeployMultiInit();

DeployTipping tippingHelper = new DeployTipping();
address tokenOwnable;
address diamondCut;
address diamondLoupe;
Expand All @@ -83,6 +84,7 @@ contract DeploySpace is DiamondHelper, Deployer {
address ownablePending;
address prepay;
address referrals;
address tipping;
address multiInit;

function versionName() public pure override returns (string memory) {
Expand Down Expand Up @@ -134,7 +136,7 @@ contract DeploySpace is DiamondHelper, Deployer {
prepay = prepayHelper.deploy(deployer);
referrals = referralsHelper.deploy(deployer);
entitlementGated = entitlementGatedHelper.deploy(deployer);

tipping = tippingHelper.deploy(deployer);
membershipTokenHelper.removeSelector(IERC721A.tokenURI.selector);

addCut(
Expand Down Expand Up @@ -180,6 +182,7 @@ contract DeploySpace is DiamondHelper, Deployer {
);
addCut(prepayHelper.makeCut(prepay, IDiamond.FacetCutAction.Add));
addCut(referralsHelper.makeCut(referrals, IDiamond.FacetCutAction.Add));
addCut(tippingHelper.makeCut(tipping, IDiamond.FacetCutAction.Add));

return
Diamond.InitParams({
Expand Down
34 changes: 34 additions & 0 deletions contracts/scripts/deployments/facets/DeployTipping.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

//interfaces

//libraries

//contracts
import {Deployer} from "contracts/scripts/common/Deployer.s.sol";
import {TippingFacet} from "contracts/src/spaces/facets/tipping/TippingFacet.sol";
import {FacetHelper} from "contracts/test/diamond/Facet.t.sol";

contract DeployTipping is FacetHelper, Deployer {
constructor() {
addSelector(TippingFacet.tip.selector);
addSelector(TippingFacet.tipsByCurrencyAndTokenId.selector);
addSelector(TippingFacet.tippingCurrencies.selector);
}

function initializer() public pure override returns (bytes4) {
return TippingFacet.__Tipping_init.selector;
}

function versionName() public pure override returns (string memory) {
return "tippingFacet";
}

function __deploy(address deployer) public override returns (address) {
vm.startBroadcast(deployer);
TippingFacet tipping = new TippingFacet();
vm.stopBroadcast();
return address(tipping);
}
}
4 changes: 1 addition & 3 deletions contracts/src/spaces/facets/membership/MembershipBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ abstract contract MembershipBase is IMembershipBase {
}

function _getCreatorBalance() internal view returns (uint256) {
MembershipStorage.Layout storage ds = MembershipStorage.layout();
uint256 contractBalance = address(this).balance;
return FixedPointMathLib.min(contractBalance, ds.tokenBalance);
return MembershipStorage.layout().tokenBalance;
}

function _setCreatorBalance(uint256 newBalance) internal {
Expand Down
69 changes: 69 additions & 0 deletions contracts/src/spaces/facets/tipping/ITipping.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

// interfaces

// libraries

// contracts

interface ITippingBase {
// =============================================================
// Structs
// =============================================================

struct TipRequest {
uint256 tokenId;
address currency;
uint256 amount;
bytes32 messageId;
bytes32 channelId;
}

// =============================================================
// Events
// =============================================================

event Tip(
uint256 indexed tokenId,
address indexed currency,
address sender,
address receiver,
uint256 amount
);

event TipMessage(bytes32 indexed messageId, bytes32 indexed channelId);

// =============================================================
// Errors
// =============================================================

error TokenDoesNotExist();
error SenderIsNotMember();
error ReceiverIsNotMember();
error CannotTipSelf();
error AmountIsZero();
error CurrencyIsZero();
}

interface ITipping is ITippingBase {
/// @notice Sends a tip to a space member
/// @param tipRequest The tip request containing token ID, currency, amount, message ID and channel ID
/// @dev Requires sender and receiver to be members of the space
/// @dev Requires amount > 0 and valid currency address
/// @dev Emits Tip and TipMessage events
function tip(TipRequest calldata tipRequest) external payable;

/// @notice Gets the total tips received for a token ID in a specific currency
/// @param tokenId The token ID to get tips for
/// @param currency The currency address to get tips in
/// @return The total amount of tips received in the specified currency
function tipsByCurrencyAndTokenId(
uint256 tokenId,
address currency
) external view returns (uint256);

/// @notice Gets the list of currencies that have been tipped to the space
/// @return An array of currency addresses
function tippingCurrencies() external view returns (address[] memory);
}
54 changes: 54 additions & 0 deletions contracts/src/spaces/facets/tipping/TippingBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

// interfaces

// libraries
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {CurrencyTransfer} from "contracts/src/utils/libraries/CurrencyTransfer.sol";
// contracts

library TippingBase {
using EnumerableSet for EnumerableSet.AddressSet;

// keccak256(abi.encode(uint256(keccak256("spaces.facets.tipping.storage")) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant STORAGE_SLOT =
0xb6cb334a9eea0cca2581db4520b45ac6f03de8e3927292302206bb82168be300;

struct Layout {
EnumerableSet.AddressSet currencies;
mapping(uint256 tokenId => mapping(address currency => uint256 amount)) tipsByCurrencyByTokenId;
}

function layout() internal pure returns (Layout storage l) {
assembly {
l.slot := STORAGE_SLOT
}
}

function tip(
address sender,
address receiver,
uint256 tokenId,
address currency,
uint256 amount
) internal {
Layout storage ds = layout();

ds.currencies.add(currency);
ds.tipsByCurrencyByTokenId[tokenId][currency] += amount;

CurrencyTransfer.transferCurrency(currency, sender, receiver, amount);
}

function tipsByCurrencyByTokenId(
uint256 tokenId,
address currency
) internal view returns (uint256) {
return layout().tipsByCurrencyByTokenId[tokenId][currency];
}

function tippingCurrencies() internal view returns (address[] memory) {
return layout().currencies.values();
}
}
80 changes: 80 additions & 0 deletions contracts/src/spaces/facets/tipping/TippingFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

// interfaces
import {ITipping} from "./ITipping.sol";

// libraries
import {TippingBase} from "./TippingBase.sol";
import {CustomRevert} from "contracts/src/utils/libraries/CustomRevert.sol";

// contracts
import {ERC721ABase} from "contracts/src/diamond/facets/token/ERC721A/ERC721ABase.sol";
import {Facet} from "contracts/src/diamond/facets/Facet.sol";

contract TippingFacet is ITipping, ERC721ABase, Facet {
function __Tipping_init() external onlyInitializing {
_addInterface(type(ITipping).interfaceId);
}

/// @inheritdoc ITipping
function tip(TipRequest calldata tipRequest) external payable {
address receiver = _ownerOf(tipRequest.tokenId);

_validateTipRequest(
msg.sender,
receiver,
tipRequest.currency,
tipRequest.amount
);

TippingBase.tip(
msg.sender,
receiver,
tipRequest.tokenId,
tipRequest.currency,
tipRequest.amount
);

emit Tip(
tipRequest.tokenId,
tipRequest.currency,
msg.sender,
receiver,
tipRequest.amount
);

emit TipMessage(tipRequest.messageId, tipRequest.channelId);
}

/// @inheritdoc ITipping
function tippingCurrencies() external view returns (address[] memory) {
return TippingBase.tippingCurrencies();
}

/// @inheritdoc ITipping
function tipsByCurrencyAndTokenId(
uint256 tokenId,
address currency
) external view returns (uint256) {
return TippingBase.tipsByCurrencyByTokenId(tokenId, currency);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Internal */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

function _validateTipRequest(
address sender,
address receiver,
address currency,
uint256 amount
) internal view {
if (currency == address(0))
CustomRevert.revertWith(CurrencyIsZero.selector);
if (sender == receiver) CustomRevert.revertWith(CannotTipSelf.selector);
if (amount == 0) CustomRevert.revertWith(AmountIsZero.selector);
if (_balanceOf(sender) == 0)
CustomRevert.revertWith(SenderIsNotMember.selector);
}
}
Loading

0 comments on commit 4028e76

Please sign in to comment.