Skip to content

Commit

Permalink
Merge branch 'SOLID-133-erc-1155-offers-buy-orders'
Browse files Browse the repository at this point in the history
  • Loading branch information
cinnabarhorse committed Oct 8, 2024
2 parents 9f7b58e + f51c1b5 commit 1185928
Show file tree
Hide file tree
Showing 7 changed files with 885 additions and 17 deletions.
192 changes: 192 additions & 0 deletions contracts/Aavegotchi/facets/ERC1155BuyOrderFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.1;

import {LibBuyOrder} from "../libraries/LibBuyOrder.sol";
import {LibERC1155Marketplace} from "../libraries/LibERC1155Marketplace.sol";
import {LibERC20} from "../../shared/libraries/LibERC20.sol";
import {LibERC1155} from "../../shared/libraries/LibERC1155.sol";
import {LibItems} from "../libraries/LibItems.sol";
import {IERC20} from "../../shared/interfaces/IERC20.sol";
import {IERC1155} from "../../shared/interfaces/IERC1155.sol";
import {LibMeta} from "../../shared/libraries/LibMeta.sol";
import {Modifiers, ERC1155BuyOrder} from "../libraries/LibAppStorage.sol";
import {BaazaarSplit, LibSharedMarketplace, SplitAddresses} from "../libraries/LibSharedMarketplace.sol";
import "../WearableDiamond/interfaces/IEventHandlerFacet.sol";

contract ERC1155BuyOrderFacet is Modifiers {
event ERC1155BuyOrderAdd(
uint256 indexed buyOrderId,
address indexed buyer,
address erc1155TokenAddress,
uint256 erc1155TokenId,
uint256 indexed category,
uint256 priceInWei,
uint256 quantity,
uint256 duration,
uint256 time
);
event ERC1155BuyOrderCancel(uint256 indexed buyOrderId, uint256 time);
event ERC1155BuyOrderExecute(
uint256 indexed buyOrderId,
address indexed buyer,
address seller,
address erc1155TokenAddress,
uint256 erc1155TokenId,
uint256 indexed category,
uint256 priceInWei,
uint256 quantity,
uint256 time
);

function placeERC1155BuyOrder(
address _erc1155TokenAddress,
uint256 _erc1155TokenId,
uint256 _priceInWei,
uint256 _quantity,
uint256 _duration
) external {
uint256 cost = _quantity * _priceInWei;
require(cost >= 1e15, "ERC1155BuyOrder: cost should be 0.001 GHST or larger");

address sender = LibMeta.msgSender();
uint256 category = LibSharedMarketplace.getERC1155Category(_erc1155TokenAddress, _erc1155TokenId);
uint256 ghstBalance = IERC20(s.ghstContract).balanceOf(sender);

// New order
// Transfer GHST
require(ghstBalance >= cost, "ERC1155BuyOrder: Not enough GHST!");
LibERC20.transferFrom(s.ghstContract, sender, address(this), cost);

// Place new buy order
s.nextERC1155BuyOrderId++;
uint256 buyOrderId = s.nextERC1155BuyOrderId;

s.erc1155BuyOrders[buyOrderId] = ERC1155BuyOrder({
buyOrderId: buyOrderId,
buyer: sender,
erc1155TokenAddress: _erc1155TokenAddress,
erc1155TokenId: _erc1155TokenId,
priceInWei: _priceInWei,
quantity: _quantity,
timeCreated: block.timestamp,
lastTimePurchased: 0,
duration: _duration,
completed: false,
cancelled: false
});
emit ERC1155BuyOrderAdd(
buyOrderId,
sender,
_erc1155TokenAddress,
_erc1155TokenId,
category,
_priceInWei,
_quantity,
_duration,
block.timestamp
);
}

function cancelERC1155BuyOrder(uint256 _buyOrderId) external {
address sender = LibMeta.msgSender();
ERC1155BuyOrder memory erc1155BuyOrder = s.erc1155BuyOrders[_buyOrderId];
require(erc1155BuyOrder.timeCreated != 0, "ERC1155BuyOrder: ERC1155 buyOrder does not exist");
require((erc1155BuyOrder.cancelled == false) && (erc1155BuyOrder.completed == false), "ERC1155BuyOrder: Already processed");

//Anyone can cancel an expired order
if (
(erc1155BuyOrder.duration == 0) ||
((erc1155BuyOrder.duration > 0) && (erc1155BuyOrder.timeCreated + erc1155BuyOrder.duration > block.timestamp))
) {
require(sender == erc1155BuyOrder.buyer, "ERC1155BuyOrder: Only buyer can call this function");
}

LibBuyOrder.cancelERC1155BuyOrder(_buyOrderId);
emit ERC1155BuyOrderCancel(_buyOrderId, block.timestamp);
}

function executeERC1155BuyOrder(
uint256 _buyOrderId,
address _erc1155TokenAddress,
uint256 _erc1155TokenId,
uint256 _priceInWei,
uint256 _quantity
) external {
address sender = LibMeta.msgSender();
ERC1155BuyOrder storage erc1155BuyOrder = s.erc1155BuyOrders[_buyOrderId];

require(erc1155BuyOrder.timeCreated != 0, "ERC1155BuyOrder: ERC1155 buyOrder does not exist");
require(erc1155BuyOrder.erc1155TokenAddress == _erc1155TokenAddress, "ERC1155BuyOrder: ERC1155 token address not matched");
require(erc1155BuyOrder.erc1155TokenId == _erc1155TokenId, "ERC1155BuyOrder: ERC1155 token id not matched");
require(erc1155BuyOrder.priceInWei == _priceInWei, "ERC1155BuyOrder: Price not matched");
require(erc1155BuyOrder.buyer != sender, "ERC1155BuyOrder: Buyer can't be seller");
require((erc1155BuyOrder.cancelled == false) && (erc1155BuyOrder.completed == false), "ERC1155BuyOrder: Already processed");
if (erc1155BuyOrder.duration > 0) {
require(erc1155BuyOrder.timeCreated + erc1155BuyOrder.duration >= block.timestamp, "ERC1155BuyOrder: Already expired");
}
require(erc1155BuyOrder.quantity >= _quantity, "ERC1155BuyOrder: Sell amount should not be larger than quantity of the buy order");

uint256 cost = _quantity * erc1155BuyOrder.priceInWei;
require(cost >= 1e15, "ERC1155BuyOrder: execution cost should be 0.001 GHST or larger");

IERC1155 erc1155Token = IERC1155(erc1155BuyOrder.erc1155TokenAddress);
require(erc1155Token.balanceOf(sender, erc1155BuyOrder.erc1155TokenId) >= _quantity, "ERC1155Marketplace: Not enough ERC1155 token");

erc1155BuyOrder.quantity -= _quantity;
erc1155BuyOrder.lastTimePurchased = block.timestamp;

BaazaarSplit memory split = LibSharedMarketplace.getBaazaarSplit(cost, new uint256[](0), [10000, 0]);
LibSharedMarketplace.transferSales(
SplitAddresses({
ghstContract: s.ghstContract,
buyer: address(this),
seller: sender,
affiliate: address(0),
royalties: new address[](0),
daoTreasury: s.daoTreasury,
pixelCraft: s.pixelCraft,
rarityFarming: s.rarityFarming
}),
split
);

if (erc1155BuyOrder.quantity == 0) {
erc1155BuyOrder.completed = true;
}

// ERC1155 transfer
if (erc1155BuyOrder.erc1155TokenAddress == address(this)) {
LibItems.removeFromOwner(sender, erc1155BuyOrder.erc1155TokenId, _quantity);
LibItems.addToOwner(erc1155BuyOrder.buyer, erc1155BuyOrder.erc1155TokenId, _quantity);
IEventHandlerFacet(s.wearableDiamond).emitTransferSingleEvent(
address(this),
sender,
erc1155BuyOrder.buyer,
erc1155BuyOrder.erc1155TokenId,
_quantity
);
LibERC1155.onERC1155Received(address(this), sender, erc1155BuyOrder.buyer, erc1155BuyOrder.erc1155TokenId, _quantity, "");
} else {
IERC1155(erc1155BuyOrder.erc1155TokenAddress).safeTransferFrom(
sender,
erc1155BuyOrder.buyer,
erc1155BuyOrder.erc1155TokenId,
_quantity,
new bytes(0)
);
}
LibERC1155Marketplace.updateERC1155Listing(erc1155BuyOrder.erc1155TokenAddress, erc1155BuyOrder.erc1155TokenId, sender);

emit ERC1155BuyOrderExecute(
_buyOrderId,
erc1155BuyOrder.buyer,
sender,
erc1155BuyOrder.erc1155TokenAddress,
erc1155BuyOrder.erc1155TokenId,
LibSharedMarketplace.getERC1155Category(erc1155BuyOrder.erc1155TokenAddress, erc1155BuyOrder.erc1155TokenId),
erc1155BuyOrder.priceInWei,
_quantity,
block.timestamp
);
}
}
18 changes: 2 additions & 16 deletions contracts/Aavegotchi/facets/ERC1155MarketplaceFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,7 @@ contract ERC1155MarketplaceFacet is Modifiers {
///@param _erc1155TypeId Identifier of the NFT to query
///@return category_ Category of the NFT // 0 is wearable, 1 is badge, 2 is consumable, 3 is tickets
function getERC1155Category(address _erc1155TokenAddress, uint256 _erc1155TypeId) public view returns (uint256 category_) {
if (_erc1155TokenAddress == s.forgeDiamond && _erc1155TypeId < 1_000_000_000) {
//Schematics are always supported to trade, so long as the wearable exists
//Schematic IDs are under 1_000_000_000 offset.
category_ = 7;
require(s.itemTypes[_erc1155TypeId].maxQuantity > 0, "ERC1155Marketplace: erc1155 item not supported");
} else {
category_ = s.erc1155Categories[_erc1155TokenAddress][_erc1155TypeId];
}

if (category_ == 0) {
require(
_erc1155TokenAddress == address(this) && s.itemTypes[_erc1155TypeId].maxQuantity > 0,
"ERC1155Marketplace: erc1155 item not supported"
);
}
category_ = LibSharedMarketplace.getERC1155Category(_erc1155TokenAddress, _erc1155TypeId);
}

///@notice Allow an ERC1155 owner to list his NFTs for sale
Expand Down Expand Up @@ -131,7 +117,7 @@ contract ERC1155MarketplaceFacet is Modifiers {
uint32 _whitelistId
) internal {
address seller = LibMeta.msgSender();
uint256 category = getERC1155Category(_erc1155TokenAddress, _erc1155TypeId);
uint256 category = LibSharedMarketplace.getERC1155Category(_erc1155TokenAddress, _erc1155TypeId);

IERC1155 erc1155Token = IERC1155(_erc1155TokenAddress);
require(erc1155Token.balanceOf(seller, _erc1155TypeId) >= _quantity, "ERC1155Marketplace: Not enough ERC1155 token");
Expand Down
17 changes: 17 additions & 0 deletions contracts/Aavegotchi/libraries/LibAppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,20 @@ struct ItemRolesInfo {
uint256 balanceUsed;
}

struct ERC1155BuyOrder {
uint256 buyOrderId;
address buyer;
address erc1155TokenAddress;
uint256 erc1155TokenId;
uint256 priceInWei;
uint256 quantity;
uint256 timeCreated;
uint256 lastTimePurchased;
uint256 duration; //0 for unlimited
bool cancelled;
bool completed;
}

struct AppStorage {
mapping(address => AavegotchiCollateralTypeInfo) collateralTypeInfo;
mapping(address => uint256) collateralTypeIndexes;
Expand Down Expand Up @@ -366,6 +380,9 @@ struct AppStorage {
// Auxiliary structs for Items Roles Registry
// gotchiId => equippedDepositsInfo
mapping(uint256 => GotchiEquippedDepositsInfo) gotchiEquippedDepositsInfo;
// states for erc1155 buy orders
uint256 nextERC1155BuyOrderId;
mapping(uint256 => ERC1155BuyOrder) erc1155BuyOrders; // buyOrderId => data
}

library LibAppStorage {
Expand Down
19 changes: 18 additions & 1 deletion contracts/Aavegotchi/libraries/LibBuyOrder.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.1;

import {LibAppStorage, AppStorage, ERC721BuyOrder} from "./LibAppStorage.sol";
import {LibAppStorage, AppStorage, ERC721BuyOrder, ERC1155BuyOrder} from "./LibAppStorage.sol";
import {LibERC20} from "../../shared/libraries/LibERC20.sol";
import {LibAavegotchi} from "./LibAavegotchi.sol";
import {LibSharedMarketplace} from "./LibSharedMarketplace.sol";
Expand Down Expand Up @@ -74,4 +74,21 @@ library LibBuyOrder {
}
return keccak256(_params);
}

function cancelERC1155BuyOrder(uint256 _buyOrderId) internal {
AppStorage storage s = LibAppStorage.diamondStorage();

ERC1155BuyOrder memory erc1155BuyOrder = s.erc1155BuyOrders[_buyOrderId];
if (erc1155BuyOrder.timeCreated == 0) {
return;
}
if ((erc1155BuyOrder.cancelled == true) || (erc1155BuyOrder.completed == true)) {
return;
}

s.erc1155BuyOrders[_buyOrderId].cancelled = true;

// refund GHST to buyer
LibERC20.transfer(s.ghstContract, erc1155BuyOrder.buyer, erc1155BuyOrder.priceInWei * erc1155BuyOrder.quantity);
}
}
23 changes: 23 additions & 0 deletions contracts/Aavegotchi/libraries/LibSharedMarketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,27 @@ library LibSharedMarketplace {
category_ = s.aavegotchis[_erc721TokenId].status; // 0 == portal, 1 == vrf pending, 2 == open portal, 3 == Aavegotchi
}
}

///@notice Query the category details of a ERC1155 NFT
///@param _erc1155TokenAddress Contract address of NFT to query
///@param _erc1155TypeId Identifier of the NFT to query
///@return category_ Category of the NFT // 0 is wearable, 1 is badge, 2 is consumable, 3 is tickets
function getERC1155Category(address _erc1155TokenAddress, uint256 _erc1155TypeId) internal view returns (uint256 category_) {
AppStorage storage s = LibAppStorage.diamondStorage();
if (_erc1155TokenAddress == s.forgeDiamond && _erc1155TypeId < 1_000_000_000) {
//Schematics are always supported to trade, so long as the wearable exists
//Schematic IDs are under 1_000_000_000 offset.
category_ = 7;
require(s.itemTypes[_erc1155TypeId].maxQuantity > 0, "ERC1155Marketplace: erc1155 item not supported");
} else {
category_ = s.erc1155Categories[_erc1155TokenAddress][_erc1155TypeId];
}

if (category_ == 0) {
require(
_erc1155TokenAddress == address(this) && s.itemTypes[_erc1155TypeId].maxQuantity > 0,
"ERC1155Marketplace: erc1155 item not supported"
);
}
}
}
49 changes: 49 additions & 0 deletions scripts/upgrades/upgrade-erc1155BuyOrderFacet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ethers, run } from "hardhat";
import {
convertFacetAndSelectorsToString,
DeployUpgradeTaskArgs,
FacetsAndAddSelectors,
} from "../../tasks/deployUpgrade";
import { maticDiamondAddress, maticDiamondUpgrader } from "../helperFunctions";

export async function upgrade() {

const facets: FacetsAndAddSelectors[] = [
{
facetName: "ERC1155BuyOrderFacet",
addSelectors: [
"function placeERC1155BuyOrder(address _erc1155TokenAddress, uint256 _erc1155TokenId, uint256 _priceInWei, uint256 _quantity, uint256 _duration) external",
"function executeERC1155BuyOrder(uint256 _buyOrderId, address _erc1155TokenAddress, uint256 _erc1155TokenId, uint256 _priceInWei, uint256 _quantity) external",
"function cancelERC1155BuyOrder(uint256 _buyOrderId) external",
],
removeSelectors: [],
},
{
facetName: "ERC1155MarketplaceFacet",
addSelectors: [],
removeSelectors: [],
},
];

const joined = convertFacetAndSelectorsToString(facets);

const args: DeployUpgradeTaskArgs = {
diamondOwner: maticDiamondUpgrader,
diamondAddress: maticDiamondAddress,
facetsAndAddSelectors: joined,
useLedger: true,
useMultisig: true,
};

await run("deployUpgrade", args);
}

if (require.main === module) {
upgrade()
.then(() => process.exit(0))
// .then(() => console.log('upgrade completed') /* process.exit(0) */)
.catch((error) => {
console.error(error);
process.exit(1);
});
}
Loading

0 comments on commit 1185928

Please sign in to comment.