Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Arbitrum #157

Merged
merged 13 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ artifacts

.env*
!.env.template
NOTES.md
NOTES.md
/deployments
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,34 +62,40 @@ Then, you can go ahead and deploy:

```bash
pnpm crosschain:sepolia
pnpm crosschain:op-sepolia
pnpm crosschain:opSepolia
pnpm crosschain:baseSepolia
pnpm crosschain:arbitrumSepolia
```

Your DAO will be deployed on every networks at the same address.
Your DAO will be deployed on every networks at the same address (watch the [Asciinema video](https://asciinema.org/a/rc8bTqbBiW7e0xevewxCwCP7C)).

Then you can follow these steps to verify that proofs can be generated on home chain and claimed on foreign chain:

```bash

# Watch the [Asciinema video](https://asciinema.org/a/1iZZQVKU51U86hzYYLfjSVtw6)
npx hardhat run scripts/propose.ts --network sepolia
npx hardhat run scripts/verify-proof.ts --network sepolia
npx hardhat run scripts/claim-membership.ts --network op-sepolia
npx hardhat run scripts/claim-membership.ts --network opSepolia
npx hardhat run scripts/claim-membership.ts --network baseSepolia
npx hardhat run scripts/claim-membership.ts --network arbitrumSepolia

npx hardhat run scripts/gov-burn.ts --network sepolia
npx hardhat run scripts/verify-gov-burn-proof.ts --network sepolia
npx hardhat run scripts/claim-gov-burn.ts --network op-sepolia
npx hardhat run scripts/claim-gov-burn.ts --network opSepolia

npx hardhat run scripts/verify-metadata-proof.ts --network sepolia
npx hardhat run scripts/claim-metadata-update.ts --network op-sepolia
npx hardhat run scripts/claim-metadata-update.ts --network opSepolia

npx hardhat run scripts/verify-manifesto-proof.ts --network sepolia
npx hardhat run scripts/claim-manifesto-update.ts --network op-sepolia
npx hardhat run scripts/claim-manifesto-update.ts --network opSepolia

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/claim-voting-delay.ts --network opSepolia

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

## Security
Expand Down Expand Up @@ -117,7 +123,7 @@ The following functions are `onlyOwner`, and since the NFT contract ownership is
| Optimism Mainnet | 10 | [Documentation](https://docs.optimism.io/chain/networks#op-mainnet) |
| Base Mainnet | 8453 | [Documentation](https://docs.base.org/docs/network-information#base-mainnet) |
| Sepolia Testnet | 11155111 | [Documentation](https://ethereum.org/nb/developers/docs/networks/#sepolia) |
| OP Sepolia Testnet | 11155420 | [Documentation](https://docs.optimism.io/chain/networks#op-sepolia) |
| OP Sepolia Testnet | 11155420 | [Documentation](https://docs.optimism.io/chain/networks#opSepolia) |
| Base Sepolia Testnet | 84532 | [Documentation](https://docs.base.org/docs/network-information/#base-testnet-sepolia) |

## Core Dependencies
Expand Down
1 change: 0 additions & 1 deletion contracts/NFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ 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 Down
65 changes: 15 additions & 50 deletions contracts/variants/crosschain/NFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,15 @@ contract NFT is
home = _home;
for (uint i; i < _firstMembers.length; i++) {
_govMint(_firstMembers[i], _uri);
_delegate(_firstMembers[i], _firstMembers[i]);
}
}

/**
* @notice Adds a new member on home chain
* @dev Only callable by owner (governance) on home chain
* @param to Address to mint token to
* @param uri Token metadata URI
*/
/// @notice Adds a new member to the DAO
/// @dev Mints a new NFT to the specified address
/// @param to The address of the new member
/// @param uri The metadata URI for the new NFT
function safeMint(address to, string memory uri) public onlyOwner onlyHomeChain {
uint256 nonce = _proofStorage.incrementNonce(uint8(OperationType.MINT));
_govMint(to, uri);
emit MembershipClaimed(_nextTokenId - 1, to, nonce);
}

/**
Expand Down Expand Up @@ -162,29 +157,6 @@ 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

/**
Expand All @@ -196,9 +168,17 @@ contract NFT is
.verifyAndClaimProof(proof, address(this), _proofStorage);

if (operationType == uint8(OperationType.MINT)) {
(address to, string memory uri) = abi.decode(params, (address, string));
_govMint(to, uri);
emit MembershipClaimed(_nextTokenId - 1, to, nonce);
(uint256 tokenId, address owner, string memory uri) = abi.decode(
params,
(uint256, address, string)
);

try this.ownerOf(tokenId) returns (address) {
revert("Token already exists");
} catch {
_govMint(owner, uri);
emit MembershipClaimed(_nextTokenId - 1, owner, nonce);
}
} else if (operationType == uint8(OperationType.BURN)) {
uint256 tokenId = abi.decode(params, (uint256));
address owner = ownerOf(tokenId);
Expand All @@ -215,21 +195,6 @@ contract NFT is
}
}

/**
* @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 Down
20 changes: 12 additions & 8 deletions contracts/variants/crosschain/ProofHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pragma solidity ^0.8.20;
* @custom:security-contact [email protected]
*/
library ProofHandler {
/// @notice Tracks which proofs have been applied on each chain
/// @notice Tracks which proofs have been applied and nonces for operations that require them
struct ProofStorage {
mapping(bytes32 => bool) updateAppliedOnChain;
mapping(uint8 => uint256) currentNonce;
Expand All @@ -23,7 +23,7 @@ library ProofHandler {
* @param contractAddress Address of contract generating the proof
* @param operationType Type of operation being performed
* @param params Operation parameters
* @param nonce Current nonce for this operation type
* @param nonce Current nonce for this operation type (0 for nonce-free operations)
* @return proof Encoded proof data
*/
function generateProof(
Expand Down Expand Up @@ -60,10 +60,6 @@ library ProofHandler {
(uint8, bytes, uint256, bytes32)
);

bytes32 proofHash = keccak256(proof);
require(!storage_.updateAppliedOnChain[proofHash], "Proof already claimed");
require(nonce == storage_.currentNonce[operationType] + 1, "Invalid nonce");

bytes32 message = keccak256(
abi.encodePacked(contractAddress, operationType, params, nonce)
);
Expand All @@ -72,8 +68,14 @@ library ProofHandler {
);
require(digest == expectedDigest, "Invalid proof");

storage_.updateAppliedOnChain[proofHash] = true;
storage_.currentNonce[operationType] = nonce;
if (operationType > 1) {
bytes32 proofHash = keccak256(proof);
require(!storage_.updateAppliedOnChain[proofHash], "Proof already claimed");
require(nonce == storage_.currentNonce[operationType] + 1, "Invalid nonce");

storage_.updateAppliedOnChain[proofHash] = true;
storage_.currentNonce[operationType] = nonce;
}

emit ProofClaimed(operationType, params, nonce);

Expand All @@ -90,6 +92,7 @@ library ProofHandler {
ProofStorage storage storage_,
uint8 operationType
) public view returns (uint256 nonce) {
if (operationType <= 1) return 0; // MINT or BURN operations don't use nonces
return storage_.currentNonce[operationType] + 1;
}

Expand All @@ -103,6 +106,7 @@ library ProofHandler {
ProofStorage storage storage_,
uint8 operationType
) public returns (uint256 nonce) {
if (operationType <= 1) return 0; // MINT or BURN operations don't use nonces
storage_.currentNonce[operationType]++;
return storage_.currentNonce[operationType];
}
Expand Down
6 changes: 5 additions & 1 deletion dao.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
///// Home chain /////

export const homeChain = 11155111

///// Membership NFT /////

export const firstMembers = [
Expand All @@ -16,6 +20,6 @@ export const manifesto =
"https://bafkreifnnreoxxgkhty7v2w3qwiie6cfxpv3vcco2xldekfvbiem3nm6dm.ipfs.w3s.link/"
export const daoName = "Test DAO"
export const votingDelay = 0
export const votingPeriod = 200
export const votingPeriod = 60
export const votingThreshold = 1
export const quorum = 5
48 changes: 34 additions & 14 deletions deploy/deploy-crosschain-gov.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types"
import color from "cli-color"
var msg = color.xterm(39).bgXterm(128)
import {
homeChain,
firstMembers,
uri,
name,
Expand All @@ -19,6 +20,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre
const { deterministic } = deployments
const { deployer } = await getNamedAccounts()
const salt = "-v1"

function wait(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
Expand All @@ -30,7 +32,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
from: deployer,
contract:
"contracts/variants/crosschain/ProofHandler.sol:ProofHandler",
salt: hre.ethers.id("ProofHandler"),
salt: hre.ethers.id("ProofHandler" + salt),
log: true,
waitConfirmations: 1
})
Expand All @@ -44,13 +46,14 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
{
from: deployer,
contract: "contracts/variants/crosschain/NFT.sol:NFT",
args: [11155111, deployer, firstMembers, uri, name, symbol],
args: [homeChain, deployer, firstMembers, uri, name, symbol],
libraries: {
ProofHandler: proofHandlerAddress
},
salt: hre.ethers.id("NFT"),
salt: hre.ethers.id("NFT" + salt),
log: true,
waitConfirmations: 1
waitConfirmations: 1,
gasLimit: 10000000
}
)

Expand All @@ -64,7 +67,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
from: deployer,
contract: "contracts/variants/crosschain/Gov.sol:Gov",
args: [
11155111,
homeChain,
nftAddress,
manifesto,
daoName,
Expand All @@ -76,7 +79,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
libraries: {
ProofHandler: proofHandlerAddress
},
salt: hre.ethers.id("Gov"),
salt: hre.ethers.id("Gov" + salt),
log: true,
waitConfirmations: 5
}
Expand All @@ -86,12 +89,29 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
console.log("Gov contract address:", msg(govAddress))

// Transfer NFT ownership to Gov
const nft = await hre.ethers.getContractAt(
"contracts/variants/crosschain/NFT.sol:NFT",
nftAddress
)
await nft.transferOwnership(govAddress)
console.log("NFT ownership transferred to Gov")
try {
let txOptions = {}

switch (hre.network.name) {
case "arbitrum":
case "arbitrumSepolia":
case "sepolia":
case "opSepolia":
txOptions = { gasLimit: 500000 }
break
default:
txOptions = {}
}

const nft = await hre.ethers.getContractAt(
"contracts/variants/crosschain/NFT.sol:NFT",
nftAddress
)
await nft.transferOwnership(govAddress, txOptions)
console.log("NFT ownership transferred to Gov")
} catch (e: any) {
console.warn("error during ownership transfer", e)
}

if (hre.network.name !== "hardhat") {
console.log("\nVerifying ProofHandler library...")
Expand All @@ -112,7 +132,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
address: nftAddress,
contract: "contracts/variants/crosschain/NFT.sol:NFT",
constructorArguments: [
11155111,
homeChain,
deployer,
firstMembers,
uri,
Expand All @@ -131,7 +151,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
address: govAddress,
contract: "contracts/variants/crosschain/Gov.sol:Gov",
constructorArguments: [
11155111,
homeChain,
nftAddress,
manifesto,
daoName,
Expand Down
4 changes: 2 additions & 2 deletions deploy/deploy-gov.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export default async ({ getNamedAccounts, deployments }: any) => {

break

case "op-sepolia":
case "opSepolia":
try {
console.log(
"NFT contract deployed:",
Expand Down Expand Up @@ -274,7 +274,7 @@ export default async ({ getNamedAccounts, deployments }: any) => {

break

case "base-sepolia":
case "baseSepolia":
try {
console.log(
"NFT contract deployed:",
Expand Down
1 change: 0 additions & 1 deletion deployments/op-sepolia/.chainId

This file was deleted.

Loading
Loading