Skip to content

Commit

Permalink
erc2309 collection edition implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
cblanquera committed Aug 2, 2021
1 parent 61e7245 commit dca8ad3
Show file tree
Hide file tree
Showing 30 changed files with 2,614 additions and 29 deletions.
582 changes: 582 additions & 0 deletions contracts/token/ERC2309/ERC2309.sol

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions contracts/token/ERC2309/IERC2309.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

//ERC721 interface
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/**
* @dev Required interface of an ERC2309 compliant contract.
*/
interface IERC2309 is IERC721 {
/**
* @dev Emitted when a batch of tokens is transferred from `fromAddress` to `toAddress`.
*/
event ConsecutiveTransfer(
uint256 indexed fromTokenId,
uint256 toTokenId,
address indexed fromAddress,
address indexed toAddress
);
}
3 changes: 3 additions & 0 deletions contracts/token/ERC2309/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ERC2309 Collection Edition

EIP2309 extends 721 version that considers the management of multiple collections of NFTs.
28 changes: 28 additions & 0 deletions contracts/token/ERC2309/extensions/ERC2309Burnable.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 "./../ERC2309.sol";
import "@openzeppelin/contracts/utils/Context.sol";

/**
* @title ERC2309 Burnable Token
* @dev ERC2309 Token that can be irreversibly burned (destroyed).
*/
abstract contract ERC2309Burnable is Context, ERC2309 {
/**
* @dev Burns `tokenId`. See {ERC721-_burn}.
*
* Requirements:
*
* - The caller must own `tokenId` or be an approved operator.
*/
function burn(uint256 tokenId) public virtual {
//solhint-disable-next-line max-line-length
require(
_isApprovedOrOwner(_msgSender(), tokenId),
"ERC2309Burnable: caller is not owner nor approved"
);
_burn(tokenId);
}
}
181 changes: 181 additions & 0 deletions contracts/token/ERC2309/extensions/ERC2309Collection.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

//A simple way to get a counter that can only be incremented or decremented.
//Very useful for ID generation, counting contract activity, among others
//usage: Counters.Counter private ids
// ids.increment()
// ids.current()
//see: https://docs.openzeppelin.com/contracts/3.x/api/utils#Counters
import "@openzeppelin/contracts/utils/Counters.sol";

//implementation of ERC2309
import "./../ERC2309.sol";

abstract contract ERC2309Collection is ERC2309 {
using Counters for Counters.Counter;
Counters.Counter public LastCollectionId;

//ex. precision 4 is 0.0000
//10000001 = 1000.0001 vs 10000001 = 10.000001
uint256 immutable internal _collectionPrecision;
//collection id -> uri
mapping(uint256 => string) private _collectionURIs;
//collection id -> last token id
mapping(uint256 => uint256) private _collectionLast;
//collection id -> allowance
mapping(uint256 => uint256) private _collectionAllowance;

/**
* @dev Constructor function
*/
constructor (uint256 precision) {
_collectionPrecision = precision;
}

/**
* @dev Defines a collection
*/
function _createCollection(string memory uri, uint256 allowance)
internal
returns(uint256)
{
//allowance cannot be more than the precision allowance
//to get the precision allowance of precision 4 is
//(10 ** 4) - 1 = 10000 - 1 = 9999
require(
allowance <= ((10 ** _collectionPrecision) - 1),
"Allowance more than the max allowance"
);

//get the collection id
LastCollectionId.increment();
uint256 collectionId = LastCollectionId.current();

//set the allowance
_collectionAllowance[collectionId] = allowance;
//set the collection uri
_collectionURIs[collectionId] = uri;

return collectionId;
}

/**
* @dev Collection minting factory
*/
function _mintCollection(uint256 collectionId, address recipient)
internal
returns(uint256)
{
require(
_collectionAllowance[collectionId] > 0,
"ERC2309Collection: Collection does not exist"
);

//calculate the zero id
//to get the zero id of precision 4 of collection id 22
//(10 ** 4) * 22 = (10000 * 22) = 220000
uint256 zeroId = (10 ** _collectionPrecision) * collectionId;
//determine last id
uint256 lastId = _collectionLast[collectionId];
//if no last
if (lastId == 0) {
//make it the zero id
lastId = zeroId;
}

//determine the token id
uint256 tokenId = lastId + 1;
//determine the allowance
//if zero id is 220000 and allowance is 1000 the allowance should be 221000
uint256 allowance = zeroId + _collectionAllowance[collectionId];
require(
tokenId <= allowance,
"ERC2309Collection: Minting exceeded for this collection"
);

//now mint
_safeMint(recipient, tokenId);
//update the last id
_collectionLast[collectionId] = tokenId;

return tokenId;
}

/**
* @dev Collection minting factory
*/
function _mintCollectionBatch(
uint256 collectionId,
address[] memory recipients
) internal returns(uint256) {
//calculate the zero id
//to get the zero id of precision 4 of collection id 22
//(10 ** 4) * 22 = (10000 * 22) = 220000
uint256 zeroId = (10 ** _collectionPrecision) * collectionId;
//determine last id
uint256 lastId = _collectionLast[collectionId];
//if no last
if (lastId == 0) {
//make it the zero id
lastId = zeroId;
}

//ex. from token id 2 and 3 recipients, the to token should be 4 [2, 3, 4]
uint256 fromTokenId = lastId + 1;
uint256 toTokenId = fromTokenId + recipients.length - 1;
//determine the allowance
//if zero id is 220000 and allowance is 1000 the allowance should be 221000
uint256 allowance = zeroId + _collectionAllowance[collectionId];
require(
toTokenId <= allowance,
"ERC2309Collection: Minting exceeded for this collection"
);

//now mint
_mintBatchEnumerate(fromTokenId, recipients);
//update the last id
_collectionLast[collectionId] = toTokenId;

return toTokenId;
}

/**
* @dev Returns a collection's URI
*/
function CollectionURI(uint256 collectionId) external view returns(string memory) {
return _collectionURIs[collectionId];
}

/**
* @dev Returns a collection's URI
*/
function CollectionAllowance(uint256 collectionId)
external
view
returns(uint256)
{
return _collectionAllowance[collectionId];
}

/**
* @dev Returns a collection id given the token id
*/
function TokenCollection(uint256 tokenId) public view returns(uint256) {
//to get the collection id 22 from the token id 220034 of precision 4
//220034 / (10 ** 4) = 220034 / 10000 = 22 (solidity automatically floors)
return tokenId / (10 ** _collectionPrecision);
}

/**
* @dev Returns the last token id for a collection
*/
function LastCollectionToken(uint256 collectionId)
public
view
returns(uint256)
{
return _collectionLast[collectionId];
}
}
59 changes: 59 additions & 0 deletions contracts/token/ERC2309/extensions/ERC2309CollectionDrops.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

//abstract implementation of ERC2309
//allows this contract to be treated as a token factory organized by collections
import "./ERC2309Collection.sol";

//crypto logic for merkle trees
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

/**
* @title ERC2309 Collection Based Air Drops
* @dev ERC2309 Factory where merkle drops are attached per collection
*/
abstract contract ERC2309CollectionDrops is ERC2309Collection {
//collection id -> root
mapping(uint256 => bytes32) private _collectionRoots;

/**
* @dev Defines a redeemable collection
*/
function _drop(
string memory uri,
uint256 allowance,
bytes32 merkleroot
) internal returns(uint256) {
//create the collection first to see if there are any errors
uint256 collectionId = _createCollection(uri, allowance);

//next save the merkle root
_collectionRoots[collectionId] = merkleroot;

return collectionId;
}

/**
* @dev Allows anyone to generally redeem anyone's token
*/
function _redeem(
uint256 collectionId,
uint256 key,
address recipient,
bytes32[] calldata proof
) internal returns(uint256) {
//error if the proof is not verified
require(
//this verifies that the recipient owns this token
MerkleProof.verify(
proof,
_collectionRoots[collectionId],
//this is the leaf hash
keccak256(abi.encodePacked(collectionId, key, recipient))
),
"ERC2309CollectionDrops: Recipient does not own this token"
);
return _mintCollection(collectionId, recipient);
}
}
61 changes: 61 additions & 0 deletions contracts/token/ERC2309/extensions/ERC2309CollectionExchange.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

//custom abstract implementation of ERC2309
import "./ERC2309CollectionFees.sol";

//custom abstract implementation of ERC2309
import "./ERC2309OrderBook.sol";

/**
* @title ERC2309 Collection Exchange
* @dev ERC2309 Factory where buyers can buy tokens in the book and royalties are paid
*/
abstract contract ERC2309CollectionExchange is
ERC2309CollectionFees,
ERC2309OrderBook
{
/**
* @dev Allows for a sender to avail of the offer price
*/
function exchange(uint256 tokenId) public payable {
//get listing
uint256 listing = book[tokenId];
//should be a valid listing
require(listing > 0, "ERC2309Exchange: Token is not listed");
//value should equal the listing amount
require(
msg.value == listing,
"ERC2309Exchange: Amount sent does not match the listing amount"
);

//get collection from token
uint256 collectionId = TokenCollection(tokenId);

//placeholder for recipient in the loop
address recipient;
//release payments to recipients
for (uint i = 0; i < _collectionRecipients[collectionId].length; i++) {
//get the recipient
recipient = _collectionRecipients[collectionId][i];
// (10 eth * 2000) / 10000 =
payable(recipient).transfer(
(msg.value * _collectionFees[collectionId][recipient]) / TOTAL_ALLOWABLE_FEES
);
}

//get the token owner
address payable tokenOwner = payable(ownerOf(tokenId));
//determine the remaining fee
uint256 remainingFee = TOTAL_ALLOWABLE_FEES - _totalCollectionFees[collectionId];
//send the remaining fee to the token owner
tokenOwner.transfer((msg.value * remainingFee) / TOTAL_ALLOWABLE_FEES);
//transfer token from owner to buyer
_transfer(tokenOwner, _msgSender(), tokenId);
//finally delist
delete book[tokenId];
//emit that something was delisted
emit Delisted(tokenOwner, tokenId);
}
}
Loading

0 comments on commit dca8ad3

Please sign in to comment.