Skip to content

Commit dcb4164

Browse files
authored
Add Arbitrum (#157)
* add utils script * fix deployment script * add arbitrum * add deployments * fix deployments * clean up * rm deployments * fix verify-proof * fix claim membership * proof in .env * clean up * rm json * use .env
1 parent 08efcf8 commit dcb4164

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+840
-10304
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ artifacts
1010

1111
.env*
1212
!.env.template
13-
NOTES.md
13+
NOTES.md
14+
/deployments

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,34 +62,40 @@ Then, you can go ahead and deploy:
6262

6363
```bash
6464
pnpm crosschain:sepolia
65-
pnpm crosschain:op-sepolia
65+
pnpm crosschain:opSepolia
66+
pnpm crosschain:baseSepolia
67+
pnpm crosschain:arbitrumSepolia
6668
```
6769

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

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

7274
```bash
75+
76+
# Watch the [Asciinema video](https://asciinema.org/a/1iZZQVKU51U86hzYYLfjSVtw6)
7377
npx hardhat run scripts/propose.ts --network sepolia
7478
npx hardhat run scripts/verify-proof.ts --network sepolia
75-
npx hardhat run scripts/claim-membership.ts --network op-sepolia
79+
npx hardhat run scripts/claim-membership.ts --network opSepolia
80+
npx hardhat run scripts/claim-membership.ts --network baseSepolia
81+
npx hardhat run scripts/claim-membership.ts --network arbitrumSepolia
7682

7783
npx hardhat run scripts/gov-burn.ts --network sepolia
7884
npx hardhat run scripts/verify-gov-burn-proof.ts --network sepolia
79-
npx hardhat run scripts/claim-gov-burn.ts --network op-sepolia
85+
npx hardhat run scripts/claim-gov-burn.ts --network opSepolia
8086

8187
npx hardhat run scripts/verify-metadata-proof.ts --network sepolia
82-
npx hardhat run scripts/claim-metadata-update.ts --network op-sepolia
88+
npx hardhat run scripts/claim-metadata-update.ts --network opSepolia
8389

8490
npx hardhat run scripts/verify-manifesto-proof.ts --network sepolia
85-
npx hardhat run scripts/claim-manifesto-update.ts --network op-sepolia
91+
npx hardhat run scripts/claim-manifesto-update.ts --network opSepolia
8692

8793
npx hardhat run scripts/gov-voting-delay.ts --network sepolia
8894
npx hardhat run scripts/verify-voting-delay-proof.ts --network sepolia
89-
npx hardhat run scripts/claim-voting-delay.ts --network op-sepolia
95+
npx hardhat run scripts/claim-voting-delay.ts --network opSepolia
9096

9197
npx hardhat run scripts/verify-delegation-proof.ts --network sepolia
92-
npx hardhat run scripts/claim-delegation.ts --network op-sepolia
98+
npx hardhat run scripts/claim-delegation.ts --network opSepolia
9399
```
94100

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

123129
## Core Dependencies

contracts/NFT.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ contract NFT is
3838
) ERC721(_name, _symbol) Ownable(initialOwner) EIP712(_name, "1") {
3939
for (uint i; i < _firstMembers.length; i++) {
4040
safeMint(_firstMembers[i], _uri);
41-
_delegate(_firstMembers[i], _firstMembers[i]);
4241
}
4342
}
4443

contracts/variants/crosschain/NFT.sol

Lines changed: 15 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,15 @@ contract NFT is
9595
home = _home;
9696
for (uint i; i < _firstMembers.length; i++) {
9797
_govMint(_firstMembers[i], _uri);
98-
_delegate(_firstMembers[i], _firstMembers[i]);
9998
}
10099
}
101100

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

114109
/**
@@ -162,29 +157,6 @@ contract NFT is
162157
return ProofHandler.generateProof(address(this), operationType, params, nextNonce);
163158
}
164159

165-
/**
166-
* @notice Generates proof for delegation
167-
* @dev Only callable on home chain
168-
* @param delegator Address delegating voting power
169-
* @param delegatee Address receiving delegation
170-
* @return Encoded proof data
171-
*/
172-
function generateDelegationProof(
173-
address delegator,
174-
address delegatee
175-
) external view returns (bytes memory) {
176-
require(block.chainid == home, "Proofs only generated on home chain");
177-
uint256 nextNonce = _proofStorage.getNextNonce(uint8(OperationType.DELEGATE));
178-
bytes memory params = abi.encode(delegator, delegatee);
179-
return
180-
ProofHandler.generateProof(
181-
address(this),
182-
uint8(OperationType.DELEGATE),
183-
params,
184-
nextNonce
185-
);
186-
}
187-
188160
// Claim operations
189161

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

198170
if (operationType == uint8(OperationType.MINT)) {
199-
(address to, string memory uri) = abi.decode(params, (address, string));
200-
_govMint(to, uri);
201-
emit MembershipClaimed(_nextTokenId - 1, to, nonce);
171+
(uint256 tokenId, address owner, string memory uri) = abi.decode(
172+
params,
173+
(uint256, address, string)
174+
);
175+
176+
try this.ownerOf(tokenId) returns (address) {
177+
revert("Token already exists");
178+
} catch {
179+
_govMint(owner, uri);
180+
emit MembershipClaimed(_nextTokenId - 1, owner, nonce);
181+
}
202182
} else if (operationType == uint8(OperationType.BURN)) {
203183
uint256 tokenId = abi.decode(params, (uint256));
204184
address owner = ownerOf(tokenId);
@@ -215,21 +195,6 @@ contract NFT is
215195
}
216196
}
217197

218-
/**
219-
* @notice Claims a delegation operation on a foreign chain
220-
* @param proof Proof generated by home chain
221-
*/
222-
function claimDelegation(bytes memory proof) external {
223-
(uint8 operationType, bytes memory params, uint256 nonce) = ProofHandler
224-
.verifyAndClaimProof(proof, address(this), _proofStorage);
225-
226-
require(operationType == uint8(OperationType.DELEGATE), "Invalid operation type");
227-
(address delegator, address delegatee) = abi.decode(params, (address, address));
228-
229-
_delegate(delegator, delegatee);
230-
emit DelegationSynced(delegator, delegatee, nonce);
231-
}
232-
233198
/**
234199
* @notice Internal function for minting without proof verification
235200
* @param to Address to receive token

contracts/variants/crosschain/ProofHandler.sol

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pragma solidity ^0.8.20;
99
* @custom:security-contact [email protected]
1010
*/
1111
library ProofHandler {
12-
/// @notice Tracks which proofs have been applied on each chain
12+
/// @notice Tracks which proofs have been applied and nonces for operations that require them
1313
struct ProofStorage {
1414
mapping(bytes32 => bool) updateAppliedOnChain;
1515
mapping(uint8 => uint256) currentNonce;
@@ -23,7 +23,7 @@ library ProofHandler {
2323
* @param contractAddress Address of contract generating the proof
2424
* @param operationType Type of operation being performed
2525
* @param params Operation parameters
26-
* @param nonce Current nonce for this operation type
26+
* @param nonce Current nonce for this operation type (0 for nonce-free operations)
2727
* @return proof Encoded proof data
2828
*/
2929
function generateProof(
@@ -60,10 +60,6 @@ library ProofHandler {
6060
(uint8, bytes, uint256, bytes32)
6161
);
6262

63-
bytes32 proofHash = keccak256(proof);
64-
require(!storage_.updateAppliedOnChain[proofHash], "Proof already claimed");
65-
require(nonce == storage_.currentNonce[operationType] + 1, "Invalid nonce");
66-
6763
bytes32 message = keccak256(
6864
abi.encodePacked(contractAddress, operationType, params, nonce)
6965
);
@@ -72,8 +68,14 @@ library ProofHandler {
7268
);
7369
require(digest == expectedDigest, "Invalid proof");
7470

75-
storage_.updateAppliedOnChain[proofHash] = true;
76-
storage_.currentNonce[operationType] = nonce;
71+
if (operationType > 1) {
72+
bytes32 proofHash = keccak256(proof);
73+
require(!storage_.updateAppliedOnChain[proofHash], "Proof already claimed");
74+
require(nonce == storage_.currentNonce[operationType] + 1, "Invalid nonce");
75+
76+
storage_.updateAppliedOnChain[proofHash] = true;
77+
storage_.currentNonce[operationType] = nonce;
78+
}
7779

7880
emit ProofClaimed(operationType, params, nonce);
7981

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

@@ -103,6 +106,7 @@ library ProofHandler {
103106
ProofStorage storage storage_,
104107
uint8 operationType
105108
) public returns (uint256 nonce) {
109+
if (operationType <= 1) return 0; // MINT or BURN operations don't use nonces
106110
storage_.currentNonce[operationType]++;
107111
return storage_.currentNonce[operationType];
108112
}

dao.config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
///// Home chain /////
2+
3+
export const homeChain = 11155111
4+
15
///// Membership NFT /////
26

37
export const firstMembers = [
@@ -16,6 +20,6 @@ export const manifesto =
1620
"https://bafkreifnnreoxxgkhty7v2w3qwiie6cfxpv3vcco2xldekfvbiem3nm6dm.ipfs.w3s.link/"
1721
export const daoName = "Test DAO"
1822
export const votingDelay = 0
19-
export const votingPeriod = 200
23+
export const votingPeriod = 60
2024
export const votingThreshold = 1
2125
export const quorum = 5

deploy/deploy-crosschain-gov.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types"
33
import color from "cli-color"
44
var msg = color.xterm(39).bgXterm(128)
55
import {
6+
homeChain,
67
firstMembers,
78
uri,
89
name,
@@ -19,6 +20,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
1920
const { deployments, getNamedAccounts } = hre
2021
const { deterministic } = deployments
2122
const { deployer } = await getNamedAccounts()
23+
const salt = "-v1"
2224

2325
function wait(ms: number): Promise<void> {
2426
return new Promise(resolve => setTimeout(resolve, ms))
@@ -30,7 +32,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
3032
from: deployer,
3133
contract:
3234
"contracts/variants/crosschain/ProofHandler.sol:ProofHandler",
33-
salt: hre.ethers.id("ProofHandler"),
35+
salt: hre.ethers.id("ProofHandler" + salt),
3436
log: true,
3537
waitConfirmations: 1
3638
})
@@ -44,13 +46,14 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
4446
{
4547
from: deployer,
4648
contract: "contracts/variants/crosschain/NFT.sol:NFT",
47-
args: [11155111, deployer, firstMembers, uri, name, symbol],
49+
args: [homeChain, deployer, firstMembers, uri, name, symbol],
4850
libraries: {
4951
ProofHandler: proofHandlerAddress
5052
},
51-
salt: hre.ethers.id("NFT"),
53+
salt: hre.ethers.id("NFT" + salt),
5254
log: true,
53-
waitConfirmations: 1
55+
waitConfirmations: 1,
56+
gasLimit: 10000000
5457
}
5558
)
5659

@@ -64,7 +67,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
6467
from: deployer,
6568
contract: "contracts/variants/crosschain/Gov.sol:Gov",
6669
args: [
67-
11155111,
70+
homeChain,
6871
nftAddress,
6972
manifesto,
7073
daoName,
@@ -76,7 +79,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
7679
libraries: {
7780
ProofHandler: proofHandlerAddress
7881
},
79-
salt: hre.ethers.id("Gov"),
82+
salt: hre.ethers.id("Gov" + salt),
8083
log: true,
8184
waitConfirmations: 5
8285
}
@@ -86,12 +89,29 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
8689
console.log("Gov contract address:", msg(govAddress))
8790

8891
// Transfer NFT ownership to Gov
89-
const nft = await hre.ethers.getContractAt(
90-
"contracts/variants/crosschain/NFT.sol:NFT",
91-
nftAddress
92-
)
93-
await nft.transferOwnership(govAddress)
94-
console.log("NFT ownership transferred to Gov")
92+
try {
93+
let txOptions = {}
94+
95+
switch (hre.network.name) {
96+
case "arbitrum":
97+
case "arbitrumSepolia":
98+
case "sepolia":
99+
case "opSepolia":
100+
txOptions = { gasLimit: 500000 }
101+
break
102+
default:
103+
txOptions = {}
104+
}
105+
106+
const nft = await hre.ethers.getContractAt(
107+
"contracts/variants/crosschain/NFT.sol:NFT",
108+
nftAddress
109+
)
110+
await nft.transferOwnership(govAddress, txOptions)
111+
console.log("NFT ownership transferred to Gov")
112+
} catch (e: any) {
113+
console.warn("error during ownership transfer", e)
114+
}
95115

96116
if (hre.network.name !== "hardhat") {
97117
console.log("\nVerifying ProofHandler library...")
@@ -112,7 +132,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
112132
address: nftAddress,
113133
contract: "contracts/variants/crosschain/NFT.sol:NFT",
114134
constructorArguments: [
115-
11155111,
135+
homeChain,
116136
deployer,
117137
firstMembers,
118138
uri,
@@ -131,7 +151,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
131151
address: govAddress,
132152
contract: "contracts/variants/crosschain/Gov.sol:Gov",
133153
constructorArguments: [
134-
11155111,
154+
homeChain,
135155
nftAddress,
136156
manifesto,
137157
daoName,

deploy/deploy-gov.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export default async ({ getNamedAccounts, deployments }: any) => {
218218

219219
break
220220

221-
case "op-sepolia":
221+
case "opSepolia":
222222
try {
223223
console.log(
224224
"NFT contract deployed:",
@@ -274,7 +274,7 @@ export default async ({ getNamedAccounts, deployments }: any) => {
274274

275275
break
276276

277-
case "base-sepolia":
277+
case "baseSepolia":
278278
try {
279279
console.log(
280280
"NFT contract deployed:",

deployments/op-sepolia/.chainId

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)