From e522f3f886bfa834468ac492fc32589c621ea561 Mon Sep 17 00:00:00 2001 From: julienbrg Date: Tue, 17 Dec 2024 12:48:55 +0100 Subject: [PATCH 1/4] add delegation --- contracts/variants/crosschain/NFT.sol | 66 ++++++++- deploy/deploy-crosschain-gov.ts | 2 +- package.json | 3 +- scripts/claim-delegation.ts | 137 +++++++++++++++++++ scripts/delegation.sh | 38 ++++++ scripts/propose-burn.ts | 189 ++++++++++++++++++++++++-- scripts/propose-delegation.ts | 138 +++++++++++++++++++ scripts/verify-delegation-proof.ts | 106 +++++++++++++++ test/Gov-crosschain.ts | 6 +- 9 files changed, 666 insertions(+), 19 deletions(-) create mode 100644 scripts/claim-delegation.ts create mode 100755 scripts/delegation.sh create mode 100644 scripts/propose-delegation.ts create mode 100644 scripts/verify-delegation-proof.ts diff --git a/contracts/variants/crosschain/NFT.sol b/contracts/variants/crosschain/NFT.sol index a84bb3d..2779dae 100644 --- a/contracts/variants/crosschain/NFT.sol +++ b/contracts/variants/crosschain/NFT.sol @@ -34,12 +34,15 @@ contract NFT is /// @notice Tracks token existence on each chain mapping(uint256 => bool) public existsOnChain; + mapping(address => address) public crosschainDelegates; + /// @notice Operation types for cross-chain message verification /// @dev Used to differentiate between different types of cross-chain operations enum OperationType { - MINT, // Mint new token - BURN, // Burn existing token - SET_METADATA // Update token metadata + MINT, + BURN, + SET_METADATA, + SET_DELEGATION } /** @@ -68,6 +71,13 @@ contract NFT is */ event MetadataUpdated(uint256 indexed tokenId, string newUri); + event DelegationUpdated(address indexed delegator, address indexed delegate); + event CrosschainDelegationClaimed( + address indexed delegator, + address indexed delegate, + address indexed claimer + ); + /** * @notice Restricts operations to the home chain * @dev Used to ensure certain operations only occur on the chain where the contract was originally deployed @@ -189,6 +199,32 @@ contract NFT is return abi.encode(tokenId, uri, digest); } + function delegate(address delegatee) public virtual override onlyHomeChain { + super.delegate(delegatee); + crosschainDelegates[msg.sender] = delegatee; + emit DelegationUpdated(msg.sender, delegatee); + } + + function generateDelegationProof( + address delegator, + address delegatee + ) external view returns (bytes memory) { + require(block.chainid == home, "Proofs can only be generated on home chain"); + require(crosschainDelegates[delegator] == delegatee, "Invalid delegation state"); + + bytes32 message = keccak256( + abi.encodePacked( + address(this), + uint8(OperationType.SET_DELEGATION), + delegator, + delegatee + ) + ); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + + return abi.encode(delegator, delegatee, digest); + } + /** * @notice Claims a membership on a foreign chain * @dev Verifies proof and mints token on foreign chain @@ -260,6 +296,30 @@ contract NFT is emit MetadataUpdated(tokenId, uri); } + function claimDelegation(bytes memory proof) external { + (address delegator, address delegatee, bytes32 digest) = abi.decode( + proof, + (address, address, bytes32) + ); + + bytes32 message = keccak256( + abi.encodePacked( + address(this), + uint8(OperationType.SET_DELEGATION), + delegator, + delegatee + ) + ); + bytes32 expectedDigest = keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", message) + ); + require(digest == expectedDigest, "Invalid delegation proof"); + + _delegate(delegator, delegatee); + crosschainDelegates[delegator] = delegatee; + emit CrosschainDelegationClaimed(delegator, delegatee, msg.sender); + } + // Internal Functions /** diff --git a/deploy/deploy-crosschain-gov.ts b/deploy/deploy-crosschain-gov.ts index 1f00d44..422a689 100644 --- a/deploy/deploy-crosschain-gov.ts +++ b/deploy/deploy-crosschain-gov.ts @@ -19,7 +19,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployments, getNamedAccounts } = hre const { deterministic } = deployments const { deployer } = await getNamedAccounts() - const salt = hre.ethers.id("Dec-12-v2") + const salt = hre.ethers.id("Dec-17-v1") const homeChainId = 11155420 function wait(ms: number): Promise { diff --git a/package.json b/package.json index 0a97ee9..e3ca357 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "compile": "hardhat compile", "test": "hardhat test", - "test:all": "./scripts/deploy.sh && ./scripts/mint.sh && ./scripts/burn.sh && ./scripts/metadata.sh && ./scripts/manifesto.sh && ./scripts/voting-delay.sh", + "test:all": "./scripts/mint.sh && ./scripts/burn.sh && ./scripts/metadata.sh && ./scripts/manifesto.sh && ./scripts/voting-delay.sh && ./scripts/delegation.sh", "test:crosschain": "hardhat test test/Gov-crosschain.ts", "deploy:optimism": "hardhat deploy --network optimism --reset", "deploy:base": "hardhat deploy --network base --reset", @@ -16,6 +16,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", + "crosschain:arbitrum-sepolia": "hardhat deploy --network arbitrum-sepolia --tags CrosschainGov --reset", "deploy:all": "./scripts/deploy.sh", "bal": "npx hardhat run scripts/check-my-balance.ts", "verify:setup": "hardhat run scripts/verify-crosschain-setup.ts", diff --git a/scripts/claim-delegation.ts b/scripts/claim-delegation.ts new file mode 100644 index 0000000..4d4cd0e --- /dev/null +++ b/scripts/claim-delegation.ts @@ -0,0 +1,137 @@ +import hre, { ethers } from "hardhat" +import { NFT__factory } from "../typechain-types/factories/contracts/variants/crosschain/NFT__factory" +import * as fs from "fs" +import * as path from "path" +import color from "cli-color" +var msg = color.xterm(39).bgXterm(128) + +function getDeployedAddress(network: string, contractName: string): string { + try { + const deploymentPath = path.join( + __dirname, + "..", + "deployments", + network, + `${contractName}.json` + ) + const deployment = JSON.parse(fs.readFileSync(deploymentPath, "utf8")) + return deployment.address + } catch (error) { + throw new Error( + `Failed to read deployment for ${contractName} on ${network}: ${error}` + ) + } +} + +function getProofFromData(): string { + try { + const dataPath = path.join(__dirname, "..", "data.json") + const data = JSON.parse(fs.readFileSync(dataPath, "utf8")) + return data.proof + } catch (error) { + throw new Error(`Failed to read proof from data.json: ${error}`) + } +} + +async function main() { + const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY + const ALICE_PRIVATE_KEY = process.env.ALICE + if (!SIGNER_PRIVATE_KEY || !ALICE_PRIVATE_KEY) { + throw new Error( + "Please set SIGNER_PRIVATE_KEY and ALICE private key in your .env file" + ) + } + const ALICE_ADDRESS = new ethers.Wallet(ALICE_PRIVATE_KEY).address + + const networkName = hre.network.name + const NFT_ADDRESS = getDeployedAddress(networkName, "CrosschainNFT") + console.log("Using NFT contract address:", msg(NFT_ADDRESS)) + + const provider = new ethers.JsonRpcProvider( + networkName === "op-sepolia" + ? process.env.OP_SEPOLIA_RPC_ENDPOINT_URL + : process.env.ARBITRUM_SEPOLIA_RPC_ENDPOINT_URL + ) + const signer = new ethers.Wallet(SIGNER_PRIVATE_KEY, provider) + + const nft = NFT__factory.connect(NFT_ADDRESS, signer) + + // Verify we're NOT on home chain + const homeChain = await nft.home() + const currentChainId = await provider.getNetwork().then(n => n.chainId) + if (homeChain === BigInt(currentChainId)) { + throw new Error( + `Cannot claim on home chain. Please use a different network.` + ) + } + + const proof = getProofFromData() + console.log("\nUsing delegation proof:", msg(proof)) + + // Decode and validate proof before submitting + const [delegator, delegate, digest] = + ethers.AbiCoder.defaultAbiCoder().decode( + ["address", "address", "bytes32"], + proof + ) + console.log("\nDecoded proof validation:") + console.log("Delegator:", msg(delegator)) + console.log("Delegate:", msg(delegate)) + + // Simulate the claim first + console.log("\nSimulating delegation claim...") + try { + await nft.claimDelegation.staticCall(proof) + console.log(color.green("✅ Simulation successful")) + } catch (error: any) { + console.error(color.red("Simulation failed:"), error.message) + throw error + } + + // Execute the actual claim + console.log("\nSubmitting delegation claim transaction...") + const tx = await nft.claimDelegation(proof, { + gasLimit: 500000 + }) + + console.log("Transaction submitted:", msg(tx.hash)) + const receipt = await tx.wait() + + if (receipt) { + const event = receipt.logs.find(log => { + try { + return ( + nft.interface.parseLog(log)?.name === + "CrosschainDelegationClaimed" + ) + } catch { + return false + } + }) + + if (event) { + const parsedEvent = nft.interface.parseLog(event) + console.log("\nDelegation claim event details:") + console.log("Delegator:", msg(parsedEvent?.args?.delegator)) + console.log("Delegate:", msg(parsedEvent?.args?.delegate)) + console.log("Claimer:", msg(parsedEvent?.args?.claimer)) + } + } + + // Verify final delegation state + const currentDelegate = await nft.delegates(signer.address) + console.log("\nVerifying final delegation state:") + console.log("Signer's current delegate:", msg(currentDelegate)) + console.log("Expected delegate (Alice):", msg(ALICE_ADDRESS)) + + console.log( + color.green( + "\n✅ Cross-chain delegation claimed and verified successfully!" + ) + ) +} + +main().catch(error => { + console.error("\nError:", error) + process.exitCode = 1 +}) diff --git a/scripts/delegation.sh b/scripts/delegation.sh new file mode 100755 index 0000000..6bf51f5 --- /dev/null +++ b/scripts/delegation.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Color codes +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Starting cross-chain delegation process...${NC}\n" + +# Create delegation on OP Sepolia (home chain) +echo -e "\n${BLUE}Creating delegation on OP Sepolia...${NC}" +if npx hardhat run scripts/propose-delegation.ts --network op-sepolia; then + echo -e "${GREEN}✓ Delegation creation successful${NC}" +else + echo -e "${RED}✗ Delegation creation failed${NC}" + exit 1 +fi + +# Generate delegation proof from OP Sepolia +echo -e "\n${BLUE}Generating delegation proof from OP Sepolia...${NC}" +if npx hardhat run scripts/verify-delegation-proof.ts --network op-sepolia; then + echo -e "${GREEN}✓ Delegation proof generation successful${NC}" +else + echo -e "${RED}✗ Delegation proof generation failed${NC}" + exit 1 +fi + +# Claim delegation on Arbitrum Sepolia +echo -e "\n${BLUE}Claiming delegation on Arbitrum Sepolia...${NC}" +if npx hardhat run scripts/claim-delegation.ts --network arbitrum-sepolia; then + echo -e "${GREEN}✓ Delegation claim successful${NC}" + echo -e "\n${GREEN}✓ All delegation steps completed successfully!${NC}" + exit 0 +else + echo -e "${RED}✗ Delegation claim failed${NC}" + exit 1 +fi \ No newline at end of file diff --git a/scripts/propose-burn.ts b/scripts/propose-burn.ts index a5d6c10..2f3fdf3 100644 --- a/scripts/propose-burn.ts +++ b/scripts/propose-burn.ts @@ -3,6 +3,8 @@ import { Gov__factory } from "../typechain-types/factories/contracts/variants/cr import { NFT__factory } from "../typechain-types/factories/contracts/variants/crosschain/NFT__factory" import * as fs from "fs" import * as path from "path" +import color from "cli-color" +var msg = color.xterm(39).bgXterm(128) function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)) @@ -26,6 +28,20 @@ function getDeployedAddress(network: string, contractName: string): string { } } +function getProposalState(state: number): string { + const states = [ + "Pending", + "Active", + "Canceled", + "Defeated", + "Succeeded", + "Queued", + "Expired", + "Executed" + ] + return states[state] +} + async function main() { const ALICE_PRIVATE_KEY = process.env.ALICE const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY @@ -44,8 +60,8 @@ async function main() { const GOV_ADDRESS = getDeployedAddress(networkName, "CrosschainGov") console.log("Using contract addresses:") - console.log("NFT:", NFT_ADDRESS) - console.log("Gov:", GOV_ADDRESS) + console.log("NFT:", msg(NFT_ADDRESS)) + console.log("Gov:", msg(GOV_ADDRESS)) const provider = new ethers.JsonRpcProvider( networkName === "op-sepolia" @@ -55,18 +71,18 @@ async function main() { const aliceSigner = new ethers.Wallet(ALICE_PRIVATE_KEY, provider) const signerZero = new ethers.Wallet(SIGNER_PRIVATE_KEY, provider) - console.log("Using address for proposals:", aliceSigner.address) - console.log("Using address for execution:", signerZero.address) + console.log("Using address for proposals:", msg(aliceSigner.address)) + console.log("Using address for execution:", msg(signerZero.address)) const gov = Gov__factory.connect(GOV_ADDRESS, aliceSigner) const nft = NFT__factory.connect(NFT_ADDRESS, aliceSigner) // Token ID to burn - const tokenIdToBurn = 2n // Adjust as needed + const tokenIdToBurn = 2n // Check current voting power const votingPower = await nft.getVotes(aliceSigner.address) - console.log("Current voting power:", votingPower) + console.log("Current voting power:", votingPower.toString()) if (votingPower === 0n) { console.log("Delegating voting power...") @@ -93,14 +109,160 @@ async function main() { const calldatas = [burnCall] const description = "Burn member NFT " + Date.now() - console.log("Creating proposal with:") - console.log("- Target:", targets[0]) + console.log("\nCreating proposal with:") + console.log("- Target:", msg(targets[0])) console.log("- Value:", values[0]) - console.log("- Calldata:", calldatas[0]) + console.log("- Calldata:", msg(calldatas[0])) console.log("- Description:", description) - // Rest of the proposal creation and execution logic remains the same as in propose.ts - // ... [Same voting and execution logic as in propose.ts] + const tx = await gov.propose(targets, values, calldatas, description) + console.log("\nProposal transaction submitted:", msg(tx.hash)) + + const receipt = await tx.wait() + let proposalId = null + if (receipt) { + console.log("Proposal confirmed in block:", receipt.blockNumber) + proposalId = + receipt.logs[0] instanceof ethers.EventLog + ? receipt.logs[0].args?.[0] + : null + if (proposalId) { + console.log("Proposal ID:", msg(proposalId)) + } + } + + if (proposalId) { + console.log("\nChecking proposal state before voting...") + const state = await gov.state(proposalId) + console.log( + "Current proposal state:", + msg(getProposalState(Number(state))) + ) + + let currentState = Number(state) + let attempts = 0 + const maxAttempts = 10 + + while (currentState === 0 && attempts < maxAttempts) { + console.log("Waiting for proposal to become active...") + await sleep(30000) + const newState = await gov.state(proposalId) + currentState = Number(newState) + console.log( + "Current proposal state:", + msg(getProposalState(currentState)) + ) + attempts++ + } + + if (currentState === 1) { + console.log("\nCasting vote...") + const voteTx = await gov.castVote(proposalId, 1) + await voteTx.wait() + console.log(msg("Vote cast successfully!")) + + let isSucceeded = false + console.log("\nStarting to check proposal state...") + + while (!isSucceeded) { + const state = await gov.state(proposalId) + console.log( + "Current proposal state:", + msg(getProposalState(Number(state))) + ) + + if (getProposalState(Number(state)) === "Succeeded") { + isSucceeded = true + console.log( + "\nProposal succeeded! Preparing for execution..." + ) + + try { + console.log("\nSimulating execution...") + await gov + .connect(signerZero) + .execute.staticCall( + targets, + values, + calldatas, + ethers.id(description) + ) + console.log("Simulation successful") + + console.log("\nSubmitting execution transaction...") + const executeTx = await gov + .connect(signerZero) + .execute( + targets, + values, + calldatas, + ethers.id(description), + { gasLimit: 500000 } + ) + + console.log( + "Execution transaction submitted:", + msg(executeTx.hash) + ) + const executeReceipt = await executeTx.wait() + console.log( + "Proposal executed successfully in block:", + executeReceipt?.blockNumber + ) + + // Verify the NFT was burned + try { + await nft.ownerOf(tokenIdToBurn) + console.log( + "Warning: Token still exists after burn attempt" + ) + } catch (error: any) { + if ( + error.message.includes("nonexistent token") + ) { + console.log( + msg( + `✅ Token ${tokenIdToBurn} successfully burned` + ) + ) + } else { + console.log( + "Error verifying token burn:", + error + ) + } + } + + break + } catch (error: any) { + console.error("\nError executing proposal:", error) + if (error.data) { + try { + const decodedError = + gov.interface.parseError(error.data) + console.error( + "Decoded error:", + decodedError + ) + } catch (e) { + console.error("Raw error data:", error.data) + } + } + throw error + } + } + + console.log("Waiting 1 minute before next state check...") + await sleep(60000) + } + } else { + console.log( + `Could not reach active state. Current state: ${getProposalState( + currentState + )}` + ) + } + } } catch (error: any) { console.error("\nError details:", error) if (error.data) { @@ -114,3 +276,8 @@ async function main() { throw error } } + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/propose-delegation.ts b/scripts/propose-delegation.ts new file mode 100644 index 0000000..d0254b6 --- /dev/null +++ b/scripts/propose-delegation.ts @@ -0,0 +1,138 @@ +import hre, { ethers } from "hardhat" +import { NFT__factory } from "../typechain-types/factories/contracts/variants/crosschain/NFT__factory" +import * as fs from "fs" +import * as path from "path" +import color from "cli-color" +var msg = color.xterm(39).bgXterm(128) + +function getDeployedAddress(network: string, contractName: string): string { + try { + const deploymentPath = path.join( + __dirname, + "..", + "deployments", + network, + `${contractName}.json` + ) + const deployment = JSON.parse(fs.readFileSync(deploymentPath, "utf8")) + return deployment.address + } catch (error) { + throw new Error( + `Failed to read deployment for ${contractName} on ${network}: ${error}` + ) + } +} + +async function main() { + const BOB_PRIVATE_KEY = process.env.BOB + + const ALICE_PRIVATE_KEY = process.env.ALICE + if (!BOB_PRIVATE_KEY || !ALICE_PRIVATE_KEY) { + throw new Error( + "Please set BOB and ALICE private keys in your .env file" + ) + } + const ALICE_ADDRESS = new ethers.Wallet(ALICE_PRIVATE_KEY).address + + if (!BOB_PRIVATE_KEY || !ALICE_ADDRESS) { + throw new Error( + "Please set BOB private key and ALICE_ADDRESS in your .env file" + ) + } + + const networkName = hre.network.name + const NFT_ADDRESS = getDeployedAddress(networkName, "CrosschainNFT") + + console.log("Using NFT contract address:", msg(NFT_ADDRESS)) + + const provider = new ethers.JsonRpcProvider( + networkName === "op-sepolia" + ? process.env.OP_SEPOLIA_RPC_ENDPOINT_URL + : process.env.ARBITRUM_SEPOLIA_RPC_ENDPOINT_URL + ) + const bobSigner = new ethers.Wallet(BOB_PRIVATE_KEY, provider) + + const nft = NFT__factory.connect(NFT_ADDRESS, bobSigner) + + console.log("\nBob's address:", msg(bobSigner.address)) + console.log("Alice's address:", msg(ALICE_ADDRESS)) + + // Check if we're on home chain + const homeChain = await nft.home() + const currentChainId = await provider.getNetwork().then(n => n.chainId) + if (homeChain !== BigInt(currentChainId)) { + throw new Error( + `Must delegate on home chain (${homeChain}). Current chain: ${currentChainId}` + ) + } + + // Check if Bob owns any NFTs + const balance = await nft.balanceOf(bobSigner.address) + if (balance === 0n) { + throw new Error("Bob does not own any NFTs") + } + + console.log("\nDelegating Bob's voting power to Alice...") + + // First check if simulation works + try { + await nft.delegate.staticCall(ALICE_ADDRESS) + console.log("✅ Delegation simulation successful") + } catch (error: any) { + console.error("Delegation simulation failed:", error.message) + throw error + } + + // Execute the actual delegation + const tx = await nft.delegate(ALICE_ADDRESS, { + gasLimit: 500000 + }) + console.log("Transaction submitted:", msg(tx.hash)) + + const receipt = await tx.wait() + + if (receipt) { + const event = receipt.logs.find(log => { + try { + return nft.interface.parseLog(log)?.name === "DelegateChanged" + } catch { + return false + } + }) + + if (event) { + const parsedEvent = nft.interface.parseLog(event) + console.log("\nDelegation event details:") + console.log("Delegator:", msg(parsedEvent?.args?.delegator)) + console.log("From:", msg(parsedEvent?.args?.fromDelegate)) + console.log("To:", msg(parsedEvent?.args?.toDelegate)) + } + } + + console.log("\nDelegation completed on home chain") + + // Verify delegation + const currentDelegate = await nft.delegates(bobSigner.address) + console.log("\nVerifying delegation:") + console.log("Bob's current delegate:", msg(currentDelegate)) + console.log("Expected delegate (Alice):", msg(ALICE_ADDRESS)) + + if (currentDelegate.toLowerCase() === ALICE_ADDRESS.toLowerCase()) { + console.log(color.green("\n✅ Delegation verified successfully")) + } else { + console.log(color.red("\n❌ Delegation verification failed")) + throw new Error("Delegation verification failed") + } + + // Get Bob's voting power after delegation + const votingPower = await nft.getVotes(bobSigner.address) + const aliceVotingPower = await nft.getVotes(ALICE_ADDRESS) + console.log("\nFinal voting power:") + console.log("Bob's voting power:", votingPower.toString()) + console.log("Alice's voting power:", aliceVotingPower.toString()) +} + +main().catch(error => { + console.error("\nError:", error) + process.exitCode = 1 +}) diff --git a/scripts/verify-delegation-proof.ts b/scripts/verify-delegation-proof.ts new file mode 100644 index 0000000..f6bee66 --- /dev/null +++ b/scripts/verify-delegation-proof.ts @@ -0,0 +1,106 @@ +import hre, { ethers } from "hardhat" +import { NFT__factory } from "../typechain-types/factories/contracts/variants/crosschain/NFT__factory" +import * as fs from "fs" +import * as path from "path" +import color from "cli-color" +var msg = color.xterm(39).bgXterm(128) + +function getDeployedAddress(network: string, contractName: string): string { + try { + const deploymentPath = path.join( + __dirname, + "..", + "deployments", + network, + `${contractName}.json` + ) + const deployment = JSON.parse(fs.readFileSync(deploymentPath, "utf8")) + return deployment.address + } catch (error) { + throw new Error( + `Failed to read deployment for ${contractName} on ${network}: ${error}` + ) + } +} + +async function main() { + const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY + const ALICE_PRIVATE_KEY = process.env.ALICE + if (!SIGNER_PRIVATE_KEY || !ALICE_PRIVATE_KEY) { + throw new Error( + "Please set SIGNER_PRIVATE_KEY and ALICE private key in your .env file" + ) + } + const ALICE_ADDRESS = new ethers.Wallet(ALICE_PRIVATE_KEY).address + + const networkName = hre.network.name + const NFT_ADDRESS = getDeployedAddress(networkName, "CrosschainNFT") + console.log("Using NFT contract address:", msg(NFT_ADDRESS)) + + const provider = new ethers.JsonRpcProvider( + networkName === "op-sepolia" + ? process.env.OP_SEPOLIA_RPC_ENDPOINT_URL + : process.env.ARBITRUM_SEPOLIA_RPC_ENDPOINT_URL + ) + + const signer = new ethers.Wallet(SIGNER_PRIVATE_KEY, provider) + const nft = NFT__factory.connect(NFT_ADDRESS, signer) + + // Verify we're on home chain + const homeChain = await nft.home() + const currentChainId = await provider.getNetwork().then(n => n.chainId) + if (homeChain !== BigInt(currentChainId)) { + throw new Error( + `Must generate proof on home chain (${homeChain}). Current chain: ${currentChainId}` + ) + } + + // Verify current delegation state + const currentDelegate = await nft.delegates(signer.address) + if (currentDelegate.toLowerCase() !== ALICE_ADDRESS.toLowerCase()) { + throw new Error( + `Invalid delegation state. Signer's current delegate: ${currentDelegate}, Expected: ${ALICE_ADDRESS}` + ) + } + + console.log("\nGenerating delegation proof...") + console.log("Delegator (Signer):", msg(signer.address)) + console.log("Delegate (Alice):", msg(ALICE_ADDRESS)) + + try { + const proof = await nft.generateDelegationProof( + signer.address, + ALICE_ADDRESS + ) + console.log("\nProof:", msg(proof)) + + const data = { proof } + fs.writeFileSync( + path.join(__dirname, "..", "data.json"), + JSON.stringify(data, null, 2) + ) + console.log(color.green("\n✅ Delegation proof saved to data.json")) + + // Validate the proof format + const [delegator, delegate, digest] = + ethers.AbiCoder.defaultAbiCoder().decode( + ["address", "address", "bytes32"], + proof + ) + console.log("\nDecoded proof validation:") + console.log("Delegator:", msg(delegator)) + console.log("Delegate:", msg(delegate)) + console.log("Digest:", msg(digest)) + } catch (error: any) { + console.error( + color.red("\nFailed to generate or save proof:"), + error.message + ) + throw error + } +} + +main().catch(error => { + console.error("\nError:", error) + process.exitCode = 1 +}) diff --git a/test/Gov-crosschain.ts b/test/Gov-crosschain.ts index 3e141b2..30e116f 100644 --- a/test/Gov-crosschain.ts +++ b/test/Gov-crosschain.ts @@ -566,7 +566,7 @@ describe("Crosschain Gov", function () { }) }) - xdescribe("Delegation Transfers", function () { + describe("Delegation Transfers", function () { it("should properly transfer voting power when changing delegates", async function () { // Initial delegation await nft.connect(alice).delegate(david.address) @@ -575,7 +575,7 @@ describe("Crosschain Gov", function () { // Change delegation await nft.connect(alice).delegate(bob.address) expect(await nft.getVotes(david.address)).to.equal(0) - expect(await nft.getVotes(bob.address)).to.equal(1) + expect(await nft.getVotes(bob.address)).to.equal(2) }) it("should maintain zero voting power for non-holders across multiple delegations", async function () { @@ -588,7 +588,7 @@ describe("Crosschain Gov", function () { await nft.connect(charlie).delegate(bob.address) expect(await nft.getVotes(david.address)).to.equal(0) - expect(await nft.getVotes(alice.address)).to.equal(0) + expect(await nft.getVotes(alice.address)).to.equal(1) // Bob should maintain only his original voting power if any expect(await nft.getVotes(bob.address)).to.equal( initialBobVotes From 1910213f2e38bb2584ddf28f89339ff6131bd37c Mon Sep 17 00:00:00 2001 From: julienbrg Date: Tue, 17 Dec 2024 12:56:04 +0100 Subject: [PATCH 2/4] clean up --- README.md | 96 +++++++++++--------------------------- scripts/burn.sh | 38 --------------- scripts/delegation.sh | 38 --------------- scripts/deploy-and-test.sh | 0 scripts/deploy.sh | 42 ----------------- scripts/manifesto.sh | 38 --------------- scripts/metadata.sh | 38 --------------- scripts/mint.sh | 51 -------------------- scripts/voting-delay.sh | 38 --------------- 9 files changed, 28 insertions(+), 351 deletions(-) delete mode 100755 scripts/burn.sh delete mode 100755 scripts/delegation.sh create mode 100644 scripts/deploy-and-test.sh delete mode 100755 scripts/deploy.sh delete mode 100755 scripts/manifesto.sh delete mode 100755 scripts/metadata.sh delete mode 100755 scripts/mint.sh delete mode 100755 scripts/voting-delay.sh diff --git a/README.md b/README.md index c6b3805..efa55df 100644 --- a/README.md +++ b/README.md @@ -94,84 +94,44 @@ The following functions are `onlyOwner`, and since the NFT contract ownership is ### Crosschain -Make sure the main account has sufficient balance on OP Sepolia and Arbitrum Sepolia: +Make sure the main account, Bob and Alice have sufficient balance on OP Sepolia and Arbitrum Sepolia: ``` -pnpm bal -``` - -Deploy: - -``` -pnpm deploy:all -``` - -It will: - -- Deploy to OP Sepolia -- Deploy to Arbitrum Sepolia - -Add a member (mint): +# Deploy to OP Sepolia and Arbitrum Sepolia +pnpm deploy:op-sepolia +pnpm deploy:arbitrum-sepolia -``` -./scripts/mint.sh -``` - -It will: - -- Submit a proposal and add a member on OP Sepolia -- Generate a membership proof on OP Sepolia -- Claim that proof on Arbitrum Sepolia +# Add a new member +npx hardhat run scripts/propose.ts --network op-sepolia +npx hardhat run scripts/verify-proof.ts --network op-sepolia +npx hardhat run scripts/claim-membership.ts --network arbitrum-sepolia -Ban a member (burn): +# Ban a member +npx hardhat run scripts/propose-burn.ts --network op-sepolia +npx hardhat run scripts/verify-burn-proof.ts --network op-sepolia +npx hardhat run scripts/claim-burn.ts --network arbitrum-sepolia -``` -./scripts/burn.sh -``` +# Edit 1 membership NFT metadata +npx hardhat run scripts/propose-metadata.ts --network op-sepolia +npx hardhat run scripts/verify-metadata-proof.ts --network op-sepolia +npx hardhat run scripts/claim-metadata.ts --network arbitrum-sepolia -It will: +# Edit the manifesto +npx hardhat run scripts/propose-manifesto.ts --network op-sepolia +npx hardhat run scripts/verify-manifesto-proof.ts --network op-sepolia +npx hardhat run scripts/claim-manifesto.ts --network arbitrum-sepolia -- Submit a proposal and ban a member on OP Sepolia -- Generate a burn proof on OP Sepolia -- Claim that proof on Arbitrum Sepolia +# Change 1 voting parameter +npx hardhat run scripts/propose-voting-delay.ts --network op-sepolia +npx hardhat run scripts/verify-voting-delay-proof.ts --network op-sepolia +npx hardhat run scripts/claim-voting-delay.ts --network arbitrum-sepolia -Edit membership NFT metadata: - -``` -./scripts/metadata.sh +# Change delegation +npx hardhat run scripts/propose-delegation.ts --network op-sepolia +npx hardhat run scripts/verify-delegation-proof.ts --network op-sepolia +npx hardhat run scripts/claim-delegation.ts --network arbitrum-sepolia ``` -It will: - -- Submit a proposal edit the NFT metadata of tokenId 1 on OP Sepolia -- Generate a metadata proof on OP Sepolia -- Claim that proof on Arbitrum Sepolia - -Edit the manifesto: - -``` -./scripts/manifesto.sh -``` - -It will: - -- Submit a proposal to edit the manifesto on OP Sepolia -- Generate a manifesto proof on OP Sepolia -- Claim that proof on Arbitrum Sepolia - -Change the voting delay: - -``` -./scripts/voting-delay.sh -``` - -It will: - -- Submit a proposal to change the voting delay on OP Sepolia -- Generate a voting delay proof on OP Sepolia -- Claim that proof on Arbitrum Sepolia - - ## Core Dependencies - Node [v20.9.0](https://nodejs.org/uk/blog/release/v20.9.0/) diff --git a/scripts/burn.sh b/scripts/burn.sh deleted file mode 100755 index 4c1e1c6..0000000 --- a/scripts/burn.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Starting cross-chain burn process...${NC}\n" - -# Create proposal on OP Sepolia -echo -e "\n${BLUE}Creating burn proposal on OP Sepolia...${NC}" -if npx hardhat run scripts/propose-burn.ts --network op-sepolia; then - echo -e "${GREEN}✓ Burn proposal creation successful${NC}" -else - echo -e "${RED}✗ Burn proposal creation failed${NC}" - exit 1 -fi - -# Generate burn proof from OP Sepolia -echo -e "\n${BLUE}Generating burn proof from OP Sepolia...${NC}" -if npx hardhat run scripts/verify-burn-proof.ts --network op-sepolia; then - echo -e "${GREEN}✓ Burn proof generation successful${NC}" -else - echo -e "${RED}✗ Burn proof generation failed${NC}" - exit 1 -fi - -# Claim burn on Arbitrum Sepolia -echo -e "\n${BLUE}Claiming burn on Arbitrum Sepolia...${NC}" -if npx hardhat run scripts/claim-burn.ts --network arbitrum-sepolia; then - echo -e "${GREEN}✓ Burn claim successful${NC}" - echo -e "\n${GREEN}✓ All burn steps completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Burn claim failed${NC}" - exit 1 -fi \ No newline at end of file diff --git a/scripts/delegation.sh b/scripts/delegation.sh deleted file mode 100755 index 6bf51f5..0000000 --- a/scripts/delegation.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Starting cross-chain delegation process...${NC}\n" - -# Create delegation on OP Sepolia (home chain) -echo -e "\n${BLUE}Creating delegation on OP Sepolia...${NC}" -if npx hardhat run scripts/propose-delegation.ts --network op-sepolia; then - echo -e "${GREEN}✓ Delegation creation successful${NC}" -else - echo -e "${RED}✗ Delegation creation failed${NC}" - exit 1 -fi - -# Generate delegation proof from OP Sepolia -echo -e "\n${BLUE}Generating delegation proof from OP Sepolia...${NC}" -if npx hardhat run scripts/verify-delegation-proof.ts --network op-sepolia; then - echo -e "${GREEN}✓ Delegation proof generation successful${NC}" -else - echo -e "${RED}✗ Delegation proof generation failed${NC}" - exit 1 -fi - -# Claim delegation on Arbitrum Sepolia -echo -e "\n${BLUE}Claiming delegation on Arbitrum Sepolia...${NC}" -if npx hardhat run scripts/claim-delegation.ts --network arbitrum-sepolia; then - echo -e "${GREEN}✓ Delegation claim successful${NC}" - echo -e "\n${GREEN}✓ All delegation steps completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Delegation claim failed${NC}" - exit 1 -fi \ No newline at end of file diff --git a/scripts/deploy-and-test.sh b/scripts/deploy-and-test.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/deploy.sh b/scripts/deploy.sh deleted file mode 100755 index c1ea5e4..0000000 --- a/scripts/deploy.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Starting cross-chain deployment process...${NC}\n" - -# Deploy to OP Sepolia -echo -e "${BLUE}Deploying to OP Sepolia...${NC}" -if pnpm crosschain:op-sepolia; then - echo -e "${GREEN}✓ OP Sepolia deployment successful${NC}" -else - echo -e "${RED}✗ OP Sepolia deployment failed${NC}" - exit 1 -fi - -# Wait a bit to ensure deployment is fully confirmed -echo -e "\n${BLUE}Waiting 30 seconds before proceeding to Arbitrum deployment...${NC}" -sleep 30 - -# Deploy to Arbitrum Sepolia -echo -e "\n${BLUE}Deploying to Arbitrum Sepolia...${NC}" -if pnpm crosschain:arbitrum-sepolia; then - echo -e "${GREEN}✓ Arbitrum Sepolia deployment successful${NC}" -else - echo -e "${RED}✗ Arbitrum Sepolia deployment failed${NC}" - exit 1 -fi - -# Run verification script -echo -e "\n${BLUE}Running cross-chain setup verification...${NC}" -if pnpm verify:setup; then - echo -e "${GREEN}✓ Cross-chain setup verification successful${NC}" - echo -e "\n${GREEN}✓ Deployment and verification completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Cross-chain setup verification failed${NC}" - exit 1 -fi \ No newline at end of file diff --git a/scripts/manifesto.sh b/scripts/manifesto.sh deleted file mode 100755 index c1ca9cc..0000000 --- a/scripts/manifesto.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Starting cross-chain manifesto update process...${NC}\n" - -# Create proposal on OP Sepolia -echo -e "\n${BLUE}Creating manifesto update proposal on OP Sepolia...${NC}" -if npx hardhat run scripts/propose-manifesto.ts --network op-sepolia; then - echo -e "${GREEN}✓ Manifesto update proposal creation successful${NC}" -else - echo -e "${RED}✗ Manifesto update proposal creation failed${NC}" - exit 1 -fi - -# Generate manifesto proof from OP Sepolia -echo -e "\n${BLUE}Generating manifesto proof from OP Sepolia...${NC}" -if npx hardhat run scripts/verify-manifesto-proof.ts --network op-sepolia; then - echo -e "${GREEN}✓ Manifesto proof generation successful${NC}" -else - echo -e "${RED}✗ Manifesto proof generation failed${NC}" - exit 1 -fi - -# Claim manifesto update on Arbitrum Sepolia -echo -e "\n${BLUE}Claiming manifesto update on Arbitrum Sepolia...${NC}" -if npx hardhat run scripts/claim-manifesto.ts --network arbitrum-sepolia; then - echo -e "${GREEN}✓ Manifesto update claim successful${NC}" - echo -e "\n${GREEN}✓ All manifesto update steps completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Manifesto update claim failed${NC}" - exit 1 -fi diff --git a/scripts/metadata.sh b/scripts/metadata.sh deleted file mode 100755 index acfa60c..0000000 --- a/scripts/metadata.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Starting cross-chain metadata update process...${NC}\n" - -# Create proposal on OP Sepolia -echo -e "\n${BLUE}Creating metadata update proposal on OP Sepolia...${NC}" -if npx hardhat run scripts/propose-metadata.ts --network op-sepolia; then - echo -e "${GREEN}✓ Metadata update proposal creation successful${NC}" -else - echo -e "${RED}✗ Metadata update proposal creation failed${NC}" - exit 1 -fi - -# Generate metadata proof from OP Sepolia -echo -e "\n${BLUE}Generating metadata proof from OP Sepolia...${NC}" -if npx hardhat run scripts/verify-metadata-proof.ts --network op-sepolia; then - echo -e "${GREEN}✓ Metadata proof generation successful${NC}" -else - echo -e "${RED}✗ Metadata proof generation failed${NC}" - exit 1 -fi - -# Claim metadata update on Arbitrum Sepolia -echo -e "\n${BLUE}Claiming metadata update on Arbitrum Sepolia...${NC}" -if npx hardhat run scripts/claim-metadata.ts --network arbitrum-sepolia; then - echo -e "${GREEN}✓ Metadata update claim successful${NC}" - echo -e "\n${GREEN}✓ All metadata update steps completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Metadata update claim failed${NC}" - exit 1 -fi \ No newline at end of file diff --git a/scripts/mint.sh b/scripts/mint.sh deleted file mode 100755 index c1f0484..0000000 --- a/scripts/mint.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -# Color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Starting cross-chain deployment process...${NC}\n" - -# Run verification script -echo -e "\n${BLUE}Running cross-chain setup verification...${NC}" -if pnpm verify:setup; then - echo -e "${GREEN}✓ Cross-chain setup verification successful${NC}" - echo -e "\n${GREEN}✓ Deployment and verification completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Cross-chain setup verification failed${NC}" - exit 1 -fi - -# Create proposal on OP Sepolia -echo -e "\n${BLUE}Creating proposal on OP Sepolia...${NC}" -if npx hardhat run scripts/propose.ts --network op-sepolia; then - echo -e "${GREEN}✓ Proposal creation successful${NC}" - echo -e "\n${GREEN}✓ Deployment, verification, and proposal creation completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Proposal creation failed${NC}" - exit 1 -fi - -# Generate proof from OP Sepolia -echo -e "\n${BLUE}Generating proof from OP Sepolia...${NC}" -if npx hardhat run scripts/verify-proof.ts --network op-sepolia; then - echo -e "${GREEN}✓ Proof generation successful${NC}" -else - echo -e "${RED}✗ Proof generation failed${NC}" - exit 1 -fi - -# Claim membership on Arbitrum Sepolia -echo -e "\n${BLUE}Claiming membership on Arbitrum Sepolia...${NC}" -if npx hardhat run scripts/claim-membership.ts --network arbitrum-sepolia; then - echo -e "${GREEN}✓ Membership claim successful${NC}" - echo -e "\n${GREEN}✓ All steps completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Membership claim failed${NC}" - exit 1 -fi \ No newline at end of file diff --git a/scripts/voting-delay.sh b/scripts/voting-delay.sh deleted file mode 100755 index 4f30966..0000000 --- a/scripts/voting-delay.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Color codes -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Starting cross-chain voting delay update process...${NC}\n" - -# Create proposal on OP Sepolia -echo -e "\n${BLUE}Creating voting delay update proposal on OP Sepolia...${NC}" -if npx hardhat run scripts/propose-voting-delay.ts --network op-sepolia; then - echo -e "${GREEN}✓ Voting delay update proposal creation successful${NC}" -else - echo -e "${RED}✗ Voting delay update proposal creation failed${NC}" - exit 1 -fi - -# Generate voting delay proof from OP Sepolia -echo -e "\n${BLUE}Generating voting delay proof from OP Sepolia...${NC}" -if npx hardhat run scripts/verify-voting-delay-proof.ts --network op-sepolia; then - echo -e "${GREEN}✓ Voting delay proof generation successful${NC}" -else - echo -e "${RED}✗ Voting delay proof generation failed${NC}" - exit 1 -fi - -# Claim voting delay update on Arbitrum Sepolia -echo -e "\n${BLUE}Claiming voting delay update on Arbitrum Sepolia...${NC}" -if npx hardhat run scripts/claim-voting-delay.ts --network arbitrum-sepolia; then - echo -e "${GREEN}✓ Voting delay update claim successful${NC}" - echo -e "\n${GREEN}✓ All voting delay update steps completed successfully!${NC}" - exit 0 -else - echo -e "${RED}✗ Voting delay update claim failed${NC}" - exit 1 -fi \ No newline at end of file From 76dadce4bc19ae2f7b6499bc79023a28e5a0f8f0 Mon Sep 17 00:00:00 2001 From: julienbrg Date: Tue, 17 Dec 2024 12:56:16 +0100 Subject: [PATCH 3/4] rm script --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index e3ca357..255a325 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "scripts": { "compile": "hardhat compile", "test": "hardhat test", - "test:all": "./scripts/mint.sh && ./scripts/burn.sh && ./scripts/metadata.sh && ./scripts/manifesto.sh && ./scripts/voting-delay.sh && ./scripts/delegation.sh", "test:crosschain": "hardhat test test/Gov-crosschain.ts", "deploy:optimism": "hardhat deploy --network optimism --reset", "deploy:base": "hardhat deploy --network base --reset", From 7b5127392255755acf7f277e3e1742536ad5ad5d Mon Sep 17 00:00:00 2001 From: julienbrg Date: Tue, 17 Dec 2024 12:57:11 +0100 Subject: [PATCH 4/4] rm empty script --- scripts/deploy-and-test.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 scripts/deploy-and-test.sh diff --git a/scripts/deploy-and-test.sh b/scripts/deploy-and-test.sh deleted file mode 100644 index e69de29..0000000