-
Notifications
You must be signed in to change notification settings - Fork 230
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Compatible with OpenZeppelin Contracts ^5.0.0 | ||
pragma solidity ^0.8.20; | ||
|
||
import "@openzeppelin/contracts/governance/Governor.sol"; | ||
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; | ||
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; | ||
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; | ||
import "@openzeppelin/contracts/interfaces/IERC6372.sol"; | ||
|
||
import "./xpNFT_V2.sol"; | ||
|
||
contract ZetaXPGov is Governor, GovernorSettings, GovernorCountingSimple, GovernorTimelockControl { | ||
bytes32 public tagValidToVote; | ||
ZetaXP_V2 public xpNFT; | ||
Check warning Code scanning / Slither State variables that could be declared immutable Warning
ZetaXPGov.xpNFT should be immutable
|
||
uint256 public quorumPercentage; // New state to store the quorum percentage | ||
Check warning Code scanning / Slither State variables that could be declared immutable Warning
ZetaXPGov.quorumPercentage should be immutable
|
||
|
||
constructor( | ||
ZetaXP_V2 _xpNFT, | ||
TimelockController _timelock, | ||
uint256 _quorumPercentage // Set the quorum percentage (e.g., 4%) | ||
) | ||
Governor("ZetaXPGov") | ||
GovernorSettings(7200 /* 1 day */, 50400 /* 1 week */, 0) | ||
GovernorTimelockControl(_timelock) | ||
{ | ||
xpNFT = _xpNFT; | ||
quorumPercentage = _quorumPercentage; | ||
} | ||
|
||
function setTagValidToVote(bytes32 _tag) external onlyGovernance { | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Parameter ZetaXPGov.setTagValidToVote(bytes32)._tag is not in mixedCase
|
||
tagValidToVote = _tag; | ||
} | ||
|
||
// Override the _getVotes function to apply custom weight based on NFT levels | ||
function _getVotes( | ||
address account, | ||
uint256 blockNumber, | ||
bytes memory params | ||
) internal view override returns (uint256) { | ||
uint256 tokenId = xpNFT.tokenByUserTag(account, tagValidToVote); | ||
uint256 level = xpNFT.getLevel(tokenId); | ||
|
||
// Assign voting weight based on NFT level | ||
if (level == 1) { | ||
return 1; // Rosegold | ||
} else if (level == 2) { | ||
return 2; // Black | ||
} else if (level == 3) { | ||
return 3; // Green | ||
} else { | ||
return 0; // Silver cannot vote | ||
} | ||
} | ||
|
||
// Manually implement the quorum function to define quorum based on the total percentage of votes | ||
function quorum(uint256 blockNumber) public view override returns (uint256) { | ||
uint256 totalSupply = xpNFT.totalSupply(); // Total number of NFTs in circulation | ||
return (totalSupply * quorumPercentage) / 100; // Quorum calculation based on the percentage | ||
} | ||
|
||
// Override the _execute function to resolve the conflict | ||
function _execute( | ||
uint256 proposalId, | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
bytes32 descriptionHash | ||
) internal override(Governor, GovernorTimelockControl) { | ||
super._execute(proposalId, targets, values, calldatas, descriptionHash); | ||
} | ||
|
||
// Override the supportsInterface function to resolve the conflict | ||
function supportsInterface( | ||
bytes4 interfaceId | ||
) public view override(Governor, GovernorTimelockControl) returns (bool) { | ||
return super.supportsInterface(interfaceId); | ||
} | ||
|
||
// Implementation of clock and CLOCK_MODE functions to comply with IERC6372 | ||
function clock() public view override returns (uint48) { | ||
return uint48(block.timestamp); | ||
} | ||
|
||
function CLOCK_MODE() public view override returns (string memory) { | ||
return "mode=timestamp"; | ||
} | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Function ZetaXPGov.CLOCK_MODE() is not in mixedCase
|
||
|
||
// The rest of the functions required to be overridden by Solidity | ||
|
||
function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) { | ||
return super.votingDelay(); | ||
} | ||
|
||
function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) { | ||
return super.votingPeriod(); | ||
} | ||
|
||
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { | ||
return super.state(proposalId); | ||
} | ||
|
||
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { | ||
return super.proposalThreshold(); | ||
} | ||
|
||
function _cancel( | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
bytes32 descriptionHash | ||
) internal override(Governor, GovernorTimelockControl) returns (uint256) { | ||
return super._cancel(targets, values, calldatas, descriptionHash); | ||
} | ||
|
||
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { | ||
return super._executor(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,38 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "./xpNFT.sol"; | ||
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; | ||
import "@openzeppelin/contracts/utils/Strings.sol"; | ||
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; | ||
|
||
contract ZetaXP_V2 is ERC721Upgradeable, Ownable2StepUpgradeable, EIP712Upgradeable { | ||
bytes32 private constant MINTORUPDATE_TYPEHASH = | ||
keccak256("MintOrUpdateNFT(address to,uint256 signatureExpiration,uint256 sigTimestamp,bytes32 tag)"); | ||
|
||
contract ZetaXP_V2 is ZetaXP { | ||
bytes32 private constant SETLEVEL_TYPEHASH = | ||
keccak256("SetLevel(uint256 tokenId,uint256 signatureExpiration,uint256 sigTimestamp,uint256 level)"); | ||
|
||
struct UpdateData { | ||
address to; | ||
bytes signature; | ||
uint256 signatureExpiration; | ||
uint256 sigTimestamp; | ||
bytes32 tag; | ||
} | ||
|
||
mapping(uint256 => uint256) public lastUpdateTimestampByTokenId; | ||
mapping(uint256 => bytes32) public tagByTokenId; | ||
mapping(address => mapping(bytes32 => uint256)) public tokenByUserTag; | ||
|
||
// Base URL for NFT images | ||
string public baseTokenURI; | ||
address public signerAddress; | ||
|
||
// Counter for the next token ID | ||
uint256 private _currentTokenId; | ||
|
||
struct SetLevelData { | ||
uint256 tokenId; | ||
bytes signature; | ||
|
@@ -16,12 +42,137 @@ contract ZetaXP_V2 is ZetaXP { | |
} | ||
|
||
mapping(uint256 => uint256) public levelByTokenId; | ||
|
||
// Event for New Mint | ||
event NFTMinted(address indexed sender, uint256 indexed tokenId, bytes32 tag); | ||
// Event for NFT Update | ||
event NFTUpdated(address indexed sender, uint256 indexed tokenId, bytes32 tag); | ||
// Event for Signer Update | ||
event SignerUpdated(address indexed signerAddress); | ||
// Event for Base URI Update | ||
event BaseURIUpdated(string baseURI); | ||
// Event for Level Set | ||
event LevelSet(address indexed sender, uint256 indexed tokenId, uint256 level); | ||
|
||
function version() public pure override returns (string memory) { | ||
error InvalidSigner(); | ||
error SignatureExpired(); | ||
error InvalidAddress(); | ||
error LengthMismatch(); | ||
error TransferNotAllowed(); | ||
error OutdatedSignature(); | ||
error TagAlreadyHoldByUser(); | ||
|
||
/// @custom:oz-upgrades-unsafe-allow constructor | ||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
function initialize( | ||
string memory name, | ||
string memory symbol, | ||
string memory baseTokenURI_, | ||
address signerAddress_, | ||
address owner | ||
) public initializer { | ||
if (signerAddress_ == address(0)) revert InvalidAddress(); | ||
__EIP712_init("ZetaXP", "1"); | ||
__ERC721_init(name, symbol); | ||
__Ownable_init(); | ||
transferOwnership(owner); | ||
baseTokenURI = baseTokenURI_; | ||
signerAddress = signerAddress_; | ||
_currentTokenId = 1; // Start token IDs from 1 | ||
} | ||
|
||
function version() public pure returns (string memory) { | ||
return "2.0.0"; | ||
} | ||
|
||
// Internal function to set the signer address | ||
function setSignerAddress(address signerAddress_) external onlyOwner { | ||
if (signerAddress_ == address(0)) revert InvalidAddress(); | ||
signerAddress = signerAddress_; | ||
emit SignerUpdated(signerAddress_); | ||
} | ||
|
||
// Set the base URI for tokens | ||
function setBaseURI(string calldata _uri) external onlyOwner { | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Parameter ZetaXP_V2.setBaseURI(string)._uri is not in mixedCase
|
||
baseTokenURI = _uri; | ||
emit BaseURIUpdated(_uri); | ||
} | ||
|
||
// The following functions are overrides required by Solidity. | ||
function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable) returns (string memory) { | ||
_requireMinted(tokenId); | ||
|
||
return string(abi.encodePacked(baseTokenURI, Strings.toString(tokenId))); | ||
} | ||
Check failure Code scanning / Slither ABI encodePacked Collision High
ZetaXP_V2.tokenURI(uint256) calls abi.encodePacked() with multiple dynamic arguments:
- string(abi.encodePacked(baseTokenURI,Strings.toString(tokenId))) |
||
|
||
function supportsInterface(bytes4 interfaceId) public view override(ERC721Upgradeable) returns (bool) { | ||
return super.supportsInterface(interfaceId); | ||
} | ||
|
||
function _verifyUpdateNFTSignature(uint256 tokenId, UpdateData memory updateData) private view { | ||
bytes32 structHash = keccak256( | ||
abi.encode( | ||
MINTORUPDATE_TYPEHASH, | ||
updateData.to, | ||
updateData.signatureExpiration, | ||
updateData.sigTimestamp, | ||
updateData.tag | ||
) | ||
); | ||
bytes32 constructedHash = _hashTypedDataV4(structHash); | ||
|
||
if (!SignatureChecker.isValidSignatureNow(signerAddress, constructedHash, updateData.signature)) { | ||
revert InvalidSigner(); | ||
} | ||
|
||
if (block.timestamp > updateData.signatureExpiration) revert SignatureExpired(); | ||
if (updateData.sigTimestamp <= lastUpdateTimestampByTokenId[tokenId]) revert OutdatedSignature(); | ||
} | ||
Check notice Code scanning / Slither Block timestamp Low
ZetaXP_V2._verifyUpdateNFTSignature(uint256,ZetaXP_V2.UpdateData) uses timestamp for comparisons
Dangerous comparisons: - block.timestamp > updateData.signatureExpiration |
||
|
||
function _updateNFT(uint256 tokenId, UpdateData memory updateData) internal { | ||
_verifyUpdateNFTSignature(tokenId, updateData); | ||
lastUpdateTimestampByTokenId[tokenId] = updateData.sigTimestamp; | ||
tagByTokenId[tokenId] = updateData.tag; | ||
tokenByUserTag[updateData.to][updateData.tag] = tokenId; | ||
} | ||
|
||
// External mint function with auto-incremented token ID | ||
function mintNFT(UpdateData memory mintData) external { | ||
uint256 newTokenId = _currentTokenId; | ||
_mint(mintData.to, newTokenId); | ||
|
||
if (tokenByUserTag[mintData.to][mintData.tag] != 0) revert TagAlreadyHoldByUser(); | ||
_updateNFT(newTokenId, mintData); | ||
|
||
emit NFTMinted(mintData.to, newTokenId, mintData.tag); | ||
|
||
_currentTokenId++; // Increment the token ID for the next mint | ||
} | ||
|
||
// External update function | ||
function updateNFT(uint256 tokenId, UpdateData memory updateData) external { | ||
address owner = ownerOf(tokenId); | ||
updateData.to = owner; | ||
bool willUpdateTag = tagByTokenId[tokenId] != updateData.tag; | ||
|
||
if (willUpdateTag) { | ||
if (tokenByUserTag[owner][updateData.tag] != 0) revert TagAlreadyHoldByUser(); | ||
tokenByUserTag[owner][tagByTokenId[tokenId]] = 0; | ||
} | ||
|
||
_updateNFT(tokenId, updateData); | ||
|
||
emit NFTUpdated(owner, tokenId, updateData.tag); | ||
} | ||
|
||
function _transfer(address from, address to, uint256 tokenId) internal override { | ||
revert TransferNotAllowed(); | ||
} | ||
|
||
// V2 Methods | ||
function _verifySetLevelSignature(SetLevelData memory data) private view { | ||
bytes32 structHash = keccak256( | ||
abi.encode(SETLEVEL_TYPEHASH, data.tokenId, data.signatureExpiration, data.sigTimestamp, data.level) | ||
|
@@ -47,4 +198,8 @@ contract ZetaXP_V2 is ZetaXP { | |
function getLevel(uint256 tokenId) external view returns (uint256) { | ||
return levelByTokenId[tokenId]; | ||
} | ||
|
||
function totalSupply() external view returns (uint256) { | ||
return _currentTokenId; | ||
} | ||
} | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Contract ZetaXP_V2 is not in CapWords
|