Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip fee for TSB seller #1186

Merged
merged 8 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions packages/marketplace/contracts/exchange/Exchange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
pragma solidity 0.8.21;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import {ERC2771HandlerUpgradeable} from "@sandbox-smart-contracts/dependency-metatx/contracts/ERC2771HandlerUpgradeable.sol";
import {IOrderValidator} from "../interfaces/IOrderValidator.sol";
import {IAssetMatcher} from "../interfaces/IAssetMatcher.sol";
Expand All @@ -17,7 +15,7 @@ import {ExchangeCore} from "./ExchangeCore.sol";
/// @notice Used to exchange assets, that is, tokens.
/// @dev Main functions are in ExchangeCore
/// @dev TransferManager is used to execute token transfers
contract Exchange is Initializable, AccessControlUpgradeable, ExchangeCore, TransferManager, ERC2771HandlerUpgradeable {
contract Exchange is Initializable, ExchangeCore, TransferManager, ERC2771HandlerUpgradeable {
/// @notice role erc1776 trusted meta transaction contracts (Sand for example).
/// @return hash for ERC1776_OPERATOR_ROLE
bytes32 public constant ERC1776_OPERATOR_ROLE = keccak256("ERC1776_OPERATOR_ROLE");
Expand Down Expand Up @@ -53,7 +51,6 @@ contract Exchange is Initializable, AccessControlUpgradeable, ExchangeCore, Tran
IAssetMatcher newAssetMatcher
) external initializer {
__ERC2771Handler_init(newTrustedForwarder);
__AccessControl_init();
__TransferManager_init_unchained(
newProtocolFeePrimary,
newProtocolFeeSecondary,
Expand Down Expand Up @@ -128,16 +125,6 @@ contract Exchange is Initializable, AccessControlUpgradeable, ExchangeCore, Tran
_setDefaultFeeReceiver(newDefaultFeeReceiver);
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC165Upgradeable, AccessControlUpgradeable) returns (bool) {
return
ERC165Upgradeable.supportsInterface(interfaceId) || AccessControlUpgradeable.supportsInterface(interfaceId);
}

function _msgSender()
internal
view
Expand Down
97 changes: 61 additions & 36 deletions packages/marketplace/contracts/transfer-manager/TransferManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity 0.8.21;

import {ERC165Upgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {IRoyaltiesProvider} from "../interfaces/IRoyaltiesProvider.sol";
import {BpLibrary} from "../lib-bp/BpLibrary.sol";
import {IRoyaltyUGC} from "./interfaces/IRoyaltyUGC.sol";
Expand All @@ -14,11 +15,15 @@ import {LibPart} from "../lib-part/LibPart.sol";
/// @notice responsible for transferring all Assets
/// @dev this manager supports different types of fees
/// @dev also it supports different beneficiaries
abstract contract TransferManager is ERC165Upgradeable, ITransferManager {
abstract contract TransferManager is ERC165Upgradeable, AccessControlUpgradeable, ITransferManager {
mvanmeerbeck marked this conversation as resolved.
Show resolved Hide resolved
using BpLibrary for uint;

bytes4 internal constant INTERFACE_ID_IROYALTYUGC = 0xa30b4db9;

/// @notice role to identify the sandbox accounts
/// @return hash for TSB_WALLET
bytes32 public constant TSB_WALLET = keccak256("TSB_WALLET");
mvanmeerbeck marked this conversation as resolved.
Show resolved Hide resolved

/// @notice fee for primary sales
/// @return uint256 of primary sale fee
uint256 public protocolFeePrimary;
Expand Down Expand Up @@ -61,6 +66,8 @@ abstract contract TransferManager is ERC165Upgradeable, ITransferManager {
IRoyaltiesProvider newRoyaltiesProvider
) internal onlyInitializing {
__ERC165_init();
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setProtocolFee(newProtocolFeePrimary, newProtocolFeeSecondary);
_setRoyaltiesRegistry(newRoyaltiesProvider);
_setDefaultFeeReceiver(newDefaultFeeReceiver);
Expand Down Expand Up @@ -122,44 +129,52 @@ abstract contract TransferManager is ERC165Upgradeable, ITransferManager {
function doTransfersWithFees(LibDeal.DealSide memory paymentSide, LibDeal.DealSide memory nftSide) internal {
uint256 rest = paymentSide.asset.value;

rest = transferRoyalties(
paymentSide.asset.assetType,
nftSide.asset.assetType,
nftSide.payouts,
rest,
paymentSide.asset.value,
paymentSide.from
);

LibPart.Part[] memory origin = new LibPart.Part[](1);
origin[0].account = payable(defaultFeeReceiver);

bool primaryMarket = false;

// check if primary or secondary market
if (
nftSide.asset.assetType.assetClass == LibAsset.AssetClassType.ERC1155_ASSET_CLASS ||
nftSide.asset.assetType.assetClass == LibAsset.AssetClassType.ERC721_ASSET_CLASS
) {
(address token, uint256 tokenId) = abi.decode(nftSide.asset.assetType.data, (address, uint));
try IERC165Upgradeable(token).supportsInterface(INTERFACE_ID_IROYALTYUGC) returns (bool result) {
if (result) {
address creator = IRoyaltyUGC(token).getCreatorAddress(tokenId);
if (nftSide.from == creator) {
primaryMarket = true;
if (!hasRole(TSB_WALLET, paymentSide.from)) {
rest = transferRoyalties(
paymentSide.asset.assetType,
nftSide.asset.assetType,
nftSide.payouts,
rest,
paymentSide.asset.value,
paymentSide.from
);

LibPart.Part[] memory origin = new LibPart.Part[](1);
origin[0].account = payable(defaultFeeReceiver);

bool primaryMarket = false;

// check if primary or secondary market
if (
nftSide.asset.assetType.assetClass == LibAsset.AssetClassType.ERC1155_ASSET_CLASS ||
nftSide.asset.assetType.assetClass == LibAsset.AssetClassType.ERC721_ASSET_CLASS
) {
(address token, uint256 tokenId) = abi.decode(nftSide.asset.assetType.data, (address, uint));
try IERC165Upgradeable(token).supportsInterface(INTERFACE_ID_IROYALTYUGC) returns (bool result) {
if (result) {
address creator = IRoyaltyUGC(token).getCreatorAddress(tokenId);
if (nftSide.from == creator) {
primaryMarket = true;
}
}
}
// solhint-disable-next-line no-empty-blocks
} catch {}
}
// solhint-disable-next-line no-empty-blocks
} catch {}
}

if (primaryMarket) {
origin[0].value = uint96(protocolFeePrimary);
} else {
origin[0].value = uint96(protocolFeeSecondary);
}
if (primaryMarket) {
origin[0].value = uint96(protocolFeePrimary);
} else {
origin[0].value = uint96(protocolFeeSecondary);
}

(rest, ) = transferFees(paymentSide.asset.assetType, rest, paymentSide.asset.value, origin, paymentSide.from);
(rest, ) = transferFees(
paymentSide.asset.assetType,
rest,
paymentSide.asset.value,
origin,
paymentSide.from
);
}

transferPayouts(paymentSide.asset.assetType, rest, paymentSide.from, nftSide.payouts);
}
Expand Down Expand Up @@ -308,5 +323,15 @@ abstract contract TransferManager is ERC165Upgradeable, ITransferManager {
}
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC165Upgradeable, AccessControlUpgradeable) returns (bool) {
return
ERC165Upgradeable.supportsInterface(interfaceId) || AccessControlUpgradeable.supportsInterface(interfaceId);
}

uint256[46] private __gap;
}
116 changes: 113 additions & 3 deletions packages/marketplace/test/exchange/Exchange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ describe('Exchange.sol', function () {
);
expect(await ERC721Contract.ownerOf(1)).to.be.equal(taker.address);

// check primary market protocol fee
// check protocol fee
expect(
await ERC20Contract.balanceOf(defaultFeeReceiver.address)
).to.be.equal(2500000000); // 250 * 10000000000 / 10000 = 250000000
Expand Down Expand Up @@ -718,7 +718,7 @@ describe('Exchange.sol', function () {
);
expect(await ERC721Contract.ownerOf(1)).to.be.equal(taker.address);

// check primary market protocol fee
// check protocol fee
expect(
await ERC20Contract.balanceOf(defaultFeeReceiver.address)
).to.be.equal(2500000000); // 250 * 10000000000 / 10000 = 250000000
Expand Down Expand Up @@ -825,7 +825,7 @@ describe('Exchange.sol', function () {
);
expect(await ERC721WithRoyaltyV2981.ownerOf(1)).to.be.equal(taker.address);

// check primary market protocol fee
// check protocol fee
expect(
await ERC20Contract.balanceOf(defaultFeeReceiver.address)
).to.be.equal(2500000000); // 250 * 10000000000 / 10000 = 250000000
Expand All @@ -840,6 +840,116 @@ describe('Exchange.sol', function () {
);
});

it('should execute a complete match order without fee and royalties for TSB seller', async function () {
const {
ExchangeContractAsUser,
ExchangeContractAsDeployer,
OrderValidatorAsDeployer,
ERC20Contract,
ERC721WithRoyaltyV2981,
defaultFeeReceiver,
deployer,
user1: maker,
user2: taker,
} = await loadFixture(deployFixtures);

await ERC721WithRoyaltyV2981.mint(maker.address, 1, [
await FeeRecipientsData(maker.address, 10000),
]);

await ERC721WithRoyaltyV2981.connect(maker).approve(
await ExchangeContractAsUser.getAddress(),
1
);

// set up receiver
await ERC721WithRoyaltyV2981.setRoyaltiesReceiver(1, deployer.address);

// grant TSB Wallet role to seller
const TSB_WALLET =
'0x1c3ffa8a78d1cdbeb9812a2ae7c540d20292531d0254f9ac1fa85e0ac44b9ad0'; // keccak256("TSB_WALLET")
await ExchangeContractAsDeployer.connect(deployer).grantRole(
TSB_WALLET,
taker.address
);

await ERC20Contract.mint(taker.address, 100000000000);
await ERC20Contract.connect(taker).approve(
await ExchangeContractAsUser.getAddress(),
100000000000
);

expect(await ERC721WithRoyaltyV2981.ownerOf(1)).to.be.equal(maker.address);
expect(await ERC20Contract.balanceOf(taker.address)).to.be.equal(
100000000000
);
const makerAsset = await AssetERC721(ERC721WithRoyaltyV2981, 1);
const takerAsset = await AssetERC20(ERC20Contract, 100000000000);
const orderLeft = await OrderDefault(
maker,
makerAsset,
ZeroAddress,
takerAsset,
1,
0,
0
);
const orderRight = await OrderDefault(
taker,
takerAsset,
ZeroAddress,
makerAsset,
1,
0,
0
);
const makerSig = await signOrder(
orderLeft,
maker,
OrderValidatorAsDeployer
);
const takerSig = await signOrder(
orderRight,
taker,
OrderValidatorAsDeployer
);

expect(await ExchangeContractAsUser.fills(hashKey(orderLeft))).to.be.equal(
0
);
expect(await ExchangeContractAsUser.fills(hashKey(orderRight))).to.be.equal(
0
);

await ExchangeContractAsUser.matchOrders([
{
orderLeft,
signatureLeft: makerSig,
orderRight,
signatureRight: takerSig,
},
]);
expect(await ExchangeContractAsUser.fills(hashKey(orderLeft))).to.be.equal(
100000000000
);
expect(await ExchangeContractAsUser.fills(hashKey(orderRight))).to.be.equal(
1
);
expect(await ERC721WithRoyaltyV2981.ownerOf(1)).to.be.equal(taker.address);

// no protocol fee paid
expect(
await ERC20Contract.balanceOf(defaultFeeReceiver.address)
).to.be.equal(0);

// no royalties paid
expect(await ERC20Contract.balanceOf(deployer.address)).to.be.equal(0);

expect(await ERC20Contract.balanceOf(maker.address)).to.be.equal(
100000000000
);
});

it('should execute a complete match order between ERC721 and ERC20 tokens', async function () {
const {
ExchangeContractAsUser,
Expand Down
Loading