Skip to content

Commit

Permalink
Add delegation proofs (#156)
Browse files Browse the repository at this point in the history
* fix bal script

* fix .env

* fix script

* add auto-delegate

* add auto-delegation

* add 2 scripts
  • Loading branch information
julienbrg authored Dec 2, 2024
1 parent e279711 commit 08efcf8
Show file tree
Hide file tree
Showing 15 changed files with 350 additions and 54 deletions.
14 changes: 8 additions & 6 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
# Signer Private Key (signer[0])
SIGNER_PRIVATE_KEY="88888"

# Optimism Mainnet
OPTIMISM_MAINNET_RPC_ENDPOINT_URL="https://mainnet.optimism.io"
OPTIMISM_MAINNET_PRIVATE_KEY="88888"
OP_ETHERSCAN_API_KEY="88888"

# Base Mainnet
BASE_MAINNET_RPC_ENDPOINT_URL="https://mainnet.base.org"
BASE_MAINNET_PRIVATE_KEY="88888"
BASE_ETHERSCAN_API_KEY="88888"

# Sepolia
SEPOLIA_RPC_ENDPOINT_URL="https://ethereum-sepolia.publicnode.com"
SEPOLIA_PRIVATE_KEY="88888"
SEPOLIA_RPC_ENDPOINT_URL="https://sepolia.infura.io/v3/88888"
ETHERSCAN_API_KEY="88888"

# OP Sepolia
OP_SEPOLIA_RPC_ENDPOINT_URL="https://sepolia.optimism.io"
OP_SEPOLIA_PRIVATE_KEY="88888"

# Base Sepolia
BASE_SEPOLIA_RPC_ENDPOINT_URL="https://sepolia.base.org"
BASE_SEPOLIA_PRIVATE_KEY="88888"

# Addresses used when running scripts for testing cross-chain scenarios
ALICE="88888" # Alice // 0xD8a394e7d7894bDF2C57139fF17e5CBAa29Dd977
JUNGLE="88888" # Jungle Fever // 0xBDC0E420aB9ba144213588A95fa1E5e63CEFf1bE
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ Then you can add your DAO in [Tally](https://www.tally.xyz/) and/or spin up your

### Crosschain

Make sure the contracts are deployed from the same account.
Make sure that the deployer wallet address is funded on each notwork you want to deploy to:

```
pnpm bal
```

Then, you can go ahead and deploy:

```bash
pnpm crosschain:sepolia
Expand All @@ -61,7 +67,7 @@ pnpm crosschain:op-sepolia

Your DAO will be deployed on every networks at the same address.

Then you can follow these steps to verify that proofs of `safeMint`, `govBurn`, `setMetadata`, `setManifesto`, `setVotingDelay`, and `delegate` can be generated on source chain and claimed on foreign chain:
Then you can follow these steps to verify that proofs can be generated on home chain and claimed on foreign chain:

```bash
npx hardhat run scripts/propose.ts --network sepolia
Expand All @@ -81,6 +87,9 @@ npx hardhat run scripts/claim-manifesto-update.ts --network op-sepolia
npx hardhat run scripts/gov-voting-delay.ts --network sepolia
npx hardhat run scripts/verify-voting-delay-proof.ts --network sepolia
npx hardhat run scripts/claim-voting-delay.ts --network op-sepolia

npx hardhat run scripts/verify-delegation-proof.ts --network sepolia
npx hardhat run scripts/claim-delegation.ts --network op-sepolia
```

## Security
Expand Down
2 changes: 2 additions & 0 deletions contracts/NFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract NFT is
) ERC721(_name, _symbol) Ownable(initialOwner) EIP712(_name, "1") {
for (uint i; i < _firstMembers.length; i++) {
safeMint(_firstMembers[i], _uri);
_delegate(_firstMembers[i], _firstMembers[i]);
}
}

Expand All @@ -63,6 +64,7 @@ contract NFT is
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
_delegate(to, to);
}

/// @notice Updates the NFT ownership
Expand Down
102 changes: 100 additions & 2 deletions contracts/variants/crosschain/NFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import "./ProofHandler.sol";
* @title Cross-chain Membership NFT Contract
* @author Web3 Hackers Collective
* @notice Non-transferable NFT implementation for DAO membership with cross-chain capabilities
* @dev Extends OpenZeppelin's NFT standards with cross-chain operation support
* @dev Extends OpenZeppelin's NFT standards with cross-chain operation support and delegation
* @custom:security-contact [email protected]
*/
contract NFT is
Expand All @@ -41,7 +41,8 @@ contract NFT is
enum OperationType {
MINT,
BURN,
SET_METADATA
SET_METADATA,
DELEGATE
}

/// @notice Emitted when a membership is claimed
Expand All @@ -62,6 +63,12 @@ contract NFT is
/// @param nonce Operation sequence number
event MetadataUpdated(uint256 indexed tokenId, string newUri, uint256 nonce);

/// @notice Emitted when delegation is synchronized across chains
/// @param delegator The address delegating their voting power
/// @param delegatee The address receiving the delegation
/// @param nonce Operation sequence number
event DelegationSynced(address indexed delegator, address indexed delegatee, uint256 nonce);

/// @notice Restricts functions to home chain
modifier onlyHomeChain() {
require(block.chainid == home, "Operation only allowed on home chain");
Expand All @@ -88,6 +95,7 @@ contract NFT is
home = _home;
for (uint i; i < _firstMembers.length; i++) {
_govMint(_firstMembers[i], _uri);
_delegate(_firstMembers[i], _firstMembers[i]);
}
}

Expand Down Expand Up @@ -127,8 +135,20 @@ contract NFT is
emit MetadataUpdated(tokenId, uri, nonce);
}

/**
* @notice Delegates voting power to another address on home chain
* @dev Overrides ERC721Votes delegate function to add cross-chain functionality
* @param delegatee Address to delegate voting power to
*/
function delegate(address delegatee) public virtual override onlyHomeChain {
uint256 nonce = _proofStorage.incrementNonce(uint8(OperationType.DELEGATE));
_delegate(_msgSender(), delegatee);
emit DelegationSynced(_msgSender(), delegatee, nonce);
}

/**
* @notice Generates proof for NFT operations
* @dev Only callable on home chain
* @param operationType Type of operation
* @param params Operation parameters
* @return Encoded proof data
Expand All @@ -142,6 +162,31 @@ contract NFT is
return ProofHandler.generateProof(address(this), operationType, params, nextNonce);
}

/**
* @notice Generates proof for delegation
* @dev Only callable on home chain
* @param delegator Address delegating voting power
* @param delegatee Address receiving delegation
* @return Encoded proof data
*/
function generateDelegationProof(
address delegator,
address delegatee
) external view returns (bytes memory) {
require(block.chainid == home, "Proofs only generated on home chain");
uint256 nextNonce = _proofStorage.getNextNonce(uint8(OperationType.DELEGATE));
bytes memory params = abi.encode(delegator, delegatee);
return
ProofHandler.generateProof(
address(this),
uint8(OperationType.DELEGATE),
params,
nextNonce
);
}

// Claim operations

/**
* @notice Claims an NFT operation on a foreign chain
* @param proof Proof generated by home chain
Expand All @@ -163,9 +208,28 @@ contract NFT is
(uint256 tokenId, string memory uri) = abi.decode(params, (uint256, string));
_setTokenURI(tokenId, uri);
emit MetadataUpdated(tokenId, uri, nonce);
} else if (operationType == uint8(OperationType.DELEGATE)) {
(address delegator, address delegatee) = abi.decode(params, (address, address));
_delegate(delegator, delegatee);
emit DelegationSynced(delegator, delegatee, nonce);
}
}

/**
* @notice Claims a delegation operation on a foreign chain
* @param proof Proof generated by home chain
*/
function claimDelegation(bytes memory proof) external {
(uint8 operationType, bytes memory params, uint256 nonce) = ProofHandler
.verifyAndClaimProof(proof, address(this), _proofStorage);

require(operationType == uint8(OperationType.DELEGATE), "Invalid operation type");
(address delegator, address delegatee) = abi.decode(params, (address, address));

_delegate(delegator, delegatee);
emit DelegationSynced(delegator, delegatee, nonce);
}

/**
* @notice Internal function for minting without proof verification
* @param to Address to receive token
Expand All @@ -175,10 +239,19 @@ contract NFT is
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
_delegate(to, to);
}

// Required overrides

/**
* @notice Updates token data
* @dev Overrides ERC721 _update to make NFTs non-transferable
* @param to Recipient address
* @param tokenId Token ID
* @param auth Address authorized for transfer
* @return Previous owner address
*/
function _update(
address to,
uint256 tokenId,
Expand All @@ -188,29 +261,54 @@ contract NFT is
return super._update(to, tokenId, auth);
}

/**
* @notice Increments account balance
* @dev Internal override to maintain compatibility
* @param account Account to update
* @param value Amount to increase by
*/
function _increaseBalance(
address account,
uint128 value
) internal override(ERC721, ERC721Enumerable, ERC721Votes) {
super._increaseBalance(account, value);
}

/**
* @notice Gets token URI
* @param tokenId Token ID to query
* @return URI string
*/
function tokenURI(
uint256 tokenId
) public view override(ERC721, ERC721URIStorage) returns (string memory) {
return super.tokenURI(tokenId);
}

/**
* @notice Checks interface support
* @param interfaceId Interface identifier to check
* @return bool True if interface is supported
*/
function supportsInterface(
bytes4 interfaceId
) public view override(ERC721, ERC721Enumerable, ERC721URIStorage) returns (bool) {
return super.supportsInterface(interfaceId);
}

/**
* @notice Gets current timestamp
* @dev Used for voting snapshots
* @return Current block timestamp
*/
function clock() public view override returns (uint48) {
return uint48(block.timestamp);
}

/**
* @notice Gets clock mode description
* @return String indicating timestamp-based voting
*/
function CLOCK_MODE() public pure override returns (string memory) {
return "mode=timestamp";
}
Expand Down
26 changes: 7 additions & 19 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@ import * as dotenv from "dotenv"
dotenv.config()

const {
SIGNER_PRIVATE_KEY,
OPTIMISM_MAINNET_RPC_ENDPOINT_URL,
OPTIMISM_MAINNET_PRIVATE_KEY,
OP_ETHERSCAN_API_KEY,
BASE_MAINNET_RPC_ENDPOINT_URL,
BASE_MAINNET_PRIVATE_KEY,
BASE_ETHERSCAN_API_KEY,
SEPOLIA_RPC_ENDPOINT_URL,
SEPOLIA_PRIVATE_KEY,
ETHERSCAN_API_KEY,
OP_SEPOLIA_RPC_ENDPOINT_URL,
OP_SEPOLIA_PRIVATE_KEY,
BASE_SEPOLIA_RPC_ENDPOINT_URL,
BASE_SEPOLIA_PRIVATE_KEY
BASE_SEPOLIA_RPC_ENDPOINT_URL
} = process.env

const config: HardhatUserConfig = {
Expand All @@ -37,43 +33,35 @@ const config: HardhatUserConfig = {
SEPOLIA_RPC_ENDPOINT_URL ||
"https://ethereum-sepolia.publicnode.com",
accounts:
SEPOLIA_PRIVATE_KEY !== undefined ? [SEPOLIA_PRIVATE_KEY] : []
SIGNER_PRIVATE_KEY !== undefined ? [SIGNER_PRIVATE_KEY] : []
},
optimism: {
chainId: 10,
url:
OPTIMISM_MAINNET_RPC_ENDPOINT_URL ||
"https://mainnet.optimism.io",
accounts:
OPTIMISM_MAINNET_PRIVATE_KEY !== undefined
? [OPTIMISM_MAINNET_PRIVATE_KEY]
: []
SIGNER_PRIVATE_KEY !== undefined ? [SIGNER_PRIVATE_KEY] : []
},
base: {
chainId: 8453,
url: BASE_MAINNET_RPC_ENDPOINT_URL || "https://mainnet.base.org",
accounts:
BASE_MAINNET_PRIVATE_KEY !== undefined
? [BASE_MAINNET_PRIVATE_KEY]
: []
SIGNER_PRIVATE_KEY !== undefined ? [SIGNER_PRIVATE_KEY] : []
},
"op-sepolia": {
chainId: 11155420,
url:
OP_SEPOLIA_RPC_ENDPOINT_URL ||
"https://ethereum-sepolia.publicnode.com",
accounts:
OP_SEPOLIA_PRIVATE_KEY !== undefined
? [OP_SEPOLIA_PRIVATE_KEY]
: []
SIGNER_PRIVATE_KEY !== undefined ? [SIGNER_PRIVATE_KEY] : []
},
"base-sepolia": {
chainId: 84532,
url: BASE_SEPOLIA_RPC_ENDPOINT_URL || "https://sepolia.base.org",
accounts:
BASE_SEPOLIA_PRIVATE_KEY !== undefined
? [BASE_SEPOLIA_PRIVATE_KEY]
: []
SIGNER_PRIVATE_KEY !== undefined ? [SIGNER_PRIVATE_KEY] : []
}
},
solidity: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"deploy:base-sepolia": "hardhat deploy --network base-sepolia --reset",
"crosschain:sepolia": "hardhat deploy --network sepolia --tags CrosschainGov --reset",
"crosschain:op-sepolia": "hardhat deploy --network op-sepolia --tags CrosschainGov --reset",
"bal": "npx hardhat run scripts/check-my-balance.ts --network",
"bal": "npx hardhat run scripts/check-my-balance.ts",
"prettier": "prettier --write \"**/*.ts\"",
"prettier-check": "prettier --check \"**/*.ts\""
},
Expand Down
Loading

0 comments on commit 08efcf8

Please sign in to comment.