-
Notifications
You must be signed in to change notification settings - Fork 12.1k
Add NFT-Based Voting & Dynamic Delegation Support to Votes.behavior.js #5842
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,13 +9,34 @@ const { shouldBehaveLikeERC6372 } = require('./ERC6372.behavior'); | |||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
function shouldBehaveLikeVotes(tokens, { mode = 'blocknumber', fungible = true }) { | ||||||||||||||||||||||||||||||
beforeEach(async function () { | ||||||||||||||||||||||||||||||
[this.delegator, this.delegatee, this.alice, this.bob, this.other] = this.accounts; | ||||||||||||||||||||||||||||||
[ | ||||||||||||||||||||||||||||||
this.delegator, | ||||||||||||||||||||||||||||||
this.delegatee, | ||||||||||||||||||||||||||||||
this.alice, | ||||||||||||||||||||||||||||||
this.bob, | ||||||||||||||||||||||||||||||
this.other, | ||||||||||||||||||||||||||||||
this.charlie, | ||||||||||||||||||||||||||||||
this.dave | ||||||||||||||||||||||||||||||
] = this.accounts; | ||||||||||||||||||||||||||||||
this.domain = await getDomain(this.votes); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Mock metadata registry for NFT weight calculation | ||||||||||||||||||||||||||||||
if (!fungible) { | ||||||||||||||||||||||||||||||
this.metadata = {}; | ||||||||||||||||||||||||||||||
tokens.forEach(tokenId => { | ||||||||||||||||||||||||||||||
this.metadata[tokenId] = { | ||||||||||||||||||||||||||||||
weight: (tokenId % 10n) + 1n, // Example: weight based on token ID | ||||||||||||||||||||||||||||||
rarity: ['common', 'uncommon', 'rare', 'legendary'][Number(tokenId % 4n)] // Example metadata | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
Comment on lines
+25
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can build the metadata registry directly
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
this.getNFTWeight = (tokenId) => this.metadata[tokenId]?.weight || 1n; | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should not need that, |
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
shouldBehaveLikeERC6372(mode); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const getWeight = token => (fungible ? token : 1n); | ||||||||||||||||||||||||||||||
const getWeight = token => (fungible ? token : (this.getNFTWeight ? this.getNFTWeight(token) : 1n)); | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
describe('run votes workflow', function () { | ||||||||||||||||||||||||||||||
it('initial nonce is 0', async function () { | ||||||||||||||||||||||||||||||
|
@@ -211,6 +232,74 @@ function shouldBehaveLikeVotes(tokens, { mode = 'blocknumber', fungible = true } | |||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Add NFT-specific tests | ||||||||||||||||||||||||||||||
if (!fungible) { | ||||||||||||||||||||||||||||||
describe('NFT-specific voting behavior', function () { | ||||||||||||||||||||||||||||||
it('uses dynamic NFT weights based on metadata', async function () { | ||||||||||||||||||||||||||||||
const token = tokens[0]; | ||||||||||||||||||||||||||||||
const weight = getWeight(token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await this.votes.$_mint(this.alice, token); | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.bob); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(weight); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
it('handles metadata changes affecting vote weight', async function () { | ||||||||||||||||||||||||||||||
const token = tokens[0]; | ||||||||||||||||||||||||||||||
await this.votes.$_mint(this.alice, token); | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.bob); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const initialWeight = getWeight(token); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(initialWeight); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Update metadata | ||||||||||||||||||||||||||||||
this.metadata[token] = { ...this.metadata[token], weight: initialWeight * 2n }; | ||||||||||||||||||||||||||||||
const newWeight = getWeight(token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Weight change should be reflected | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(newWeight); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
it('maintains delegation after metadata update', async function () { | ||||||||||||||||||||||||||||||
const token = tokens[0]; | ||||||||||||||||||||||||||||||
await this.votes.$_mint(this.alice, token); | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.bob); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Update metadata | ||||||||||||||||||||||||||||||
const originalWeight = getWeight(token); | ||||||||||||||||||||||||||||||
this.metadata[token].weight = originalWeight * 3n; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Delegation should persist | ||||||||||||||||||||||||||||||
expect(await this.votes.delegates(this.alice)).to.equal(this.bob); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(getWeight(token)); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Add batch operations test | ||||||||||||||||||||||||||||||
it('handles batch minting and delegation', async function () { | ||||||||||||||||||||||||||||||
const batchSize = 10; | ||||||||||||||||||||||||||||||
const batchTokens = Array.from({ length: batchSize }, (_, i) => | ||||||||||||||||||||||||||||||
fungible ? 1000n * BigInt(i + 1) : tokens[0] + BigInt(i) | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Mint batch | ||||||||||||||||||||||||||||||
for (const token of batchTokens) { | ||||||||||||||||||||||||||||||
await this.votes.$_mint(this.alice, token); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Calculate total weight | ||||||||||||||||||||||||||||||
const totalWeight = batchTokens.reduce( | ||||||||||||||||||||||||||||||
(sum, token) => sum + getWeight(token), | ||||||||||||||||||||||||||||||
0n | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Delegate and verify | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.bob); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(totalWeight); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
describe('getPastTotalSupply', function () { | ||||||||||||||||||||||||||||||
beforeEach(async function () { | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.alice); | ||||||||||||||||||||||||||||||
|
@@ -275,6 +364,64 @@ function shouldBehaveLikeVotes(tokens, { mode = 'blocknumber', fungible = true } | |||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// New section for transfer tests | ||||||||||||||||||||||||||||||
describe('transfer behavior', function () { | ||||||||||||||||||||||||||||||
const token = tokens[0]; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
it('updates delegation when token is transferred', async function () { | ||||||||||||||||||||||||||||||
const weight = getWeight(token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Setup initial state | ||||||||||||||||||||||||||||||
await this.votes.$_mint(this.alice, token); | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.bob); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(weight); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Transfer token | ||||||||||||||||||||||||||||||
await this.votes.$_transfer(this.alice, this.charlie, token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Verify delegation updates | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(0n); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.alice)).to.equal(0n); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// New owner delegates | ||||||||||||||||||||||||||||||
await this.votes.connect(this.charlie).delegate(this.dave); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.dave)).to.equal(weight); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
it('clears delegation when transferred to non-delegated account', async function () { | ||||||||||||||||||||||||||||||
const weight = getWeight(token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Setup initial state | ||||||||||||||||||||||||||||||
await this.votes.$_mint(this.alice, token); | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.bob); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Transfer to non-delegated account | ||||||||||||||||||||||||||||||
await this.votes.$_transfer(this.alice, this.charlie, token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Verify votes are cleared | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.bob)).to.equal(0n); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.charlie)).to.equal(0n); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if (!fungible) { | ||||||||||||||||||||||||||||||
it('handles NFT transfer with metadata changes', async function () { | ||||||||||||||||||||||||||||||
const token = tokens[0]; | ||||||||||||||||||||||||||||||
await this.votes.$_mint(this.alice, token); | ||||||||||||||||||||||||||||||
await this.votes.connect(this.alice).delegate(this.bob); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const initialWeight = getWeight(token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Transfer and update metadata | ||||||||||||||||||||||||||||||
await this.votes.$_transfer(this.alice, this.charlie, token); | ||||||||||||||||||||||||||||||
this.metadata[token].weight = initialWeight * 2n; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// New owner delegates | ||||||||||||||||||||||||||||||
await this.votes.connect(this.charlie).delegate(this.dave); | ||||||||||||||||||||||||||||||
expect(await this.votes.getVotes(this.dave)).to.equal(getWeight(token)); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// The following tests are an adaptation of | ||||||||||||||||||||||||||||||
// https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js. | ||||||||||||||||||||||||||||||
describe('Compound test suite', function () { | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd consider renaming to alice / bruce / chris / david, so that all names are 5 letters long