Skip to content

Commit

Permalink
Retored TokenPack contract and tests to hook it with frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
eguajardo committed May 29, 2021
1 parent 89ea740 commit 0439cb9
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 134 deletions.
110 changes: 61 additions & 49 deletions contracts/TokenPack.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ contract TokenPack is Context, VRFConsumerBase {
* @notice Struct containing the collection details
*/
struct TokenCollection {
string name;
string description;
string ipfsPath;
uint256 price;
uint8 capacity;
uint256[] blueprints;
Expand All @@ -62,35 +61,38 @@ contract TokenPack is Context, VRFConsumerBase {
*/
struct PurchaseOrder {
address buyer;
address packer;
uint16 collectionIndex;
uint256 collectionId;
}

/**
* @dev Mapping of the packer to their current own collections index counter
* Limit maximum collections index per author to 65535 by using uint16
* @dev Mapping from distributor to list of collections IDs
*/
mapping (address => uint16) private _mapPackerToCollectionCounter;
mapping (address => uint256[]) private _distributorCollections;

/**
* @dev Mapping of the addresses that created the collection and their Collections
* @dev Keeps track of total of collections
*/
mapping (address => mapping (uint16 => TokenCollection)) private _mapPackerToCollection;
uint256 private _collectionsCounter;

/**
* @dev Mapping of the requestId for randomness (ie. purchase order id) to the Purchase order
* @dev Mapping storing all TokenCollections
*/
mapping (bytes32 => PurchaseOrder) private _mapRequestIdToPurchaseOrder;
mapping (uint256 => TokenCollection) private _tokenCollections;

/**
* @notice Emitted when the sender 'packer' creates the collection with index 'collectionIndex'
* @dev Mapping from the requestId for randomness (ie. purchase order id) to the Purchase order
*/
event CollectionCreated (address indexed packer, uint16 indexed collectionIndex);
mapping (bytes32 => PurchaseOrder) private _purchaseOrders;

/**
* @notice Emitted when the 'buyer' buys a pack from 'packer' collection with index 'collectionIndex'
* @notice Emitted when the sender 'distributor' creates the collection with index 'collectionIndex'
*/
event PurchaseOrdered (address indexed buyer, address indexed packer, uint16 collectionIndex, bytes32 indexed purchaseOrderId);
event CollectionCreated (address indexed distributor, uint256 indexed collectionId, uint256 distributorCollectionIndex);

/**
* @notice Emitted when the 'buyer' generates a 'purchaseOrderId' for purchasing a pack from the collection with ID 'collectionId'
*/
event PurchaseOrdered (address indexed buyer, uint256 collectionId, bytes32 indexed purchaseOrderId);

/**
* @notice Emitted when the pack bought by 'buyer' in 'purchaseOrderId' was opened
Expand All @@ -107,77 +109,88 @@ contract TokenPack is Context, VRFConsumerBase {

/**
* @notice Creates a new token collection
* @param name The name of the collection
* @param description The description of the collection
* @param ipfsPath The path to the collection metadata
* @param price Cost to buy a pack from this collection
* @param capacity The amount of cards per pack in this collection
* @param blueprints An array of blueprint keys that represents this collection
* @return id of the collection index relative to the collection creator
*/
function createTokenCollection(string calldata name,
string calldata description,
function createTokenCollection(string calldata ipfsPath,
uint256 price,
uint8 capacity,
uint256[] calldata blueprints) external returns (uint16) {
uint256[] calldata blueprints) external returns (uint256) {

require (Utils.isNotEmptyString(name), "ERROR_EMPTY_COLLECTION_NAME");
require (Utils.isNotEmptyString(description), "ERROR_EMPTY_COLLECTION_DESCRIPTION");
require (Utils.isNotEmptyString(ipfsPath), "ERROR_EMPTY_IPFS_PATH");
require (price >= MINIMUM_PACK_PRICE, "ERROR_PRICE_UNDER_LIMIT");
require (capacity >= MINIMUM_PACK_CAPACITY, "ERROR_CAPACITY_UNDER_LIMIT");
require (blueprints.length >= MINIMUM_COLLECTION_BLUEPRINTS, "ERROR_BLUEPRINTS_UNDER_LIMIT");

uint16 currentIndex = _mapPackerToCollectionCounter[_msgSender()];
TokenCollection storage collection = _mapPackerToCollection[_msgSender()][currentIndex];
collection.name = name;
collection.description = description;
uint256 collectionId = _collectionsCounter;
_collectionsCounter += 1;
uint256 distributorCollectionIndex = _distributorCollections[_msgSender()].length;

TokenCollection storage collection = _tokenCollections[collectionId];
collection.ipfsPath = ipfsPath;
collection.price = price;
collection.capacity = capacity;
collection.blueprints = blueprints;

// for (uint256 i; i < blueprints.length; i++) {
// collection.blueprints.push(blueprints[i]);
// }
_distributorCollections[_msgSender()].push(collectionId);

_mapPackerToCollectionCounter[_msgSender()] = currentIndex + 1;

emit CollectionCreated(_msgSender(), currentIndex);
return currentIndex;
emit CollectionCreated(_msgSender(), collectionId, distributorCollectionIndex);
return collectionId;
}

/**
* @notice Buys a pack of Tokens as defined in the collection
* @param packer The address of the packer who created the collection for identification purposes
* @param collectionIndex The index of the packer collection to which the pack being bought belongs
* @param collectionId The collection ID to which the pack belongs
*/
function buyPack(address packer, uint16 collectionIndex) public returns (bytes32) {
function buyPack(uint256 collectionId) public returns (bytes32) {
// TODO: price and token transfer
// TODO: income amount split

TokenCollection storage collection = _mapPackerToCollection[packer][collectionIndex];
require(Utils.isNotEmptyString(collection.name), "ERROR_INVALID_COLLECTION");
require(exist(collectionId), "ERROR_INVALID_COLLECTION_ID");

uint256 seed = uint(keccak256(abi.encodePacked(_msgSender())));
// callback function will be fulfillRandomness (see Chainlink VRF documentation)
bytes32 requestId = _requestRandomTokens(seed);

_mapRequestIdToPurchaseOrder[requestId] = PurchaseOrder(
_msgSender(), packer, collectionIndex
_purchaseOrders[requestId] = PurchaseOrder(
_msgSender(), collectionId
);

emit PurchaseOrdered(_msgSender(), packer, collectionIndex, requestId);
emit PurchaseOrdered(_msgSender(), collectionId, requestId);
return requestId;
}

/**
* @notice Collection URI pointing to it's metadata.
*/
function collectionURI(uint256 collectionId) external view returns (string memory) {
require(exist(collectionId), "ERROR_INVALID_COLLECTION_ID");
return string(abi.encodePacked("ipfs://", _tokenCollections[collectionId].ipfsPath));
}

function exist(uint256 collectionId) public view returns (bool) {
return Utils.isNotEmptyString(_tokenCollections[collectionId].ipfsPath);
}

/**
* @notice Returns the total amount of collections stored by the contract.
*/
function totalBlueprints() external view returns (uint256) {
return _collectionsCounter;
}

/**
* @dev internal function caalled after random number is generated to open the pack bought
* @param randomNumber The random number received from chainlink
* @param purchaseOrderId The purchase order id, ie. the request id for randomness to chainlink
* @param buyer The buyer address
* @param packer The address of the packer who created the collection for identification purposes
* @param collectionIndex The index of the packer collection to which the pack being bought belongs
* @param collectionId The collection ID to which the pack belongs
*/
function _openPack(uint256 randomNumber, bytes32 purchaseOrderId, address buyer, address packer, uint16 collectionIndex) internal {
TokenCollection storage collection = _mapPackerToCollection[packer][collectionIndex];
function _openPack(uint256 randomNumber, bytes32 purchaseOrderId, address buyer, uint256 collectionId) internal {
TokenCollection storage collection = _tokenCollections[collectionId];

for (uint8 i = 0; i < collection.capacity; i++) {
uint256 derivedRandom = uint(keccak256(abi.encodePacked(randomNumber, i)));
Expand Down Expand Up @@ -206,14 +219,13 @@ contract TokenPack is Context, VRFConsumerBase {
* @notice Callback function used by VRF Coordinator
*/
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
require (_mapRequestIdToPurchaseOrder[requestId].buyer != address(0), "ERROR_INVALID_PURCHASE_ORDER");
require (_purchaseOrders[requestId].buyer != address(0), "ERROR_INVALID_PURCHASE_ORDER");

_openPack(
randomness,
requestId,
_mapRequestIdToPurchaseOrder[requestId].buyer,
_mapRequestIdToPurchaseOrder[requestId].packer,
_mapRequestIdToPurchaseOrder[requestId].collectionIndex
_purchaseOrders[requestId].buyer,
_purchaseOrders[requestId].collectionId
);
}
}
48 changes: 30 additions & 18 deletions frontend/src/components/UI/CollectionForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function CollectionForm(props) {
const [buttonState, setButtonState] = useState({
class: "btn btn-primary btn-lg btn-block",
disabled: false,
text: "Submit collection"
text: "Submit collection",
});

const [enteredTitleIsValid, setEnteredNameIsValid] = useState(true);
Expand All @@ -48,22 +48,28 @@ function CollectionForm(props) {
setButtonState({
class: "btn btn-success btn-lg btn-block",
disabled: true,
text: "Success!"
text: "Success!",
});

nameInputRef.current.value = "";
descriptionInputRef.current.value = "";
priceInputRef.current.value = 0.0;
capacityInputRef.current.value = 0;
fileInputRef.current.value = "";
} else if (
ethTxState.status === "Exception" ||
ethTxState.status === "Fail"
) {
setButtonState({
class: "btn btn-danger btn-lg btn-block",
disabled: true,
text: "Failure"
class: "btn btn-primary btn-lg btn-block",
disabled: false,
text: "Submit collection",
});
} else if (ethTxState.status === "Mining") {
setButtonState({
class: "btn btn-primary btn-lg btn-block",
disabled: true,
text: "Processing..."
text: "Processing...",
});
}
}, [ethTxState]);
Expand Down Expand Up @@ -117,7 +123,7 @@ function CollectionForm(props) {
setButtonState({
class: "btn btn-primary btn-lg btn-block",
disabled: true,
text: "Processing..."
text: "Processing...",
});

const imageIpfsPath = await uploadFileToIPFS(enteredFile);
Expand All @@ -129,13 +135,12 @@ function CollectionForm(props) {
};

const metadataIpfsPath = await uploadJsonToIPFS(metadata);
//sendCreateCollection(metadataIpfsPath);

nameInputRef.current.value = "";
descriptionInputRef.current.value = "";
priceInputRef.current.value = 0.0;
capacityInputRef.current.value = 0;
fileInputRef.current.value = "";
sendCreateCollection(
metadataIpfsPath,
utils.parseEther(enteredPrice),
enteredCapacity,
props.selectedBlueprints
);
};

return (
Expand Down Expand Up @@ -186,9 +191,7 @@ function CollectionForm(props) {
step="0.01"
id="price"
className={
enteredPriceIsValid
? "form-control"
: "form-control is-invalid"
enteredPriceIsValid ? "form-control" : "form-control is-invalid"
}
/>
<div className="invalid-feedback">Price must be grater than 0</div>
Expand All @@ -208,7 +211,9 @@ function CollectionForm(props) {
: "form-control is-invalid"
}
/>
<div className="invalid-feedback">Cards per booster must be grater than 0</div>
<div className="invalid-feedback">
Cards per booster must be grater than 0
</div>
</div>

<div className="form-group">
Expand All @@ -229,6 +234,13 @@ function CollectionForm(props) {
</div>

<div id="actions" className="mt-4">
{(ethTxState.status === "Exception" ||
ethTxState.status === "Fail") && (
<div className="alert alert-danger">
<strong>Error executing transaction</strong>
<p>{ethTxState.errorMessage}</p>
</div>
)}
<button
name="submit"
className={buttonState.class}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/NFTIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function NFTIndex() {
<div className="col-4">
<div className="mb-5"></div>
<div className="content-container">
<CollectionForm closeCollectionForm={closeCollectionForm} />
<CollectionForm selectedBlueprints={selectedBlueprints} closeCollectionForm={closeCollectionForm} />
</div>
</div>
)}
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/pages/NFTNew.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ function NFTNew() {
ethTxState.status === "Fail"
) {
setButtonState({
class: "btn btn-danger btn-lg btn-block",
disabled: true,
text: "Failure",
class: "btn btn-primary btn-lg btn-block",
disabled: false,
text: "Submit NFT blueprint",
status: ethTxState.status,
});
} else if (ethTxState.status === "Mining") {
Expand Down Expand Up @@ -170,6 +170,13 @@ function NFTNew() {
</div>

<div id="actions" className="mt-4">
{(ethTxState.status === "Exception" ||
ethTxState.status === "Fail") && (
<div className="alert alert-danger">
<strong>Error executing transaction</strong>
<p>{ethTxState.errorMessage}</p>
</div>
)}
<button
name="submit"
className={buttonState.class}
Expand Down
Loading

0 comments on commit 0439cb9

Please sign in to comment.