https://github.com/pyk/llamalocker
Lock your LLAMAs to claim share of yields
https://github.com/pyk/llamalocker
ethereum nft
Last synced: 10 days ago
JSON representation
Lock your LLAMAs to claim share of yields
- Host: GitHub
- URL: https://github.com/pyk/llamalocker
- Owner: pyk
- Created: 2023-12-03T08:14:40.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-11-29T10:34:09.000Z (over 1 year ago)
- Last Synced: 2026-01-16T05:31:28.391Z (5 months ago)
- Topics: ethereum, nft
- Language: Solidity
- Homepage: https://www.thellamas.io/
- Size: 1.03 MB
- Stars: 1
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Audit: audits/ottersec.pdf
Awesome Lists containing this project
README
# Llama Locker
Llama Locker allows users to lock their LLAMA tokens and earn a share of the
yield generated by the treasury over time. It manages epochs, reward token
distribution, and token locking/unlocking, ensuring fair and transparent reward
distribution.
## Lock Mechanism
- Locked NFTs cannot be withdrawn for 4 epochs (4 weeks) and are eligible to
receive a proportionate share of yields during this period.
- Unlike the CVX Lock style, which requires active kicking out of tokens after
the lock duration ends, LlamaLocker offers a more user-friendly approach.
- NFT owners can withdraw their NFTs in the epoch after the lock duration ends.
If not withdrawn, the NFTs will automatically re-lock for the subsequent lock
duration, streamlining the process and saving users on gas costs.
### Example of Lock Mechanism
1. Alice locks Llama #1 on January 28, 2024, at 22:49:42 GMT.
2. Llama #1 starts accruing yields from February 1, 2024, at 00:00:00 GMT (next epoch).
3. Withdrawal of Llama #1 is possible anytime from February 29, 2024, at 00:00:00 GMT to March 7, 2024, at 00:00:00 GMT (one-week epoch).
4. If Llama #1 remains unwithdrawn during this window, it will automatically re-lock starting March 7, 2024, at 00:00:00 GMT.
## Getting Started
Ensure you are using the latest version of Foundry:
```shell
foundryup
```
Install dependencies:
```shell
forge install
```
Run the tests:
```shell
forge test
```
Example output:
```
$ forge test
[⠊] Compiling...
[⠑] Compiling 1 files with Solc 0.8.23
[⠘] Solc 0.8.23 finished in 1.71s
Compiler run successful!
Ran 1 test for test/LlamaLocker.t.sol:LlamaLockerTest
[PASS] test_renounceOwnership_InvalidAction() (gas: 13389)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.44ms (93.38µs CPU time)
Ran 4 tests for test/AddRewardTokens.t.sol:AddRewardTokensTest
[PASS] test_addRewardTokens_InvalidRewardToken() (gas: 92195)
[PASS] test_addRewardTokens_InvalidRewardTokenCount() (gas: 13954)
[PASS] test_addRewardTokens_Unauthorized() (gas: 14191)
[PASS] test_addRewardTokens_Valid() (gas: 133255)
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.60ms (179.63µs CPU time)
Ran 8 tests for test/Whitelist.t.sol:RewardDistributionTest
[PASS] test_disableWhitelist_InvalidAction() (gas: 20211)
[PASS] test_disableWhitelist_Unauthorized() (gas: 13655)
[PASS] test_disableWhitelist_Valid() (gas: 19409)
[PASS] test_lock_InvalidAction() (gas: 115192)
[PASS] test_lock_Valid() (gas: 225023)
[PASS] test_setRoot_InvalidAction() (gas: 15583)
[PASS] test_setRoot_Unauthorized() (gas: 13695)
[PASS] test_setRoot_Valid() (gas: 19625)
Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 1.65ms (751.29µs CPU time)
Ran 6 tests for test/LockMechanism.t.sol:LockMechanismTest
[PASS] test_lock_InvalidTokenCount() (gas: 11454)
[PASS] test_lock_Valid() (gas: 496076)
[PASS] test_unlock_InvalidLockOwner() (gas: 225991)
[PASS] test_unlock_InvalidTokenCount() (gas: 9265)
[PASS] test_unlock_InvalidUnlockWindow() (gas: 246840)
[PASS] test_unlock_ValidUnlockWindow() (gas: 222618)
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 1.71ms (784.38µs CPU time)
Ran 5 tests for test/RewardDistribution.t.sol:RewardDistributionTest
[PASS] test_distributeRewardToken_Claimables() (gas: 1020171)
[PASS] test_distributeRewardToken_InvalidRewardAmount() (gas: 134541)
[PASS] test_distributeRewardToken_InvalidRewardToken() (gas: 17599)
[PASS] test_distributeRewardToken_InvalidTotalShares() (gas: 136631)
[PASS] test_distributeRewardToken_Unauthorized() (gas: 13900)
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 2.06ms (1.10ms CPU time)
Ran 1 test for test/OffchainQuery.sol:LockMechanismTest
[PASS] test_getLocks() (gas: 11133290)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.34ms (9.86ms CPU time)
Ran 6 test suites in 161.35ms (19.81ms CPU time): 25 tests passed, 0 failed, 0 skipped (25 total tests)
```
## Generate Merkle Tree
Install dependencies:
```sh
pnpm install
```
Update the `whitelist.txt` file.
Then get the merkle tree root and proofs via the following command:
```sh
pnpm run gen:merkle
```
Check the proof inside `merkle-proofs.json`.
## Front End Integration
There are two main actions for users:
1. **Lock NFT:** Users can lock their NFT via `lock(proofs, tokenIds)`
for whitelisted user and `lock(tokenIds)` for public.
2. **Unlock NFT:** Users can unlock their NFT via `unlock`.
Additional information:
- Claimable rewards are available via `claimables(account)`.
- Claimed rewards are available via `getClaimedRewards(account)`.
- Lock information can be retrieved via `locks(nftId)`.
To compute the next unlock for the specified NFT, use the following formula:
```shell
lockedDuration = currentTimestamp - lockedAt
lockedDurationInEpoch = lockedDuration / EPOCH_DURATION
modulo = lockedDurationInEpoch % LOCK_DURATION_IN_EPOCH
unlockNextEpoch = LOCK_DURATION_IN_EPOCH - modulo
unlockStart = currentTimestamp + (unlockNextEpoch * EPOCH_DURATION)
unlockEnd = unlockStart + EPOCH_DURATION
```
`unlockStart` and `unlockEnd` define a time window in Unix timestamp when users can unlock their locked NFTs.
Admin actions:
- Admin can add a new reward token via `addRewardToken`.
- Admin can distribute weekly rewards via `distributeRewardToken`.
- Admin need to approve the LlamaLocker contract before executing `distributeRewardToken`.
- Admin can set new merkle tree root via `setRoot(root)` (for rolling whitelist)
- Admin can disable the whitelist via `disableWhitelist()` (for public launch)
## Gas Report
```
| src/LlamaLocker.sol:LlamaLocker contract | | | | | |
|------------------------------------------|-----------------|---------|---------|---------|---------|
| Deployment Cost | Deployment Size | | | | |
| 2020278 | 9131 | | | | |
| Function Name | min | avg | median | max | # calls |
| addRewardTokens | 24227 | 78136 | 84896 | 139290 | 10 |
| claim | 162883 | 162883 | 162883 | 162883 | 1 |
| claimable | 2401 | 3067 | 2401 | 4401 | 12 |
| disableWhitelist | 23478 | 28734 | 29335 | 29335 | 16 |
| distributeRewardToken | 24051 | 45139 | 28627 | 81528 | 7 |
| getLocks | 8018452 | 8018452 | 8018452 | 8018452 | 1 |
| getLocksByOwner | 1364152 | 1364152 | 1364152 | 1364152 | 2 |
| getRewardTokenCount | 370 | 370 | 370 | 370 | 1 |
| lock(bytes32[],uint256[]) | 26080 | 95257 | 95257 | 164435 | 2 |
| lock(uint256[]) | 24103 | 147342 | 160540 | 183588 | 12 |
| renounceOwnership | 23504 | 23504 | 23504 | 23504 | 1 |
| root | 350 | 350 | 350 | 350 | 1 |
| setRoot | 24077 | 26788 | 26180 | 30107 | 3 |
| unlock | 21914 | 33682 | 28960 | 78956 | 9 |
| whitelistDisabled | 378 | 378 | 378 | 378 | 1 |
```
Assuming gas fee is 10gwei:
- Deployment: 0,00000001 * 2020278 = 0,02020278 ETH
- `claim`: 0,00000001 * 162883 = 0,00162883 ETH
- `lock(bytes32[],uint256[])` (lock for whitelisted addy): 0,00000001 * 95257 = 0,00095257 ETH
- `lock(uint256[])`: 0,00000001 * 147342 = 0,00147342 ETH
- `unlock`: 0,00000001 * 33682 = 0,00033682 ETH