Skip to content

Commit

Permalink
Add maxSupply to ConfigurableGuildRewardNFT
Browse files Browse the repository at this point in the history
  • Loading branch information
TomiOhl committed Apr 30, 2024
1 parent 47c678c commit 063213e
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 1 deletion.
12 changes: 12 additions & 0 deletions contracts/ConfigurableGuildRewardNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ contract ConfigurableGuildRewardNFT is
uint256 public constant SIGNATURE_VALIDITY = 1 hours;

address public factoryProxy;
uint256 public maxSupply;
uint256 public mintableAmountPerUser;

/// @notice The cid for tokenURI.
Expand All @@ -37,7 +38,10 @@ contract ConfigurableGuildRewardNFT is
IGuildRewardNFTFactory.ConfigurableNFTConfig memory nftConfig,
address factoryProxyAddress
) public initializer {
if (nftConfig.maxSupply <= 0) revert MaxSupplyZero();

cid = nftConfig.cid;
maxSupply = nftConfig.maxSupply;
mintableAmountPerUser = nftConfig.mintableAmountPerUser;
factoryProxy = factoryProxyAddress;

Expand Down Expand Up @@ -68,6 +72,8 @@ contract ConfigurableGuildRewardNFT is
uint256 firstTokenId = totalSupply();
uint256 lastTokenId = firstTokenId + amount - 1;

if (lastTokenId >= maxSupply) revert MaxSupplyReached(maxSupply);

for (uint256 tokenId = firstTokenId; tokenId <= lastTokenId; ) {
_safeMint(receiver, tokenId);

Expand Down Expand Up @@ -115,6 +121,12 @@ contract ConfigurableGuildRewardNFT is
else emit Unlocked(0);
}

function setMaxSupply(uint256 newMaxSupply) external onlyOwner {
if (newMaxSupply <= 0) revert MaxSupplyZero();
maxSupply = newMaxSupply;
emit MaxSupplyChanged(newMaxSupply);
}

function setMintableAmountPerUser(uint256 newAmount) external onlyOwner {
mintableAmountPerUser = newAmount;
emit MintableAmountPerUserChanged(newAmount);
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/IConfigurableGuildRewardNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
pragma solidity ^0.8.0;

import { IGuildRewardNFTFactory } from "./IGuildRewardNFTFactory.sol";
import { IMaxSupply } from "./IMaxSupply.sol";

/// @title An NFT distributed as a reward for Guild.xyz users.
interface IConfigurableGuildRewardNFT {
interface IConfigurableGuildRewardNFT is IMaxSupply {
/// @notice The address of the proxy to be used when interacting with the factory.
/// @dev Used to access the factory's address when interacting through minimal proxies.
/// @return factoryAddress The address of the factory.
Expand Down
2 changes: 2 additions & 0 deletions contracts/interfaces/IGuildRewardNFTFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface IGuildRewardNFTFactory {
/// @param tokenTreasury The address that will collect the prices of the minted tokens.
/// @param tokenFee The price of every mint in wei.
/// @param soulbound Whether the token should be soulbound.
/// @param maxSupply The maximum number of tokens that users will ever be able to mint.
/// @param mintableAmountPerUser The maximum amount a user will be able to mint from the deployed token.
struct ConfigurableNFTConfig {
string name;
Expand All @@ -28,6 +29,7 @@ interface IGuildRewardNFTFactory {
address payable treasury;
uint256 tokenFee;
bool soulbound;
uint256 maxSupply;
uint256 mintableAmountPerUser;
}

Expand Down
24 changes: 24 additions & 0 deletions contracts/interfaces/IMaxSupply.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMaxSupply {
/// @notice The maximum number of tokens that can ever be minted.
/// @return count The number of tokens.
function maxSupply() external view returns (uint256 count);

/// @notice Sets the maximum number of tokens that can ever be minted.
/// @dev Only callable by the owner.
/// @param newMaxSupply The number of tokens.
function setMaxSupply(uint256 newMaxSupply) external;

/// @notice Event emitted when the maxSupply is changed.
/// @param newMaxSupply The number of tokens.
event MaxSupplyChanged(uint256 newMaxSupply);

/// @notice Error thrown when the maximum supply attempted to be set is zero.
error MaxSupplyZero();

/// @notice Error thrown when the tokenId is higher than the maximum supply.
/// @param maxSupply The maximum supply of the token.
error MaxSupplyReached(uint256 maxSupply);
}
31 changes: 31 additions & 0 deletions docs/contracts/ConfigurableGuildRewardNFT.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ _Used to access the factory's address when interacting through minimal proxies._
| Name | Type | Description |
| ---- | ---- | ----------- |

### maxSupply

```solidity
uint256 maxSupply
```

The maximum number of tokens that can ever be minted.

#### Return Values

| Name | Type | Description |
| ---- | ---- | ----------- |

### mintableAmountPerUser

```solidity
Expand Down Expand Up @@ -149,6 +162,24 @@ Only callable by the owner.
| :--- | :--- | :---------- |
| `newLocked` | bool | Whether the token should be soulbound or not. |

### setMaxSupply

```solidity
function setMaxSupply(
uint256 newMaxSupply
) external
```

Sets the maximum number of tokens that can ever be minted.

Only callable by the owner.

#### Parameters

| Name | Type | Description |
| :--- | :--- | :---------- |
| `newMaxSupply` | uint256 | The number of tokens. |

### setMintableAmountPerUser

```solidity
Expand Down
1 change: 1 addition & 0 deletions docs/contracts/interfaces/IGuildRewardNFTFactory.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ struct ConfigurableNFTConfig {
address payable treasury;
uint256 tokenFee;
bool soulbound;
uint256 maxSupply;
uint256 mintableAmountPerUser;
}
```
Expand Down
77 changes: 77 additions & 0 deletions docs/contracts/interfaces/IMaxSupply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# IMaxSupply

## Functions

### maxSupply

```solidity
function maxSupply() external returns (uint256 count)
```

The maximum number of tokens that can ever be minted.

#### Return Values

| Name | Type | Description |
| :--- | :--- | :---------- |
| `count` | uint256 | The number of tokens. |
### setMaxSupply

```solidity
function setMaxSupply(
uint256 newMaxSupply
) external
```

Sets the maximum number of tokens that can ever be minted.

Only callable by the owner.

#### Parameters

| Name | Type | Description |
| :--- | :--- | :---------- |
| `newMaxSupply` | uint256 | The number of tokens. |

## Events

### MaxSupplyChanged

```solidity
event MaxSupplyChanged(
uint256 newMaxSupply
)
```

Event emitted when the maxSupply is changed.

#### Parameters

| Name | Type | Description |
| :--- | :--- | :---------- |
| `newMaxSupply` | uint256 | The number of tokens. |

## Custom errors

### MaxSupplyZero

```solidity
error MaxSupplyZero()
```

Error thrown when the maximum supply attempted to be set is zero.

### MaxSupplyReached

```solidity
error MaxSupplyReached(uint256 maxSupply)
```

Error thrown when the tokenId is higher than the maximum supply.

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| maxSupply | uint256 | The maximum supply of the token. |

1 change: 1 addition & 0 deletions scripts/deploy-configurable-nft-zksync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const nftConfig = {
treasury: "0x...", // The address that will receive the price paid for mints.
tokenFee: 0, // The price of every mint in wei.
soulbound: true, // Whether the token should be soulbound or not.
maxSupply: 10, // The maximum number of tokens that users will ever be able to mint.
mintableAmountPerUser: 1 // The maximum amount a user will be able to mint from the token.
};

Expand Down
1 change: 1 addition & 0 deletions scripts/deploy-configurable-nft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const nftConfig = {
treasury: "0x...", // The address that will receive the price paid for mints.
tokenFee: 0, // The price of every mint in wei.
soulbound: true, // Whether the token should be soulbound or not.
maxSupply: 10, // The maximum number of tokens that users will ever be able to mint.
mintableAmountPerUser: 1 // The maximum amount a user will be able to mint from the token.
};

Expand Down
51 changes: 51 additions & 0 deletions test/ConfigurableGuildRewardNFT.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const baseNFTConfig = {
cid: sampleCids[0],
tokenFee: ethers.parseEther("0.1"),
soulbound: true,
maxSupply: 10n,
mintableAmountPerUser: 1n
};
let nftConfig: typeof baseNFTConfig & { tokenOwner: string; treasury: string };
Expand Down Expand Up @@ -94,6 +95,7 @@ describe("ConfigurableGuildRewardNFT", () => {
expect(await nft.name()).to.eq(nftConfig.name);
expect(await nft.symbol()).to.eq(nftConfig.symbol);
expect(await nft.owner()).to.eq(wallet0.address);
expect(await nft.maxSupply()).to.eq(nftConfig.maxSupply);
expect(await nft.mintableAmountPerUser()).to.eq(nftConfig.mintableAmountPerUser);
expect(await nft.factoryProxy()).to.eq(await factory.getAddress());
});
Expand Down Expand Up @@ -223,6 +225,31 @@ describe("ConfigurableGuildRewardNFT", () => {
expect(userBalance1).to.eq(userBalance0 + sampleAmount);
});

it("should revert if the max supply is reached", async () => {
const newMaxSupply = sampleAmount;
await nft.setMaxSupply(newMaxSupply);
await nft.claim(sampleAmount, wallet0.address, sampleUserId, sampleSignedAt, sampleSignature, {
value: fee + nftConfig.tokenFee
});

const signature = await createSignature(
signer,
sampleAmount,
randomWallet.address,
sampleUserId + 1,
sampleSignedAt,
chainId,
await nft.getAddress()
);
await expect(
nft.claim(sampleAmount, randomWallet.address, sampleUserId + 1, sampleSignedAt, signature, {
value: fee + nftConfig.tokenFee
})
)
.to.be.revertedWithCustomError(nft, "MaxSupplyReached")
.withArgs(newMaxSupply);
});

it("should mint the token", async () => {
const totalSupply = await nft.totalSupply();
const tokenId = totalSupply;
Expand Down Expand Up @@ -492,6 +519,30 @@ describe("ConfigurableGuildRewardNFT", () => {
});
});

context("#setMaxSupply", () => {
it("should revert if maxSupply is attempted to be changed by anyone but the owner", async () => {
await expect((nft.connect(randomWallet) as Contract).setMaxSupply(5)).to.be.revertedWith(
"Ownable: caller is not the owner"
);
});

it("should revert if maxSupply is attempted to be set to 0", async () => {
await expect(nft.setMaxSupply(0)).to.be.revertedWithCustomError(nft, "MaxSupplyZero");
});

it("should update maxSupply", async () => {
const maxSupply = 5;
await nft.setMaxSupply(maxSupply);
const newMaxSupply = await nft.maxSupply();
expect(newMaxSupply).to.eq(maxSupply);
});

it("should emit MaxSupplyChanged event", async () => {
const maxSupply = 5;
await expect(nft.setMaxSupply(maxSupply)).to.emit(nft, "MaxSupplyChanged").withArgs(maxSupply);
});
});

context("#setMintableAmountPerUser", () => {
it("should revert if mintableAmountPerUser is attempted to be changed by anyone but the owner", async () => {
await expect((nft.connect(randomWallet) as Contract).setMintableAmountPerUser(5)).to.be.revertedWith(
Expand Down
3 changes: 3 additions & 0 deletions test/GuildRewardNFTFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const sampleSymbol = "TGP";
const cids = ["QmPaZD7i8TpLEeGjHtGoXe4mPKbRNNt8YTHH5nrKoqz9wJ", "QmcaGypWsmzaSQQGuExUjtyTRvZ2FF525Ww6PBNWWgkkLj"];
const sampleFee = 69;
const sampleSoulbound = true;
const sampleMaxSupply = 10n;
const sampleMintableAmountPerUser = 1;

// CONTRACTS
Expand Down Expand Up @@ -80,6 +81,7 @@ describe("GuildRewardNFTFactory", () => {
treasury: treasury.address,
tokenFee: sampleFee,
soulbound: sampleSoulbound,
maxSupply: sampleMaxSupply,
mintableAmountPerUser: sampleMintableAmountPerUser
});
const nftAddresses = await factory.getDeployedTokenContracts(wallet0.address);
Expand All @@ -88,6 +90,7 @@ describe("GuildRewardNFTFactory", () => {
expect(await nft.symbol()).to.eq(sampleSymbol);
expect(await nft.owner()).to.eq(randomWallet.address);
expect(await nft.fee()).to.eq(sampleFee);
expect(await nft.maxSupply()).to.eq(sampleMaxSupply);
expect(await nft.mintableAmountPerUser()).to.eq(sampleMintableAmountPerUser);
});

Expand Down

0 comments on commit 063213e

Please sign in to comment.