Skip to content

Commit

Permalink
Merge pull request #29 from windranger-io/afk/metadataUpdaterLocker
Browse files Browse the repository at this point in the history
Metadata updater role and metadata locking
  • Loading branch information
bitparadigm authored Feb 24, 2022
2 parents e7f7f8f + d14c897 commit b6ff3a2
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 16 deletions.
61 changes: 47 additions & 14 deletions contracts/TimelockedERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ contract TimelockedERC721 is
ERC721EnumerableUpgradeable
{
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant METADATA_MODIFIER_ROLE = keccak256("METADATA_MODIFIER_ROLE");

mapping(uint256 => bool) public lockedTokens;

uint256 public tokenUnlockTimestamp;

bool public metadataUpdatable = true;

string private _baseTokenURI;

event TokenUnlockTimestampSet(address adminAddress, uint256 timestamp);
Expand All @@ -44,11 +47,27 @@ contract TimelockedERC721 is
_;
}

modifier onlyMetadataModifier() {
require(
hasRole(METADATA_MODIFIER_ROLE, _msgSender()),
"Doesn't have metadata modifier role!"
);
_;
}

modifier futureTimestamp(uint256 timestamp_) {
require(timestamp_ > block.timestamp, "timestamp must be in future!");
_;
}

modifier metadataLocked() {
require(
metadataUpdatable,
"Metadata cannot be updated!"
);
_;
}

modifier tokenLocked(uint256 tokenId_) {
if (lockedTokens[tokenId_]) {
if (block.timestamp >= tokenUnlockTimestamp) {
Expand Down Expand Up @@ -80,6 +99,7 @@ contract TimelockedERC721 is

_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender());
_setupRole(METADATA_MODIFIER_ROLE, _msgSender());
}

function setTokenUnlockTimestamp(uint256 timestamp_)
Expand All @@ -101,10 +121,19 @@ contract TimelockedERC721 is
emit MinterAdded(minter_);
}

function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
function addMetadataModifier(address metadataUpdater_) external onlyAdmin {
grantRole(METADATA_MODIFIER_ROLE, metadataUpdater_);
}

function updateBaseTokenURI(string memory newBaseTokenURI_) external onlyMetadataModifier metadataLocked {
_baseTokenURI = newBaseTokenURI_;
}

function lockMetadata() external onlyMetadataModifier {
metadataUpdatable = false;
}


function lockToken(uint256 tokenId) external {
require(ownerOf(tokenId) == _msgSender(), "token not owned!");
require(!lockedTokens[tokenId], "token already locked!");
Expand All @@ -122,18 +151,6 @@ contract TimelockedERC721 is
}
}

function _beforeTokenTransfer(
address from_,
address to_,
uint256 tokenId_
)
internal
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
tokenLocked(tokenId_)
{
super._beforeTokenTransfer(from_, to_, tokenId_);
}

function supportsInterface(bytes4 interfaceId)
public
view
Expand All @@ -147,4 +164,20 @@ contract TimelockedERC721 is
{
return super.supportsInterface(interfaceId);
}

function _beforeTokenTransfer(
address from_,
address to_,
uint256 tokenId_
)
internal
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
tokenLocked(tokenId_)
{
super._beforeTokenTransfer(from_, to_, tokenId_);
}

function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
}
}
33 changes: 31 additions & 2 deletions test/TimelockedERC721.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('Timelocked ERC721', () => {
)
})

it('[sanity] admin has DEFAULT_ADMIN_ROLE and MINTER_ROLE', async () => {
it('[sanity] admin has DEFAULT_ADMIN_ROLE, MINTER_ROLE, and METADATA_UPDATER_ROLE', async () => {
expect(
await timelockedERC721.hasRole(
ethers.utils.keccak256(
Expand All @@ -56,12 +56,22 @@ describe('Timelocked ERC721', () => {
await admin.getAddress()
)
)

expect(
await timelockedERC721.hasRole(
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('MINTER_ROLE')),
await admin.getAddress()
)
)

expect(
await timelockedERC721.hasRole(
ethers.utils.keccak256(
ethers.utils.toUtf8Bytes('METADATA_UPDATER_ROLE')
),
await admin.getAddress()
)
)
})

it('Should be able to mint tokens to other addresses with minter role', async () => {
Expand All @@ -75,7 +85,7 @@ describe('Timelocked ERC721', () => {
}
})

it('Should be able to batchMint to an addres', async () => {
it('Should be able to batchMint to an address', async () => {
await timelockedERC721
.connect(admin)
.batchMint(await users[1].getAddress(), [90, 91, 92])
Expand Down Expand Up @@ -103,6 +113,25 @@ describe('Timelocked ERC721', () => {
).to.be.revertedWith("Doesn't have minter role!")
})

it('Should be able to update baseURI with metadata updater role', async () => {
await timelockedERC721.connect(admin).updateBaseTokenURI('0x000/')

expect(await timelockedERC721.tokenURI(0)).to.equal('0x000/0')
})

it('Should not be able to update baseURI without metadata updater role', async () => {
await expect(
timelockedERC721.connect(users[1]).updateBaseTokenURI('0x000/')
).to.be.revertedWith("Doesn't have metadata modifier role!")
})

it('Should not be able to update baseURI with metadata updater role when metadata is locked', async () => {
await timelockedERC721.connect(admin).lockMetadata()
await expect(
timelockedERC721.connect(admin).updateBaseTokenURI('0x')
).to.be.revertedWith('Metadata cannot be updated!')
})

it('Should be able to transfer tokens that have never been locked', async () => {
await timelockedERC721
.connect(users[0])
Expand Down

0 comments on commit b6ff3a2

Please sign in to comment.