diff --git a/contracts/RockburgNFT.sol b/contracts/RockburgNFT.sol
index e21f5c9..1151bad 100644
--- a/contracts/RockburgNFT.sol
+++ b/contracts/RockburgNFT.sol
@@ -3,190 +3,304 @@ pragma solidity ^0.8.0;
// RockburgNFT contract by m1guelpf.eth
-import "./data/Types.sol";
-import "base64-sol/base64.sol";
-import "./interfaces/IVenueRenderer.sol";
-import "./interfaces/IStudioRenderer.sol";
-import "./interfaces/IMusicianRenderer.sol";
-import "@openzeppelin/contracts/access/Ownable.sol";
-import "@openzeppelin/contracts/utils/Counters.sol";
-import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
-import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
-
-contract RockburgNFT is Ownable, ERC721, ERC721Enumerable {
- using Counters for Counters.Counter;
- Counters.Counter private _tokenIds;
- Counters.Counter private randomnessNonce;
-
- address internal _musicianRenderer;
- address internal _venueRenderer;
- address internal _studioRenderer;
-
- mapping(uint256 => Types) private _tokenTypes;
- mapping(uint256 => Musician) private _musicians;
- mapping(uint256 => Venue) private _venues;
- mapping(uint256 => Studio) private _studios;
- mapping(uint256 => Band) private _bands;
- mapping(uint256 => Song) private _songs;
-
- constructor(address musicianRenderer, address venueRenderer, address studioRenderer) ERC721("Rockburg", "RCKBRG") {
- _musicianRenderer = musicianRenderer;
- _venueRenderer = venueRenderer;
- _studioRenderer = studioRenderer;
- }
-
- /**
- * @dev Creates a new artist token with pseudo-randomized stats and returns its identifier.
- * For simplicity, we do not check wether the caller implements ERC721Receiver.
- */
- function mintArtist(string calldata name, string calldata role) public returns (uint256) {
- _tokenIds.increment();
-
- uint256 tokenId = _tokenIds.current();
-
- _tokenTypes[tokenId] = Types.MUSICIAN;
- Musician storage musician = _musicians[tokenId];
- musician.name = name;
- musician.role = role;
- musician.skillPoints = randNum(abi.encodePacked(name, role)) % 100;
- musician.egoPoints = randNum(abi.encodePacked(name, role)) % 100;
- musician.lookPoints = randNum(abi.encodePacked(name, role)) % 100;
- musician.creativePoints = randNum(abi.encodePacked(name, role)) % 100;
-
- _mint(_msgSender(), tokenId);
-
- return tokenId;
- }
-
- /**
- * @dev Creates a new venue token with pseudo-randomized stats and returns its identifier.
- * For simplicity, we do not check wether the caller implements ERC721Receiver.
- */
- function mintVenue(string calldata name, string calldata location) public returns (uint256) {
- _tokenIds.increment();
-
- uint256 tokenId = _tokenIds.current();
-
- _tokenTypes[tokenId] = Types.VENUE;
- Venue storage venue = _venues[tokenId];
- venue.name = name;
- venue.location = location;
- venue.visitorCap = randNum(abi.encodePacked(name, location)) % 100;
- venue.dollarCost = randNum(abi.encodePacked(name, location)) % 100;
- venue.cleanlinessPoints = randNum(abi.encodePacked(name, location)) % 100;
- venue.reputationPoints = randNum(abi.encodePacked(name, location)) % 100;
-
- _mint(_msgSender(), tokenId);
-
- return tokenId;
- }
-
- /**
- * @dev Creates a new studio token with pseudo-randomized stats and returns its identifier.
- * For simplicity, we do not check wether the caller implements ERC721Receiver.
- */
- function mintStudio(string calldata name, string calldata location) public returns (uint256) {
- _tokenIds.increment();
-
- uint256 tokenId = _tokenIds.current();
-
- _tokenTypes[tokenId] = Types.STUDIO;
- Studio storage studio = _studios[tokenId];
- studio.name = name;
- studio.location = location;
- studio.dollarCost = randNum(abi.encodePacked(name, location)) % 50;
- studio.leadTime = randNum(abi.encodePacked(name, location)) % 12;
- studio.reputationPoints = randNum(abi.encodePacked(name, location)) % 100;
-
- _mint(_msgSender(), tokenId);
-
- return tokenId;
- }
-
- function getType(uint256 tokenId) public view virtual returns (Types) {
- return _tokenTypes[tokenId];
- }
-
- function getMusician(uint256 tokenId) public view virtual returns (Musician memory) {
- require(_exists(tokenId), "token does not exist");
- require(_tokenTypes[tokenId] == Types.MUSICIAN, "token is not a musician");
-
- return _musicians[tokenId];
- }
-
- function getVenue(uint256 tokenId) public view virtual returns (Venue memory) {
- require(_exists(tokenId), "token does not exist");
- require(_tokenTypes[tokenId] == Types.VENUE, "token is not a venue");
-
- return _venues[tokenId];
- }
-
- function getStudio(uint256 tokenId) public view virtual returns (Studio memory) {
- require(_exists(tokenId), "token does not exist");
- require(_tokenTypes[tokenId] == Types.STUDIO, "token is not a studio");
-
- return _studios[tokenId];
- }
-
- function getBand(uint256 tokenId) public view virtual returns (Band memory) {
- require(_exists(tokenId), "token does not exist");
- require(_tokenTypes[tokenId] == Types.BAND, "token is not a studio");
-
- return _bands[tokenId];
- }
-
- function getSong(uint256 tokenId) public view virtual returns (Song memory) {
- require(_exists(tokenId), "token does not exist");
- require(_tokenTypes[tokenId] == Types.SONG, "token is not a song");
-
- return _songs[tokenId];
- }
-
- /**
- * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
- */
- function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
- require(_exists(tokenId), "token does not exist");
-
- Types tokenType = _tokenTypes[tokenId];
-
- if (tokenType == Types.MUSICIAN) {
- return IMusicianRenderer(_musicianRenderer).constructTokenURI(_musicians[tokenId], tokenId);
- }
- if (tokenType == Types.VENUE) {
- return IVenueRenderer(_venueRenderer).constructTokenURI(_venues[tokenId], tokenId);
- }
- if (tokenType == Types.STUDIO) {
- return IStudioRenderer(_studioRenderer).constructTokenURI(_studios[tokenId], tokenId);
- }
-
- return "TBD";
- }
-
- /**
- * @dev Returns a pseudo-random `uint256`, based off a provided seed, the sender, block number, block difficulty, and an internal nonce.
- */
- function randNum(bytes memory seed) internal returns (uint256) {
- randomnessNonce.increment();
-
- return uint256(keccak256(abi.encodePacked(seed, msg.sender, block.number, block.difficulty, randomnessNonce.current())));
- }
-
- /**
- * @dev Hook that is called before any token transfer. This includes minting and burning.
- */
- function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override(ERC721, ERC721Enumerable) {
- super._beforeTokenTransfer(from, to, tokenId);
- }
-
- /**
- * @dev Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding
- * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
- * to learn more about how these ids are created.
- *
- * This function call must use less than 30 000 gas.
- */
- function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
- return super.supportsInterface(interfaceId);
- }
+import './data/Types.sol';
+import 'base64-sol/base64.sol';
+import './interfaces/IVenueRenderer.sol';
+import './interfaces/IStudioRenderer.sol';
+import './interfaces/IMusicianRenderer.sol';
+import '@openzeppelin/contracts/access/Ownable.sol';
+import '@openzeppelin/contracts/utils/Counters.sol';
+import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
+import '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol';
+import '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol';
+
+import 'hardhat/console.sol';
+
+contract RockburgNFT is Ownable, ERC721, ERC721Enumerable, ERC721Holder {
+ using Counters for Counters.Counter;
+ Counters.Counter private _tokenIds;
+ Counters.Counter private randomnessNonce;
+
+ address internal _musicianRenderer;
+ address internal _venueRenderer;
+ address internal _studioRenderer;
+
+ mapping(uint256 => Types) private _tokenTypes;
+ mapping(uint256 => Musician) private _musicians;
+ mapping(uint256 => Venue) private _venues;
+ mapping(uint256 => Studio) private _studios;
+ mapping(uint256 => Band) private _bands;
+ mapping(uint256 => Song) private _songs;
+
+ constructor(
+ address musicianRenderer,
+ address venueRenderer,
+ address studioRenderer
+ ) ERC721('Rockburg', 'RCKBRG') {
+ _musicianRenderer = musicianRenderer;
+ _venueRenderer = venueRenderer;
+ _studioRenderer = studioRenderer;
+ }
+
+ /**
+ * @dev Creates a new band token with pseudo-randomized stats and returns its identifier.
+ * For simplicity, we do not check wether the caller implements ERC721Receiver.
+ */
+ function mintBand(string calldata name, string calldata bandType) internal returns (uint256) {
+ _tokenIds.increment();
+
+ uint256 tokenId = _tokenIds.current();
+
+ _tokenTypes[tokenId] = Types.BAND;
+ Band storage band = _bands[tokenId];
+
+ band.name = name;
+ band.bandType = bandType;
+ band.fanCount = randNum(abi.encodePacked(name, bandType)) % 100;
+ band.buzzPoints = randNum(abi.encodePacked(name, bandType)) % 100;
+
+ _mint(_msgSender(), tokenId);
+
+ return tokenId;
+ }
+
+ /**
+ * @dev Creates a new artist token with pseudo-randomized stats and returns its identifier.
+ * For simplicity, we do not check wether the caller implements ERC721Receiver.
+ */
+ function mintArtist(string calldata name, Role role) public returns (uint256) {
+ _tokenIds.increment();
+
+ uint256 tokenId = _tokenIds.current();
+
+ _tokenTypes[tokenId] = Types.MUSICIAN;
+ Musician storage musician = _musicians[tokenId];
+ musician.name = name;
+ musician.role = role;
+ musician.skillPoints = randNum(abi.encodePacked(name, role)) % 100;
+ musician.egoPoints = randNum(abi.encodePacked(name, role)) % 100;
+ musician.lookPoints = randNum(abi.encodePacked(name, role)) % 100;
+ musician.creativePoints = randNum(abi.encodePacked(name, role)) % 100;
+
+ _mint(_msgSender(), tokenId);
+
+ return tokenId;
+ }
+
+ /**
+ * @dev Creates a new venue token with pseudo-randomized stats and returns its identifier.
+ * For simplicity, we do not check wether the caller implements ERC721Receiver.
+ */
+ function mintVenue(string calldata name, string calldata location) public returns (uint256) {
+ _tokenIds.increment();
+
+ uint256 tokenId = _tokenIds.current();
+
+ _tokenTypes[tokenId] = Types.VENUE;
+ Venue storage venue = _venues[tokenId];
+ venue.name = name;
+ venue.location = location;
+ venue.visitorCap = randNum(abi.encodePacked(name, location)) % 100;
+ venue.dollarCost = randNum(abi.encodePacked(name, location)) % 100;
+ venue.cleanlinessPoints = randNum(abi.encodePacked(name, location)) % 100;
+ venue.reputationPoints = randNum(abi.encodePacked(name, location)) % 100;
+
+ _mint(_msgSender(), tokenId);
+
+ return tokenId;
+ }
+
+ /**
+ * @dev Creates a new studio token with pseudo-randomized stats and returns its identifier.
+ * For simplicity, we do not check wether the caller implements ERC721Receiver.
+ */
+ function mintStudio(string calldata name, string calldata location) public returns (uint256) {
+ _tokenIds.increment();
+
+ uint256 tokenId = _tokenIds.current();
+
+ _tokenTypes[tokenId] = Types.STUDIO;
+ Studio storage studio = _studios[tokenId];
+ studio.name = name;
+ studio.location = location;
+ studio.dollarCost = randNum(abi.encodePacked(name, location)) % 50;
+ studio.leadTime = randNum(abi.encodePacked(name, location)) % 12;
+ studio.reputationPoints = randNum(abi.encodePacked(name, location)) % 100;
+
+ _mint(_msgSender(), tokenId);
+
+ return tokenId;
+ }
+
+ function getType(uint256 tokenId) public view virtual returns (Types) {
+ return _tokenTypes[tokenId];
+ }
+
+ function getMusician(uint256 tokenId) public view virtual returns (Musician memory) {
+ require(_exists(tokenId), 'token does not exist');
+ require(_tokenTypes[tokenId] == Types.MUSICIAN, 'token is not a musician');
+
+ return _musicians[tokenId];
+ }
+
+ function getVenue(uint256 tokenId) public view virtual returns (Venue memory) {
+ require(_exists(tokenId), 'token does not exist');
+ require(_tokenTypes[tokenId] == Types.VENUE, 'token is not a venue');
+
+ return _venues[tokenId];
+ }
+
+ function getStudio(uint256 tokenId) public view virtual returns (Studio memory) {
+ require(_exists(tokenId), 'token does not exist');
+ require(_tokenTypes[tokenId] == Types.STUDIO, 'token is not a studio');
+
+ return _studios[tokenId];
+ }
+
+ function getBand(uint256 tokenId) public view virtual returns (Band memory) {
+ require(_exists(tokenId), 'token does not exist');
+ require(_tokenTypes[tokenId] == Types.BAND, 'token is not a band');
+
+ return _bands[tokenId];
+ }
+
+ function getSong(uint256 tokenId) public view virtual returns (Song memory) {
+ require(_exists(tokenId), 'token does not exist');
+ require(_tokenTypes[tokenId] == Types.SONG, 'token is not a song');
+
+ return _songs[tokenId];
+ }
+
+ function checkArtistRole(uint256 tokenId, Role expectedRole) internal view returns (bool) {
+ return _musicians[tokenId].role == expectedRole;
+ }
+
+ /**
+ * @dev Form a band
+ */
+ function formBand(
+ string calldata name,
+ string calldata bandType,
+ uint256 vocalistId,
+ uint256 leadGuitarId,
+ uint256 rhythmGuitarId,
+ uint256 bassId,
+ uint256 drumsId
+ ) public returns (Band memory) {
+ // Check the type of the token
+ require(_tokenTypes[vocalistId] == Types.MUSICIAN, 'vocalistId is not an artist');
+ require(_tokenTypes[leadGuitarId] == Types.MUSICIAN, 'leadGuitarId is not an artist');
+ require(_tokenTypes[rhythmGuitarId] == Types.MUSICIAN, 'rhythmGuitarId is not an artist');
+ require(_tokenTypes[bassId] == Types.MUSICIAN, 'bassId is not an artist');
+ require(_tokenTypes[drumsId] == Types.MUSICIAN, 'drumsId is not an artist');
+
+ require(checkArtistRole(vocalistId, Role.VOCALIST), 'vocalistId is not a Vocalist');
+ require(checkArtistRole(leadGuitarId, Role.LEAD_GUITAR), 'leadGuitarId is not a Lead Guitar');
+ require(checkArtistRole(rhythmGuitarId, Role.RHYTHM_GUITAR), 'rhythmGuitarId is not a Rhythm Guitar');
+ require(checkArtistRole(bassId, Role.BASS), 'bassId is not a Bass');
+ require(checkArtistRole(drumsId, Role.DRUMS), 'drumsId is not a Drums');
+
+ // transfer all those artist to the contract itself, this will also automatically
+ // check that the send owns the token
+ safeTransferFrom(msg.sender, address(this), vocalistId);
+ safeTransferFrom(msg.sender, address(this), leadGuitarId);
+ safeTransferFrom(msg.sender, address(this), rhythmGuitarId);
+ safeTransferFrom(msg.sender, address(this), bassId);
+ safeTransferFrom(msg.sender, address(this), drumsId);
+
+ uint256 bandId = mintBand(name, bandType);
+
+ // bind artist to band
+ Band storage _band = _bands[bandId];
+ _band.vocalistId = vocalistId;
+ _band.leadGuitarId = leadGuitarId;
+ _band.rhythmGuitarId = rhythmGuitarId;
+ _band.bassId = bassId;
+ _band.drumsId = drumsId;
+
+ return _band;
+ }
+
+ function disbandBand(uint256 bandId) public {
+ require(_isApprovedOrOwner(msg.sender, bandId), "You don't own the band");
+ require(_tokenTypes[bandId] == Types.BAND, 'token is not a band');
+
+ // bind artist to band
+ Band storage _band = _bands[bandId];
+
+ // transfer all those artist to the contract itself, this will also automatically
+ // check that the send owns the token
+
+ _transfer(address(this), msg.sender, _band.vocalistId);
+ _transfer(address(this), msg.sender, _band.leadGuitarId);
+ _transfer(address(this), msg.sender, _band.rhythmGuitarId);
+ _transfer(address(this), msg.sender, _band.bassId);
+ _transfer(address(this), msg.sender, _band.drumsId);
+
+ // Should we just reset the band and transfer it back to us or burn it?
+
+ // Safe transfer: option 1
+ // _band.vocalistId = 0;
+ // _band.leadGuitarId = 0;
+ // _band.rhythmGuitarId = 0;
+ // _band.bassId = 0;
+ // _band.drumsId = 0;
+ // safeTransferFrom(msg.sender, address(this), bandId);
+
+ // Burn it: option 2
+ _burn(bandId);
+ delete _bands[bandId];
+ }
+
+ /**
+ * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
+ */
+ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
+ require(_exists(tokenId), 'token does not exist');
+
+ Types tokenType = _tokenTypes[tokenId];
+
+ if (tokenType == Types.MUSICIAN) {
+ return IMusicianRenderer(_musicianRenderer).constructTokenURI(_musicians[tokenId], tokenId);
+ }
+ if (tokenType == Types.VENUE) {
+ return IVenueRenderer(_venueRenderer).constructTokenURI(_venues[tokenId], tokenId);
+ }
+ if (tokenType == Types.STUDIO) {
+ return IStudioRenderer(_studioRenderer).constructTokenURI(_studios[tokenId], tokenId);
+ }
+
+ return 'TBD';
+ }
+
+ /**
+ * @dev Returns a pseudo-random `uint256`, based off a provided seed, the sender, block number, block difficulty, and an internal nonce.
+ */
+ function randNum(bytes memory seed) internal returns (uint256) {
+ randomnessNonce.increment();
+
+ return uint256(keccak256(abi.encodePacked(seed, msg.sender, block.number, block.difficulty, randomnessNonce.current())));
+ }
+
+ /**
+ * @dev Hook that is called before any token transfer. This includes minting and burning.
+ */
+ function _beforeTokenTransfer(
+ address from,
+ address to,
+ uint256 tokenId
+ ) internal virtual override(ERC721, ERC721Enumerable) {
+ super._beforeTokenTransfer(from, to, tokenId);
+ }
+
+ /**
+ * @dev Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding
+ * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
+ * to learn more about how these ids are created.
+ *
+ * This function call must use less than 30 000 gas.
+ */
+ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
+ return super.supportsInterface(interfaceId);
+ }
}
diff --git a/contracts/data/Types.sol b/contracts/data/Types.sol
index dc9f5e0..0ec2f63 100644
--- a/contracts/data/Types.sol
+++ b/contracts/data/Types.sol
@@ -1,45 +1,64 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
-enum Types { MUSICIAN, VENUE, STUDIO, BAND, SONG }
+enum Types {
+ MUSICIAN,
+ VENUE,
+ STUDIO,
+ BAND,
+ SONG
+}
+
+enum Role {
+ VOCALIST,
+ LEAD_GUITAR,
+ RHYTHM_GUITAR,
+ BASS,
+ DRUMS
+}
struct Musician {
- string name;
- string role;
- uint256 skillPoints;
- uint256 egoPoints;
- uint256 lookPoints;
- uint256 creativePoints;
+ string name;
+ Role role;
+ uint256 skillPoints;
+ uint256 egoPoints;
+ uint256 lookPoints;
+ uint256 creativePoints;
}
struct Venue {
- string name;
- string location;
- uint256 visitorCap;
- uint256 dollarCost;
- uint256 cleanlinessPoints;
- uint256 reputationPoints;
+ string name;
+ string location;
+ uint256 visitorCap;
+ uint256 dollarCost;
+ uint256 cleanlinessPoints;
+ uint256 reputationPoints;
}
struct Studio {
- string name;
- string location;
- uint256 dollarCost;
- uint256 leadTime;
- uint256 reputationPoints;
+ string name;
+ string location;
+ uint256 dollarCost;
+ uint256 leadTime;
+ uint256 reputationPoints;
}
struct Band {
- string name;
- string bandType;
- uint256 fanCount;
- uint256 buzzPoints;
+ string name;
+ string bandType;
+ uint256 fanCount;
+ uint256 buzzPoints;
+ uint256 vocalistId;
+ uint256 leadGuitarId;
+ uint256 rhythmGuitarId;
+ uint256 bassId;
+ uint256 drumsId;
}
struct Song {
- string name;
- string author;
- uint256 qualityPoints;
- uint256 streamCount;
- uint256 ratingPoints;
+ string name;
+ string author;
+ uint256 qualityPoints;
+ uint256 streamCount;
+ uint256 ratingPoints;
}
diff --git a/contracts/renderers/MusicianRenderer.sol b/contracts/renderers/MusicianRenderer.sol
index b72b1f2..aeb495d 100644
--- a/contracts/renderers/MusicianRenderer.sol
+++ b/contracts/renderers/MusicianRenderer.sol
@@ -7,45 +7,61 @@ import "base64-sol/base64.sol";
import "../interfaces/IMusicianRenderer.sol";
contract MusicianRenderer is IMusicianRenderer, Strings {
- function constructTokenURI(Musician calldata musician, uint256 tokenId) external pure override returns (string memory) {
- string[14] memory parts;
- // solhint-disable-next-line quotes
- parts[0] = '';
+ // solhint-disable-next-line quotes
+ parts[11] = 'Creativity';
+ parts[12] = Strings.uintToString(musician.creativePoints);
- string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8]));
- output = string(abi.encodePacked(output, parts[9], parts[10], parts[11], parts[12], parts[13]));
+ // solhint-disable-next-line quotes
+ parts[13] = 'Musician';
- // solhint-disable-next-line quotes
- string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Musician #', Strings.uintToString(tokenId), '", "description": "Rockburg is a WIP card game on the blockchain.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
- output = string(abi.encodePacked("data:application/json;base64,", json));
+ string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8]));
+ output = string(abi.encodePacked(output, parts[9], parts[10], parts[11], parts[12], parts[13]));
- return output;
- }
+ // solhint-disable-next-line quotes
+ string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Musician #', Strings.uintToString(tokenId), '", "description": "Rockburg is a WIP card game on the blockchain.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
+ output = string(abi.encodePacked("data:application/json;base64,", json));
+
+ return output;
+ }
}
diff --git a/hardhat.config.ts b/hardhat.config.ts
index e8e72ea..0247c81 100644
--- a/hardhat.config.ts
+++ b/hardhat.config.ts
@@ -9,7 +9,15 @@ import '@nomiclabs/hardhat-etherscan'
import 'solidity-coverage'
const config: HardhatUserConfig = {
- solidity: '0.8.4',
+ solidity: {
+ version: '0.8.4',
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 1000,
+ },
+ },
+ },
networks: {
hardhat: {
initialBaseFeePerGas: 0, // workaround from https://github.com/sc-forks/solidity-coverage/issues/652#issuecomment-896330136 . Remove when that issue is closed.
diff --git a/test/band-test.ts b/test/band-test.ts
new file mode 100644
index 0000000..3ee6b37
--- /dev/null
+++ b/test/band-test.ts
@@ -0,0 +1,216 @@
+import { Contract } from '@ethersproject/contracts'
+
+import { ethers, waffle } from 'hardhat'
+import chai from 'chai'
+const { expect } = chai
+const { deployContract } = waffle
+
+const renderers = ['Musician', 'Venue', 'Studio']
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
+import RockburgNFTArtifact from '../artifacts/contracts/RockburgNFT.sol/RockburgNFT.json'
+import { RockburgNFT } from '../typechain/RockburgNFT'
+
+describe('Band test', function () {
+ let owner: SignerWithAddress
+ let addr1: SignerWithAddress
+ let addr2: SignerWithAddress
+ let addrs: SignerWithAddress[]
+
+ let contract: RockburgNFT
+
+ beforeEach(async () => {
+ ;[owner, addr1, addr2, ...addrs] = await ethers.getSigners()
+ const rendererAddresses = await Promise.all(
+ renderers.map(async type => {
+ const Renderer = await ethers.getContractFactory(`${type}Renderer`)
+ const renderer = await Renderer.deploy()
+ await renderer.deployed()
+
+ return renderer.address
+ })
+ )
+
+ contract = (await deployContract(owner, RockburgNFTArtifact, rendererAddresses)) as RockburgNFT
+ })
+
+ describe('Test formBand function', () => {
+ it('form a band successfully', async function () {
+ await contract.connect(addr1).mintArtist('Vocalist 1', 0) // VOCALIST
+ await contract.connect(addr1).mintArtist('Lead Guitar 1', 1) // LEAD_GUITAR
+ await contract.connect(addr1).mintArtist('Rhythm Guitar 1', 2) // RHYTHM_GUITAR
+ await contract.connect(addr1).mintArtist('Bass 1', 3) // BASS
+ await contract.connect(addr1).mintArtist('Drums 1', 4) // DRUMS
+
+ // Artists owned by the user
+ expect(await contract.ownerOf(1)).to.equal(addr1.address)
+ expect(await contract.ownerOf(2)).to.equal(addr1.address)
+ expect(await contract.ownerOf(3)).to.equal(addr1.address)
+ expect(await contract.ownerOf(4)).to.equal(addr1.address)
+ expect(await contract.ownerOf(5)).to.equal(addr1.address)
+
+ // Form a band
+ await contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 3, 4, 5)
+
+ const band = await contract.getBand(6)
+ expect(band.name).to.equal('Amazing band')
+ expect(band.bandType).to.equal('Blues')
+ expect(band.fanCount).to.be.within(0, 100)
+ expect(band.buzzPoints).to.be.within(0, 100)
+ expect(band.vocalistId).to.equal(1)
+ expect(band.leadGuitarId).to.equal(2)
+ expect(band.rhythmGuitarId).to.equal(3)
+ expect(band.bassId).to.equal(4)
+ expect(band.drumsId).to.equal(5)
+
+ // I own the bands but I don't own anymore the single artists (owned by the contract)
+ // Otherwise I could transfer them.
+ // If I want to own them I need to disband the band
+
+ // Band is owned by the user
+ expect(await contract.ownerOf(6)).to.equal(addr1.address)
+
+ // Artists are owned by the contract
+ expect(await contract.ownerOf(1)).to.equal(contract.address)
+ expect(await contract.ownerOf(2)).to.equal(contract.address)
+ expect(await contract.ownerOf(3)).to.equal(contract.address)
+ expect(await contract.ownerOf(4)).to.equal(contract.address)
+ expect(await contract.ownerOf(5)).to.equal(contract.address)
+ })
+
+ it('form a band with tokens that does not exist', async function () {
+ // Form a band
+ const tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 7, 8, 9, 10, 11)
+
+ await expect(tx).to.be.revertedWith('leadGuitarId is not a Lead Guitar')
+ })
+
+ it('form a band with tokens that are not musicians', async function () {
+ await contract.connect(addr1).mintArtist('Vocalist 1', 0) // VOCALIST
+ await contract.connect(addr1).mintArtist('Lead Guitar 1', 1) // LEAD_GUITAR
+ await contract.connect(addr1).mintArtist('Rhythm Guitar 1', 2) // RHYTHM_GUITAR
+ await contract.connect(addr1).mintArtist('Bass 1', 3) // BASS
+ await contract.connect(addr1).mintArtist('Drums 1', 4) // DRUMS
+
+ await contract.connect(addr1).mintVenue('Venue 1', 'Italy') // Venue
+
+ // Vocalist of wrong type
+ let tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 6, 2, 3, 4, 5)
+ await expect(tx).to.be.revertedWith('vocalistId is not an artist')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 6, 3, 4, 5)
+ await expect(tx).to.be.revertedWith('leadGuitarId is not an artist')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 6, 4, 5)
+ await expect(tx).to.be.revertedWith('rhythmGuitarId is not an artist')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 3, 6, 5)
+ await expect(tx).to.be.revertedWith('bassId is not an artist')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 3, 4, 6)
+ await expect(tx).to.be.revertedWith('drumsId is not an artist')
+ })
+
+ it('form a band with tokens that are musicians but not respect role', async function () {
+ await contract.connect(addr1).mintArtist('Vocalist 1', 0) // VOCALIST
+ await contract.connect(addr1).mintArtist('Lead Guitar 1', 1) // LEAD_GUITAR
+ await contract.connect(addr1).mintArtist('Rhythm Guitar 1', 2) // RHYTHM_GUITAR
+ await contract.connect(addr1).mintArtist('Bass 1', 3) // BASS
+ await contract.connect(addr1).mintArtist('Drums 1', 4) // DRUMS
+ await contract.connect(addr1).mintArtist('Vocalist 2', 0) // VOCALIST
+
+ await contract.connect(addr1).mintVenue('Venue 1', 'Italy') // Venue
+
+ // Vocalist of wrong type
+ let tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 2, 1, 3, 4, 5)
+ await expect(tx).to.be.revertedWith('vocalistId is not a Vocalist')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 3, 2, 4, 5)
+ await expect(tx).to.be.revertedWith('leadGuitarId is not a Lead Guitar')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 4, 3, 5)
+ await expect(tx).to.be.revertedWith('rhythmGuitarId is not a Rhythm Guitar')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 3, 5, 4)
+ await expect(tx).to.be.revertedWith('bassId is not a Bass')
+
+ tx = contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 3, 4, 6)
+ await expect(tx).to.be.revertedWith('drumsId is not a Drums')
+ })
+
+ it("form a band with artists you don't own", async function () {
+ await contract.connect(addr1).mintArtist('Vocalist 1', 0) // VOCALIST
+ await contract.connect(addr1).mintArtist('Lead Guitar 1', 1) // LEAD_GUITAR
+ await contract.connect(addr1).mintArtist('Rhythm Guitar 1', 2) // RHYTHM_GUITAR
+ await contract.connect(addr1).mintArtist('Bass 1', 3) // BASS
+ await contract.connect(addr1).mintArtist('Drums 1', 4) // DRUMS
+ await contract.connect(addr1).mintArtist('Vocalist 2', 0) // VOCALIST
+
+ // Vocalist of wrong type
+ let tx = contract.connect(addr2).formBand('Amazing band', 'Blues', 1, 2, 3, 4, 5)
+ await expect(tx).to.be.revertedWith('ERC721: transfer caller is not owner nor approved')
+ })
+ })
+
+ describe('Test disbandBand function', () => {
+ it('disband a band successfully', async function () {
+ await contract.connect(addr1).mintArtist('Vocalist 1', 0) // VOCALIST
+ await contract.connect(addr1).mintArtist('Lead Guitar 1', 1) // LEAD_GUITAR
+ await contract.connect(addr1).mintArtist('Rhythm Guitar 1', 2) // RHYTHM_GUITAR
+ await contract.connect(addr1).mintArtist('Bass 1', 3) // BASS
+ await contract.connect(addr1).mintArtist('Drums 1', 4) // DRUMS
+
+ // Form a band
+ await contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 3, 4, 5)
+ expect(await contract.ownerOf(1)).to.equal(contract.address)
+ expect(await contract.ownerOf(2)).to.equal(contract.address)
+ expect(await contract.ownerOf(3)).to.equal(contract.address)
+ expect(await contract.ownerOf(4)).to.equal(contract.address)
+ expect(await contract.ownerOf(5)).to.equal(contract.address)
+
+ // Disband it and take back all
+ // At the moment the "burn" method is implemented
+ await contract.connect(addr1).disbandBand(6)
+
+ // Artists are owned by the user again
+ expect(await contract.ownerOf(1)).to.equal(addr1.address)
+ expect(await contract.ownerOf(2)).to.equal(addr1.address)
+ expect(await contract.ownerOf(3)).to.equal(addr1.address)
+ expect(await contract.ownerOf(4)).to.equal(addr1.address)
+ expect(await contract.ownerOf(5)).to.equal(addr1.address)
+
+ await expect(contract.ownerOf(6)).to.be.revertedWith('ERC721: owner query for nonexistent token')
+ await expect(contract.getBand(6)).to.be.revertedWith('token does not exist')
+ })
+
+ it('disband a band that does not exist', async function () {
+ // At the moment the "burn" method is implemented
+ const tx = contract.connect(addr1).disbandBand(6)
+
+ await expect(tx).to.be.revertedWith('ERC721: operator query for nonexistent token')
+ })
+
+ it('disband a token that is not a band', async function () {
+ await contract.connect(addr1).mintArtist('Vocalist 1', 0) // VOCALIST
+ // At the moment the "burn" method is implemented
+ const tx = contract.connect(addr1).disbandBand(1)
+
+ await expect(tx).to.be.revertedWith('token is not a band')
+ })
+
+ it("disband a band you don't own", async function () {
+ await contract.connect(addr1).mintArtist('Vocalist 1', 0) // VOCALIST
+ await contract.connect(addr1).mintArtist('Lead Guitar 1', 1) // LEAD_GUITAR
+ await contract.connect(addr1).mintArtist('Rhythm Guitar 1', 2) // RHYTHM_GUITAR
+ await contract.connect(addr1).mintArtist('Bass 1', 3) // BASS
+ await contract.connect(addr1).mintArtist('Drums 1', 4) // DRUMS
+
+ // Form a band
+ await contract.connect(addr1).formBand('Amazing band', 'Blues', 1, 2, 3, 4, 5)
+
+ // Disband it and take back all
+ const tx = contract.connect(addr2).disbandBand(6)
+
+ await expect(tx).to.be.revertedWith("You don't own the band")
+ })
+ })
+})
diff --git a/test/metadata-test.ts b/test/metadata-test.ts
index a216591..115f2e9 100644
--- a/test/metadata-test.ts
+++ b/test/metadata-test.ts
@@ -31,7 +31,7 @@ describe('RockburgNFT', function () {
})
it('correctly generates metadata for a musician', async function () {
- const mintTx = await contract.mintArtist('Shpigford', 'Coder')
+ const mintTx = await contract.mintArtist('Shpigford', 0)
// wait until the transaction is mined
await mintTx.wait()
@@ -42,8 +42,7 @@ describe('RockburgNFT', function () {
expect(metadata.name).to.equal('Musician #1')
expect(metadata.description).to.equal('Rockburg is a WIP card game on the blockchain.')
-
- expect(generatedSVG).to.equal(generateMusicianSVG(artistStats.name, artistStats.role, artistStats.skillPoints, artistStats.egoPoints, artistStats.lookPoints, artistStats.creativePoints))
+ expect(generatedSVG).to.equal(generateMusicianSVG(artistStats.name, 'Vocalist', artistStats.skillPoints, artistStats.egoPoints, artistStats.lookPoints, artistStats.creativePoints))
})
it('correctly generates metadata for a venue', async function () {