-
Notifications
You must be signed in to change notification settings - Fork 0
Create vlPUFFER #3
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
Merged
Merged
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
cb07bf6
add vlPUFFER contract
bxmmm1 b416316
change casing
bxmmm1 7611dde
update readme
bxmmm1 91d919d
update kickUser to kickUsers
bxmmm1 8d0385e
add more tests
bxmmm1 66e804b
update snapshot
bxmmm1 71b129b
write delegation test
bxmmm1 ab46b21
remove permit, token is not transferrable
bxmmm1 441a430
update multiplier and unlock time
bxmmm1 163c02e
update gas snapshot
bxmmm1 9c955ec
address pr comments
bxmmm1 20734c2
update config and fmt
bxmmm1 45c31e0
code style
bxmmm1 67eb0fa
update comment
bxmmm1 ee7fb2e
minor changes
bxmmm1 5a391dc
gas snapshot
bxmmm1 4bce0f5
update vlPUFFER
bxmmm1 488c9a1
update formatting
bxmmm1 c4aa253
update readme
bxmmm1 3894d4b
update locker to use multiplier instead of unlockTime
bxmmm1 08acc8e
updage snasphot
bxmmm1 459575f
snapshot 7702 test
bxmmm1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,39 @@ | ||
| PufferLockerTest:test_AutomaticBalanceExpiry() (gas: 463231) | ||
| PufferLockerTest:test_CreateLock() (gas: 464588) | ||
| PufferLockerTest:test_DelegateToPufferTeam() (gas: 512444) | ||
| PufferLockerTest:test_Delegation() (gas: 516030) | ||
| PufferLockerTest:test_DelegationWithMultipleLocks() (gas: 715270) | ||
| PufferLockerTest:test_EpochBasedBalance() (gas: 457333) | ||
| PufferLockerTest:test_GetAllLocks() (gas: 657506) | ||
| PufferLockerTest:test_InvalidLockId() (gas: 462996) | ||
| PufferLockerTest:test_LockAlignment() (gas: 445873) | ||
| PufferLockerTest:test_MaxLockTime() (gas: 459264) | ||
| PufferLockerTest:test_MultipleLocks() (gas: 675823) | ||
| PufferLockerTest:test_MultipleUsers() (gas: 1152438) | ||
| PufferLockerTest:test_NoExistingLock() (gas: 513668) | ||
| PufferLockerTest:test_PastLockTime() (gas: 32895) | ||
| PufferLockerTest:test_TotalSupplyAtEpoch() (gas: 752163) | ||
| PufferLockerTest:test_TransfersDisabled() (gas: 462168) | ||
| PufferLockerTest:test_WithdrawAfterDelegation() (gas: 519751) | ||
| PufferLockerTest:test_WithdrawExpiredLock() (gas: 489175) | ||
| PufferLockerTest:test_WithdrawMultipleExpiredLocks() (gas: 908448) | ||
| PufferLockerTest:test_ZeroValue() (gas: 20556) | ||
| PufferLockerTest:test_getExpiredLocks() (gas: 781967) | ||
| vlPUFFERTest:test_CLOCK_MODE() (gas: 9663) | ||
| vlPUFFERTest:test_RevertWhen_createLock_exceedsMaxLockTime() (gas: 54364) | ||
| vlPUFFERTest:test_RevertWhen_createLock_futureLockTimeRequired() (gas: 54112) | ||
| vlPUFFERTest:test_RevertWhen_createLock_insufficientAmount(uint256) (runs: 256, μ: 58760, ~: 58906) | ||
| vlPUFFERTest:test_RevertWhen_createLock_invalidDuration(uint256) (runs: 256, μ: 57241, ~: 57423) | ||
| vlPUFFERTest:test_RevertWhen_createLock_lockAlreadyExists() (gas: 295315) | ||
| vlPUFFERTest:test_RevertWhen_kickUser_beforeGracePeriod() (gas: 275500) | ||
| vlPUFFERTest:test_RevertWhen_kickUser_noLock() (gas: 18491) | ||
| vlPUFFERTest:test_RevertWhen_kickUser_twice() (gas: 313348) | ||
| vlPUFFERTest:test_RevertWhen_pause_notOwner() (gas: 13515) | ||
| vlPUFFERTest:test_RevertWhen_reLock_invalidDuration() (gas: 271127) | ||
| vlPUFFERTest:test_RevertWhen_reLock_invalidUnlockTime() (gas: 271846) | ||
| vlPUFFERTest:test_RevertWhen_reLock_noLockExists() (gas: 18261) | ||
| vlPUFFERTest:test_RevertWhen_transfer() (gas: 274266) | ||
| vlPUFFERTest:test_RevertWhen_transferFrom() (gas: 301632) | ||
| vlPUFFERTest:test_RevertWhen_unpause_notOwner() (gas: 40422) | ||
| vlPUFFERTest:test_RevertWhen_withdraw_beforeUnlock() (gas: 271594) | ||
| vlPUFFERTest:test_RevertWhen_withdraw_noLock() (gas: 18106) | ||
| vlPUFFERTest:test_clock() (gas: 8781) | ||
| vlPUFFERTest:test_constructor() (gas: 24642) | ||
| vlPUFFERTest:test_contract_deployment() (gas: 113927) | ||
| vlPUFFERTest:test_createLock() (gas: 273437) | ||
| vlPUFFERTest:test_createLockWithPermit() (gas: 317775) | ||
| vlPUFFERTest:test_createLockWithPermit_expired() (gas: 48781) | ||
| vlPUFFERTest:test_createLock_withDelegation() (gas: 271926) | ||
| vlPUFFERTest:test_delegation() (gas: 900454) | ||
| vlPUFFERTest:test_kickUser() (gas: 304249) | ||
| vlPUFFERTest:test_kickUsers() (gas: 486108) | ||
| vlPUFFERTest:test_kickUsers_withMultipleUsers() (gas: 486124) | ||
| vlPUFFERTest:test_kickUsers_withNoFee() (gas: 313348) | ||
| vlPUFFERTest:test_nonces() (gas: 13219) | ||
| vlPUFFERTest:test_pauseAndUnpause() (gas: 296969) | ||
| vlPUFFERTest:test_reLock() (gas: 310832) | ||
| vlPUFFERTest:test_reLock_decreaseVlPuffer() (gas: 291767) | ||
| vlPUFFERTest:test_reLock_decreaseVlPufferBalance() (gas: 294184) | ||
| vlPUFFERTest:test_reLock_withAdditionalTokens() (gas: 310788) | ||
| vlPUFFERTest:test_reLock_withZeroAmount() (gas: 306498) | ||
| vlPUFFERTest:test_reLock_withZeroAmountAndSameUnlockTime() (gas: 297448) | ||
| vlPUFFERTest:test_withdraw(uint256) (runs: 256, μ: 320938, ~: 321036) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,155 +1,101 @@ | ||
| # PufferLocker | ||
|
|
||
| PufferLocker is a production-grade Solidity contract implementing a token voting system for Puffer tokens, compatible with OpenZeppelin's ERC20Votes. The system enables users to lock Puffer tokens for a specified duration to receive proportional voting power. | ||
|
|
||
| ## Core Features | ||
|
|
||
| - **Epoch-Based Voting Power**: Voting power is allocated based on locked token amount × lock duration in weeks | ||
| - **Multiple Locks**: Users can have multiple independent locks with different expiration times | ||
| - **No Decay, Full Expiry**: Voting power remains constant during the lock period and expires completely at the end | ||
| - **Delegation**: Users can delegate their voting power to other addresses, including a specific Puffer team address | ||
| - **Non-transferable**: vlPUFFER tokens represent staked positions and cannot be transferred | ||
| - **History Tracking**: Epoch-based system allows querying historical voting power | ||
| - **Pausable**: Includes standard OpenZeppelin Pausable functionality with emergency withdrawals | ||
| - **Relock Capability**: Users can relock their expired tokens for a new duration without withdrawing | ||
| - **Gasless Approvals**: Supports ERC2612 permit functionality to create locks without requiring separate approval transactions | ||
|
|
||
| ## Technical Implementation | ||
|
|
||
| - **Lock Mechanism**: Tokens are locked for a user-specified duration (up to 2 years maximum) | ||
| - **Voting Power Calculation**: `votingPower = lockedAmount × lockDurationInWeeks` | ||
| - **vlPUFFER Tokens**: Non-transferable ERC20 tokens representing voting power | ||
| - **Immediate Expiration**: Voting power remains constant throughout the lock period and expires completely at the end | ||
| - **Collateral Integrity**: Original tokens always remain withdrawable after lock expiry | ||
| - **Pagination**: Efficient pagination support for users with many locks | ||
| - **Gas Optimization**: Optimized user tracking and epoch transitions for better gas efficiency | ||
| - **Seamless Relocking**: Expired locks can be renewed without withdrawing and redepositing tokens | ||
| - **Standard Security Controls**: Uses OpenZeppelin's Pausable implementation for emergency control | ||
| - **ERC2612 Support**: Implements permit functionality for gasless lock creation in a single transaction | ||
|
|
||
| ## Contract Architecture | ||
|
|
||
| ### State Management | ||
| - User locks are tracked in mapping `userLocks` with unique identifiers per user | ||
| - Active users are tracked to enable accurate `totalSupply` calculations | ||
| - Weekly epochs allow point-in-time queries through `balanceOfAtEpoch` and `totalSupplyAtEpoch` | ||
| - Two balance views: active (unexpired) and raw (total including expired) | ||
|
|
||
| ### Key Functions | ||
|
|
||
| ```solidity | ||
| // Create a new lock | ||
| function createLock(uint256 _value, uint256 _unlockTime) external returns (uint256 lockId); | ||
|
|
||
| // Create a new lock using permit functionality (no separate approval needed) | ||
| function createLockWithPermit(uint256 _value, uint256 _unlockTime, uint256 _deadline, uint8 v, bytes32 r, bytes32 s) external returns (uint256 lockId); | ||
|
|
||
| // Withdraw tokens from an expired lock | ||
| function withdraw(uint256 _lockId) external; | ||
|
|
||
| // Relock tokens from an expired lock for a new duration | ||
| function relockExpiredLock(uint256 _lockId, uint256 _unlockTime) external returns (bool); | ||
|
|
||
| // Delegate voting power to the Puffer team | ||
| function delegateToPufferTeam() external; | ||
|
|
||
| // Get active (unexpired) voting power | ||
| function balanceOf(address account) public view returns (uint256); | ||
|
|
||
| // Get raw vlToken balance (including expired tokens) | ||
| function getRawBalance(address account) external view returns (uint256); | ||
|
|
||
| // View voting power at a specific epoch | ||
| function balanceOfAtEpoch(address account, uint256 _epoch) public view returns (uint256); | ||
|
|
||
| // Pause the contract in emergency situations | ||
| function pause() external; | ||
|
|
||
| // Unpause the contract | ||
| function unpause() external; | ||
| # vlPUFFER: Voting & Locking for PUFFER | ||
|
|
||
| This document explains how the vlPUFFER system works in simple terms. vlPUFFER is a voting system where you lock your PUFFER tokens to gain voting power. | ||
|
|
||
| ## How It Works | ||
|
|
||
| - You lock your PUFFER tokens for a period of time (30 days to 2 years) | ||
| - The longer you lock, the more voting power (vlPUFFER tokens) you receive | ||
| - Your vlPUFFER tokens cannot be transferred - they represent your voting power | ||
| - Once your lock period ends, you can withdraw your original PUFFER tokens | ||
|
|
||
| ## Multiplier System | ||
|
|
||
| The amount of vlPUFFER (voting power) you receive depends on how long you lock: | ||
|
|
||
| | Lock Duration | Approximate Multiplier | | ||
| |---------------|------------------------| | ||
| | 30 days | 1x | | ||
| | 3 months | 3x | | ||
| | 6 months | 6x | | ||
| | 9 months | 9x | | ||
| | 12 months | 12x | | ||
| | 18 months | 18x | | ||
| | 24 months | 24x | | ||
|
|
||
| For example, if you lock 100 PUFFER for 6 months, you'll receive approximately 600 vlPUFFER tokens. | ||
|
|
||
| ## Entry Points (How to Lock) | ||
|
|
||
| 1. **Create a new lock** - Lock your PUFFER tokens for the first time | ||
| 2. **Create a lock with permit** - Lock your tokens in a single transaction | ||
| 3. **Re-lock** - You can: | ||
| - Add more PUFFER to your existing lock | ||
| - Extend your lock duration | ||
| - Both add more tokens and extend the duration | ||
|
|
||
| Minimum lock amount: 10 PUFFER | ||
|
|
||
| ## Exit Points (How to Unlock) | ||
|
|
||
| 1. **Withdraw** - Once your lock expires, you can withdraw your PUFFER tokens | ||
| 2. **Get kicked** - If you don't withdraw within 1 week after expiry, anyone can "kick" you: | ||
| - The kicker receives 1% of your PUFFER tokens | ||
| - The remaining 99% are returned to you automatically | ||
|
|
||
| ## Vote Delegation | ||
|
|
||
| vlPUFFER supports vote delegation, allowing you to: | ||
|
|
||
| - Delegate your voting power to another address (including yourself) | ||
| - By default, new locks are automatically self-delegated if you haven't chosen a delegate | ||
| - Delegate using a signature (delegateBySig) without requiring a transaction from the delegating account | ||
|
|
||
| ### Multiple Account Strategy | ||
|
|
||
| You can optimize your locking strategy using multiple accounts: | ||
|
|
||
| 1. Create different locks with different durations from separate accounts | ||
| - For example: 3 months in one account, 12 months in another, 24 months in a third | ||
| 2. Delegate the voting power from all these accounts to your main account | ||
| 3. This gives you flexibility to have different unlock schedules while concentrating voting power | ||
|
|
||
| ## Interaction Flow | ||
|
|
||
| ```mermaid | ||
| graph TD | ||
| User([User]) --> Lock["Create Lock"] | ||
| User --> LockPermit["Create Lock with Permit"] | ||
| User --> Relock["Re-lock (extend/add)"] | ||
|
|
||
| Lock --> vlPUFFER[("vlPUFFER (Voting Power)")] | ||
| LockPermit --> vlPUFFER | ||
| Relock --> vlPUFFER | ||
|
|
||
| vlPUFFER --> |"Lock expires"| Withdraw["Withdraw PUFFER"] | ||
| vlPUFFER --> |"Lock expires + 1 week"| Kick["Get Kicked"] | ||
| vlPUFFER --> Delegate["Delegate Votes"] | ||
|
|
||
| Withdraw --> PUFFER[("PUFFER Tokens")] | ||
| Kick --> |"99%"| PUFFER | ||
| Kick --> |"1%"| KickerFee["Kicker Fee"] | ||
|
|
||
| Account1([Account 1]) --> Lock1["Lock (3 months)"] | ||
| Account2([Account 2]) --> Lock2["Lock (12 months)"] | ||
| Account3([Account 3]) --> Lock3["Lock (24 months)"] | ||
|
|
||
| Lock1 --> vlPUFFER1["vlPUFFER"] | ||
| Lock2 --> vlPUFFER2["vlPUFFER"] | ||
| Lock3 --> vlPUFFER3["vlPUFFER"] | ||
|
|
||
| vlPUFFER1 --> |"Delegate"| MainAccount["Main Account (Combined Voting Power)"] | ||
| vlPUFFER2 --> |"Delegate"| MainAccount | ||
| vlPUFFER3 --> |"Delegate"| MainAccount | ||
| ``` | ||
|
|
||
| ## Deployment | ||
|
|
||
| The contract can be deployed to both testnet and mainnet environments using Foundry scripts. | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - Foundry installed (https://getfoundry.sh/) | ||
| - Ethereum RPC endpoints for Holesky testnet and/or Mainnet | ||
| - Private key for deployment | ||
| - Etherscan API key for verification | ||
|
|
||
| ### Deployment Commands | ||
|
|
||
| #### Holesky Testnet Deployment | ||
|
|
||
| ```bash | ||
| forge script script/Deploy.s.sol:DeployPufferLocker \ | ||
| --rpc-url $HOLESKY_RPC_URL \ | ||
| --private-key $PRIVATE_KEY \ | ||
| --broadcast \ | ||
| --verify \ | ||
| --etherscan-api-key $ETHERSCAN_API_KEY \ | ||
| -vvvv | ||
| ``` | ||
|
|
||
| #### Mainnet Deployment | ||
|
|
||
| ```bash | ||
| forge script script/Deploy.s.sol:DeployPufferLocker \ | ||
| --rpc-url $MAINNET_RPC_URL \ | ||
| --private-key $PRIVATE_KEY \ | ||
| --broadcast \ | ||
| --verify \ | ||
| --etherscan-api-key $ETHERSCAN_API_KEY \ | ||
| -vvvv | ||
| ``` | ||
|
|
||
| #### Custom Parameter Deployment | ||
|
|
||
| For deploying with custom token and team addresses: | ||
|
|
||
| ```bash | ||
| forge script script/Deploy.s.sol:DeployPufferLockerWithCustomParams \ | ||
| --sig "run(address)" $PUFFER_TEAM_ADDRESS \ | ||
| --rpc-url $RPC_URL \ | ||
| --private-key $PRIVATE_KEY \ | ||
| --broadcast \ | ||
| --verify \ | ||
| --etherscan-api-key $ETHERSCAN_API_KEY \ | ||
| -vvvv | ||
| ``` | ||
|
|
||
| ## Development and Testing | ||
|
|
||
| Contract is built using Foundry. To use: | ||
|
|
||
| ```bash | ||
| # Install dependencies | ||
| forge install | ||
|
|
||
| # Run tests | ||
| forge test | ||
|
|
||
| # Run attack vector tests | ||
| forge test --match-test "test_Lock|test_Epoch|test_Mass" | ||
|
|
||
| # Build | ||
| forge build | ||
| ``` | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| The contract has been tested against several potential attack vectors: | ||
|
|
||
| 1. **Lock Spam Attack**: Creating numerous small locks to bloat contract storage | ||
| 2. **Epoch Processing Attack**: Exploiting epoch transitions after periods of inactivity | ||
| 3. **Mass Withdrawal Attack**: Gas limitations with multiple withdrawals | ||
|
|
||
| All tests confirm the contract's resilience to these attack vectors. | ||
|
|
||
| ## License | ||
|
|
||
| Licensed under MIT | ||
| ## Important Notes | ||
|
|
||
| - You can only withdraw after your lock period ends | ||
| - Your vlPUFFER tokens cannot be transferred to other addresses | ||
| - If you don't withdraw within 1 week after lock expiry, anyone can kick you | ||
| - Re-locking cannot result in less voting power than you currently have |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.