Skip to content

Conversation

@hwrdtm
Copy link
Contributor

@hwrdtm hwrdtm commented Nov 29, 2025

Closes NODE-4819.

@hwrdtm hwrdtm self-assigned this Nov 29, 2025
@github-actions
Copy link

github-actions bot commented Dec 1, 2025

PASS [ 43.765s] (3/3) lit_node::test toxiproxy::perf_tests::load_with_no_latency
PASS [ 43.848s] (2/3) lit_node::test toxiproxy::perf_tests::load_with_50ms_latency_single_link
PASS [ 93.991s] (1/3) lit_node::test toxiproxy::perf_tests::load_with_50ms_latency_all_links

@hwrdtm
Copy link
Contributor Author

hwrdtm commented Dec 1, 2025

Just tried running the forge invariant tests locally and no issues there. Might need to run few more times 🤔 forge test --match-test invariant -vv --no-match-test Skip

@hwrdtm hwrdtm marked this pull request as ready for review December 3, 2025 04:03
@hwrdtm hwrdtm requested review from GTC6244 and glitch003 December 3, 2025 04:03
@hwrdtm hwrdtm force-pushed the feature/node-4819-stakingdiamond-nftfacet branch from a7ce0ec to 7befe08 Compare December 3, 2025 04:48
@hwrdtm hwrdtm changed the base branch from master to hwrdtm/remove-unused-staking-mapping December 3, 2025 04:51
@hwrdtm hwrdtm force-pushed the feature/node-4819-stakingdiamond-nftfacet branch from 69aad84 to 852a7f5 Compare December 3, 2025 04:58
@hwrdtm hwrdtm force-pushed the hwrdtm/remove-unused-staking-mapping branch from a4fae38 to f7c391c Compare December 3, 2025 18:49
@hwrdtm hwrdtm force-pushed the feature/node-4819-stakingdiamond-nftfacet branch 2 times, most recently from e8f9f44 to dcd1142 Compare December 3, 2025 19:02
Base automatically changed from hwrdtm/remove-unused-staking-mapping to master December 3, 2025 22:54
@hwrdtm hwrdtm force-pushed the feature/node-4819-stakingdiamond-nftfacet branch from c4c6deb to 3a9d9ec Compare December 3, 2025 22:57
Copy link
Contributor

@glitch003 glitch003 left a comment

Choose a reason for hiding this comment

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

overall, looks good. how does an existing delegated staker get an NFT? like what's the migration path?

@@ -0,0 +1,159 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
Copy link
Contributor

Choose a reason for hiding this comment

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

we should import this from openzeppelin instead of pasting it here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On line 8 (few lines below) I explain why I am doing this approach instead. Open to better ideas tho!

mappedStakeRecord.operatorStakerAddress
) == 0
) {
revert NoEmptyStakeRecordSlots();
Copy link
Contributor

Choose a reason for hiding this comment

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

under what conditions can this happen? the user has too many stake records? what's the limit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Each staker vault holds only 30 stake records. Keep in mind a staker vault is specific to a validator, so a delegating staker can have more than 30 stake records if delegating against more than one validator.


// Add NFT
LibStakingNFT._addTokenTo(to, tokenId);
// Set the block of ownership transfer (for Flash NFT protection)
Copy link
Contributor

Choose a reason for hiding this comment

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

what's flash nft protection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copied over from aerodrome contracts. It's basically to prevent buyers from doing additional actions in the same transaction / block as when they got transferred the NFT. For example, in aerodrome they prevent poking, delegating and voting in the same TX / block as when the transferred happened.

address owner = ownerOf(tokenId);
bool spenderIsOwner = owner == spender;
bool spenderIsApproved = spender == s().tokenToApprovals[tokenId];
bool spenderIsApprovedForAll = (s().ownerToOperators[owner])[spender];
Copy link
Contributor

Choose a reason for hiding this comment

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

i don't understand spenderIsApprovedForAll, can you explain how that works? is it an admin function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

An owner can approve another address for permission over all of that owner's tokens.

@glitch003
Copy link
Contributor

also question - i didn't see the NFT name or metadata uri stuff in the erc721 interface. did i miss it? i wish we could inherit from openzeppelin erc721 but it doesn't use diamond storage.

@hwrdtm
Copy link
Contributor Author

hwrdtm commented Dec 11, 2025

overall, looks good. how does an existing delegated staker get an NFT? like what's the migration path?

We would need to script / use admin powers to manipulate existing state to account for the existing stakes.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a veNFT (voting-escrow NFT) interface for the staking system. The changes introduce ERC721 functionality to represent staking positions as transferable NFTs, allowing users to trade or transfer their staked positions while maintaining proper accounting.

Key changes:

  • Introduces veNFT interface with ERC721 standard functions (approve, transfer, balanceOf, etc.)
  • Renames the staking balance query function from balanceOf to selfStakeBalanceOf to avoid collision with ERC721's balanceOf
  • Adds NFT-specific tracking with tokenId fields in StakeRecord structs
  • Updates event handling in the listener to match new contract events

Reviewed changes

Copilot reviewed 29 out of 36 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
rust/lit-node/lit-node/src/peers/peer_state/listener.rs Changes event filter from RequestToJoinFilter to RequestToJoin1Filter (appears to contain naming issue)
rust/lit-core/lit-blockchain/src/contracts/pkp_helper.rs Updates contract bytecode for PKPHelper contract
rust/lit-core/lit-blockchain/abis/Staking.json Extensive ABI updates: adds veNFT/ERC721 functions, renames balanceOf to selfStakeBalanceOf, adds tokenId fields to StakeRecord structs
blockchain/contracts/test/lit-node/Staking.js Updates test calls from balanceOf to selfStakeBalanceOf
blockchain/contracts/test/lit-node/PubkeyRouter.js Adds StakingNFTFacet to the deployment configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@hwrdtm
Copy link
Contributor Author

hwrdtm commented Dec 11, 2025

also question - i didn't see the NFT name or metadata uri stuff in the erc721 interface. did i miss it? i wish we could inherit from openzeppelin erc721 but it doesn't use diamond storage.

  1. There actually are ERC721 implementations for diamond contracts that we are using already - check out PKPNFTFacet using ERC721Upgradeable. However, we need to implement our own custom implementation anyways, and for the same reason as Implement veNFT interface #23 (comment), we can't just inherit that.
  2. I didn't implement it yet, I didn't think we needed it. Should be simple to implement, though. In any case, I think we could do in the next PR though.

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.

3 participants