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

Erc721 vote button #2680

Merged
merged 4 commits into from
Jan 21, 2025
Merged

Erc721 vote button #2680

merged 4 commits into from
Jan 21, 2025

Conversation

adamgall
Copy link
Member

@adamgall adamgall commented Jan 21, 2025

Closes https://linear.app/decent-labs/issue/ENG-136/contango-couldnt-vote

Currently, users are unable to vote on ERC 721 Token proposals. The "Vote" button does not appear when loading the app with a wallet connected that DOES have voting power on a proposal that already exists is and open for voting.

This PR addresses that bug.

Fixed two bugs, and do some cleanup.

Bugs fixed:

  1. "zero is not undefined". When the tokenId of an owned NFT by the connected address is 0, that was returning truthy in a place where we actually want to check if it's undefined.
  2. Refactor VoteContext to improve voting eligibility checks and update user reference handling
    a. Modified getCanVote function to accept remainingTokenIdsLength as a parameter for better clarity and efficiency.
    b. Updated user reference management to use a hex representation of the user's address combined with the length of remaining token IDs.
    c. Ensured that the voting eligibility logic remains intact while enhancing readability and maintainability.

Cleanup:

  1. Don't need to check a boolean twice in order to render one thing
  2. Remove unused functions from VoteContext value

Testing:

  1. Use Rabby
  2. Add Josh's address as a "read only" address in Rabby: 0xda38c543596b7E1b79ad0aC2C7797ADb914Ea3C5
  3. Load up the VANTA DAO: eth:0xB14AC30AA97057e2A934c47BDc45171335B91Bd8
  4. Connect to the Decent app with Rabby using Josh's address
  5. Load up a current active proposal.
    a. Before this PR: no Vote button
    b. In this PR: Vote button

Comment on lines -47 to -52
const { canVote, canVoteLoading, hasVoted, hasVotedLoading } = useVoteContext();

// if the user is not a signer or has no delegated tokens, don't show anything
if (!canVote) {
return null;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup 1.

Comment on lines -33 to -43
getCanVote: () => Promise<void>;
getHasVoted: () => void;
}

const VoteContext = createContext<IVoteContext>({
canVote: false,
canVoteLoading: false,
hasVoted: false,
hasVotedLoading: false,
getCanVote: async () => {},
getHasVoted: () => {},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup 2.

Comment on lines +119 to +175
const getCanVote = useCallback(
async (remainingTokenIdsLength: number) => {
setCanVoteLoading(true);
let newCanVote = false;
if (userAccount.address && publicClient) {
if (snapshotProposal) {
const votingWeightData = await loadVotingWeight();
newCanVote = votingWeightData.votingWeight >= 1;
} else if (governance.type === GovernanceType.AZORIUS_ERC20) {
const azoriusProposal = proposal as AzoriusProposal;
const ozLinearVotingContract = getContract({
abi: abis.LinearERC20Voting,
address: azoriusProposal.votingStrategy,
client: publicClient,
});
newCanVote =
(await ozLinearVotingContract.read.getVotingWeight([
userAccount.address,
Number(proposal.proposalId),
])) > 0n;
} else if (governance.type === GovernanceType.AZORIUS_ERC721) {
const votingWeight = await erc721VotingWeight();
newCanVote = votingWeight > 0n && remainingTokenIdsLength > 0;
} else if (governance.type === GovernanceType.MULTISIG) {
newCanVote = !!safe?.owners.includes(userAccount.address);
} else {
newCanVote = false;
}
}
}

if (canVote !== newCanVote) {
setCanVote(newCanVote);
}
setCanVoteLoading(false);
}, [
userAccount.address,
publicClient,
canVote,
snapshotProposal,
governance.type,
loadVotingWeight,
remainingTokenIds.length,
safe?.owners,
proposal,
erc721VotingWeight,
]);
if (canVote !== newCanVote) {
setCanVote(newCanVote);
}
setCanVoteLoading(false);
},
[
userAccount.address,
publicClient,
canVote,
snapshotProposal,
governance.type,
loadVotingWeight,
safe?.owners,
proposal,
erc721VotingWeight,
],
);

const connectedUserRef = useRef<Address>();
const connectedUserRef = useRef<Hex>();
useEffect(() => {
const isUserRefCurrent = connectedUserRef.current === userAccount.address;
const refValue = toHex(`${userAccount.address}-${remainingTokenIds.length}`);
const isUserRefCurrent = connectedUserRef.current === refValue;
if (!isUserRefCurrent) {
connectedUserRef.current = userAccount.address;
getCanVote();
connectedUserRef.current = refValue;
getCanVote(remainingTokenIds.length);
}
}, [getCanVote, userAccount.address]);
}, [getCanVote, userAccount.address, remainingTokenIds.length]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugfix 2.

Comment on lines -194 to -197
getCanVote,
getHasVoted,
};
}, [canVote, canVoteLoading, getCanVote, getHasVoted, hasVoted, hasVotedLoading]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup 2

Comment on lines +156 to +157
if (!to || !from || tokenId === undefined) {
throw new Error('An ERC721 event has undefined data');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugfix 1

Comment on lines +167 to +175
const connectedUserRef = useRef<Hex>();
useEffect(() => {
const isUserRefCurrent = connectedUserRef.current === userAccount.address;
const refValue = toHex(`${userAccount.address}-${remainingTokenIds.length}`);
const isUserRefCurrent = connectedUserRef.current === refValue;
if (!isUserRefCurrent) {
connectedUserRef.current = userAccount.address;
getCanVote();
connectedUserRef.current = refValue;
getCanVote(remainingTokenIds.length);
}
}, [getCanVote, userAccount.address]);
}, [getCanVote, userAccount.address, remainingTokenIds.length]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the important part here. We need to make sure getCanVote is ran every time the remainingTokenIds.length changes, which happens asynchronously and definitely is updated after the user's address ref was first being set.

Copy link
Member Author

@adamgall adamgall Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DarksightKellar
Copy link
Contributor

Does this need to hotfix to prod?

@adamgall
Copy link
Member Author

Does this need to hotfix to prod?

Oh yeah good point. Let me confirm that it's safe to rebase and make it happen

…user reference handling

- Modified `getCanVote` function to accept `remainingTokenIdsLength` as a parameter for better clarity and efficiency.
- Updated user reference management to use a hex representation of the user's address combined with the length of remaining token IDs.
- Ensured that the voting eligibility logic remains intact while enhancing readability and maintainability.
Copy link

cloudflare-workers-and-pages bot commented Jan 21, 2025

Deploying decent-interface with  Cloudflare Pages  Cloudflare Pages

Latest commit: fe943f0
Status: ✅  Deploy successful!
Preview URL: https://f8d33b5c.decent-interface.pages.dev
Branch Preview URL: https://erc721-vote-button.decent-interface.pages.dev

View logs

@adamgall adamgall changed the base branch from develop to hotfix/v0.4.4 January 21, 2025 18:29
@adamgall
Copy link
Member Author

adamgall commented Jan 21, 2025

@Da-Colon @DarksightKellar just to confirm, have you both successfully reproduced the bug and then confirmed that this PR fixes it?

Just considering this is a hotfix into prod, I want to be sure every "approval" includes QA testing.

See the "Testing" section in the top comment.

@johnqh
Copy link
Contributor

johnqh commented Jan 21, 2025

Created ticket

Copy link
Contributor

@johnqh johnqh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested complete flow with deployment, created proposal and voted successfully.

Copy link

linear bot commented Jan 21, 2025

@adamgall
Copy link
Member Author

@johnqh specifically, did you load the app fresh, with the proposal already created? Like, if you just refresh the page/app, does the proposal still give you the Vote button?

Copy link
Contributor

@mudrila mudrila left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on Contango /proposals/5?dao=eth:0xB14AC30AA97057e2A934c47BDc45171335B91Bd8 with the wallet that has NFT for voting - works good.
One caveat, but I think I've seen this some time ago - see screenshot. Right after voting the voting option appears as empty box but after refresh it loads correctly. I'll add a ticket for that in Linear

Screenshot 2025-01-21 at 20 55 41

@adamgall adamgall mentioned this pull request Jan 21, 2025
@adamgall adamgall merged commit 0e9dcd7 into hotfix/v0.4.4 Jan 21, 2025
4 checks passed
@adamgall adamgall deleted the erc721-vote-button branch January 21, 2025 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants