Skip to content

Commit

Permalink
feat: implement the governance smart-contract (#193)
Browse files Browse the repository at this point in the history
* feat: implement the governance smart-contract

* refactor

* fix totalSuuply

* basic test

* happy path test

* update test

* add test
  • Loading branch information
andresaiello authored Oct 28, 2024
1 parent 89275a0 commit 39e2ec2
Show file tree
Hide file tree
Showing 5 changed files with 386 additions and 6 deletions.
121 changes: 121 additions & 0 deletions packages/zevm-app-contracts/contracts/xp-nft/ZetaXPGov.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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;
uint256 public quorumPercentage; // New state to store the quorum percentage

constructor(
ZetaXP_V2 _xpNFT,
TimelockController _timelock,
uint256 _quorumPercentage, // Set the quorum percentage (e.g., 4%)
bytes32 _tag
)
Governor("ZetaXPGov")
GovernorSettings(7200 /* 1 day */, 50400 /* 1 week */, 0)
GovernorTimelockControl(_timelock)
{
xpNFT = _xpNFT;
quorumPercentage = _quorumPercentage;
tagValidToVote = _tag;
}

function setTagValidToVote(bytes32 _tag) external onlyGovernance {
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";
}

// 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();
}
}
6 changes: 3 additions & 3 deletions packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/utils/Strings.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";

contract ZetaXP is ERC721Upgradeable, Ownable2StepUpgradeable, EIP712Upgradeable {
bytes32 private constant MINTORUPDATE_TYPEHASH =
bytes32 internal constant MINTORUPDATE_TYPEHASH =
keccak256("MintOrUpdateNFT(address to,uint256 signatureExpiration,uint256 sigTimestamp,bytes32 tag)");

struct UpdateData {
Expand All @@ -28,7 +28,7 @@ contract ZetaXP is ERC721Upgradeable, Ownable2StepUpgradeable, EIP712Upgradeable
address public signerAddress;

// Counter for the next token ID
uint256 private _currentTokenId;
uint256 internal _currentTokenId;

// Event for New Mint
event NFTMinted(address indexed sender, uint256 indexed tokenId, bytes32 tag);
Expand Down Expand Up @@ -97,7 +97,7 @@ contract ZetaXP is ERC721Upgradeable, Ownable2StepUpgradeable, EIP712Upgradeable
return super.supportsInterface(interfaceId);
}

function _verify(uint256 tokenId, UpdateData memory updateData) private view {
function _verify(uint256 tokenId, UpdateData memory updateData) internal view {
bytes32 structHash = keccak256(
abi.encode(
MINTORUPDATE_TYPEHASH,
Expand Down
14 changes: 12 additions & 2 deletions packages/zevm-app-contracts/contracts/xp-nft/xpNFT_V2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.20;
import "./xpNFT.sol";

contract ZetaXP_V2 is ZetaXP {
bytes32 private constant SETLEVEL_TYPEHASH =
bytes32 internal constant SETLEVEL_TYPEHASH =
keccak256("SetLevel(uint256 tokenId,uint256 signatureExpiration,uint256 sigTimestamp,uint256 level)");

struct SetLevelData {
Expand All @@ -16,13 +16,19 @@ contract ZetaXP_V2 is ZetaXP {
}

mapping(uint256 => uint256) public levelByTokenId;

// Event for Level Set
event LevelSet(address indexed sender, uint256 indexed tokenId, uint256 level);

function version() public pure override returns (string memory) {
return "2.0.0";
}

function _verifySetLevelSignature(SetLevelData memory data) private view {
function _verifyUpdateNFTSignature(uint256 tokenId, UpdateData memory updateData) internal view {
_verify(tokenId, updateData);
}

function _verifySetLevelSignature(SetLevelData memory data) internal view {
bytes32 structHash = keccak256(
abi.encode(SETLEVEL_TYPEHASH, data.tokenId, data.signatureExpiration, data.sigTimestamp, data.level)
);
Expand All @@ -47,4 +53,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 - 1;
}
}
10 changes: 9 additions & 1 deletion packages/zevm-app-contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,15 @@ const config: HardhatUserConfig = {
{ version: "0.6.6" /** For uniswap v2 */ },
{ version: "0.8.7" },
{ version: "0.8.9" },
{ version: "0.8.20" },
{
settings: {
optimizer: {
enabled: true,
runs: 1000,
},
},
version: "0.8.20",
},
],
settings: {
/**
Expand Down
Loading

0 comments on commit 39e2ec2

Please sign in to comment.