mirror of
https://github.com/vacp2p/staking-reward-streamer.git
synced 2026-01-08 23:08:19 -05:00
import foundry template (#1)
This commit is contained in:
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
## Description
|
||||||
|
|
||||||
|
Describe the changes made in your pull request here.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
Ensure you completed **all of the steps** below before submitting your pull request:
|
||||||
|
|
||||||
|
- [ ] Added natspec comments?
|
||||||
|
- [ ] Ran `pnpm adorno`?
|
||||||
|
- [ ] Ran `pnpm verify`?
|
||||||
18
.github/workflows/add-issue-to-project-board.yml
vendored
Normal file
18
.github/workflows/add-issue-to-project-board.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Add issue to task board
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-to-project:
|
||||||
|
name: Add to task board
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/add-to-project@v0.5.0
|
||||||
|
with:
|
||||||
|
# You can target a project in a different organization
|
||||||
|
# to the issue
|
||||||
|
project-url: https://github.com/orgs/vacp2p/projects/10
|
||||||
|
github-token: ${{ secrets.ADD_TO_VAC_BOARD_PAT }}
|
||||||
18
.github/workflows/add-pr-to-project-board.yml
vendored
Normal file
18
.github/workflows/add-pr-to-project-board.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Add PR task board
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-to-project:
|
||||||
|
name: Add to task board
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/add-to-project@v0.5.0
|
||||||
|
with:
|
||||||
|
# You can target a project in a different organization
|
||||||
|
# to the issue
|
||||||
|
project-url: https://github.com/orgs/vacp2p/projects/10
|
||||||
|
github-token: ${{ secrets.ADD_TO_VAC_BOARD_PAT }}
|
||||||
171
.github/workflows/ci.yml
vendored
Normal file
171
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
name: "CI"
|
||||||
|
|
||||||
|
env:
|
||||||
|
API_KEY_ALCHEMY: ${{ secrets.API_KEY_ALCHEMY }}
|
||||||
|
FOUNDRY_PROFILE: "ci"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
cancel-in-progress: true
|
||||||
|
group: ${{github.workflow}}-${{github.ref}}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- name: "Check out the repo"
|
||||||
|
uses: "actions/checkout@v4"
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
|
||||||
|
- name: "Install Foundry"
|
||||||
|
uses: "foundry-rs/foundry-toolchain@v1"
|
||||||
|
|
||||||
|
- name: "Install Pnpm"
|
||||||
|
uses: "pnpm/action-setup@v2"
|
||||||
|
with:
|
||||||
|
version: "8"
|
||||||
|
|
||||||
|
- name: "Install Node.js"
|
||||||
|
uses: "actions/setup-node@v3"
|
||||||
|
with:
|
||||||
|
cache: "pnpm"
|
||||||
|
node-version: "lts/*"
|
||||||
|
|
||||||
|
- name: "Install the Node.js dependencies"
|
||||||
|
run: "pnpm install"
|
||||||
|
|
||||||
|
- name: "Lint the contracts"
|
||||||
|
run: "pnpm lint"
|
||||||
|
|
||||||
|
- name: "Add lint summary"
|
||||||
|
run: |
|
||||||
|
echo "## Lint result" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- name: "Check out the repo"
|
||||||
|
uses: "actions/checkout@v4"
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
|
||||||
|
- name: "Install Foundry"
|
||||||
|
uses: "foundry-rs/foundry-toolchain@v1"
|
||||||
|
|
||||||
|
- name: "Build the contracts and print their size"
|
||||||
|
run: "forge build --sizes"
|
||||||
|
|
||||||
|
- name: "Add build summary"
|
||||||
|
run: |
|
||||||
|
echo "## Build result" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: ["lint", "build"]
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- name: "Check out the repo"
|
||||||
|
uses: "actions/checkout@v4"
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
|
||||||
|
- name: "Install Foundry"
|
||||||
|
uses: "foundry-rs/foundry-toolchain@v1"
|
||||||
|
|
||||||
|
- name: "Show the Foundry config"
|
||||||
|
run: "forge config"
|
||||||
|
|
||||||
|
- name: "Generate a fuzz seed that changes weekly to avoid burning through RPC allowance"
|
||||||
|
run: >
|
||||||
|
echo "FOUNDRY_FUZZ_SEED=$(
|
||||||
|
echo $(($EPOCHSECONDS - $EPOCHSECONDS % 604800))
|
||||||
|
)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: "Run the tests"
|
||||||
|
run: "forge test"
|
||||||
|
|
||||||
|
- name: "Add test summary"
|
||||||
|
run: |
|
||||||
|
echo "## Tests result" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
needs: ["lint", "build"]
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- name: "Check out the repo"
|
||||||
|
uses: "actions/checkout@v4"
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
|
||||||
|
- name: "Install Foundry"
|
||||||
|
uses: "foundry-rs/foundry-toolchain@v1"
|
||||||
|
|
||||||
|
- name: "Generate the coverage report using the unit and the integration tests"
|
||||||
|
run: 'forge coverage --match-path "test/**/*.sol" --report lcov'
|
||||||
|
|
||||||
|
- name: "Upload coverage report to Codecov"
|
||||||
|
uses: "codecov/codecov-action@v3"
|
||||||
|
with:
|
||||||
|
files: "./lcov.info"
|
||||||
|
|
||||||
|
- name: "Add coverage summary"
|
||||||
|
run: |
|
||||||
|
echo "## Coverage result" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ Uploaded to Codecov" >> $GITHUB_STEP_SUMMARY
|
||||||
|
verify:
|
||||||
|
needs: ["lint", "build"]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with: { python-version: 3.9 }
|
||||||
|
|
||||||
|
- name: Install Java
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with: { java-version: "11", java-package: jre }
|
||||||
|
|
||||||
|
- name: Install Certora CLI
|
||||||
|
run: pip3 install certora-cli==5.0.5
|
||||||
|
|
||||||
|
- name: Install Solidity
|
||||||
|
run: |
|
||||||
|
wget https://github.com/ethereum/solidity/releases/download/v0.8.26/solc-static-linux
|
||||||
|
chmod +x solc-static-linux
|
||||||
|
sudo mv solc-static-linux /usr/local/bin/solc
|
||||||
|
|
||||||
|
- name: "Install Pnpm"
|
||||||
|
uses: "pnpm/action-setup@v2"
|
||||||
|
with:
|
||||||
|
version: "8"
|
||||||
|
|
||||||
|
- name: "Install Node.js"
|
||||||
|
uses: "actions/setup-node@v3"
|
||||||
|
with:
|
||||||
|
cache: "pnpm"
|
||||||
|
node-version: "lts/*"
|
||||||
|
|
||||||
|
- name: "Install the Node.js dependencies"
|
||||||
|
run: "pnpm install"
|
||||||
|
|
||||||
|
- name: Verify rules
|
||||||
|
run: "pnpm verify"
|
||||||
|
env:
|
||||||
|
CERTORAKEY: ${{ secrets.CERTORAKEY }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 16
|
||||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -1,14 +1,19 @@
|
|||||||
# Compiler files
|
# directories
|
||||||
cache/
|
cache
|
||||||
out/
|
node_modules
|
||||||
|
out
|
||||||
|
|
||||||
# Ignores development broadcast logs
|
# files
|
||||||
!/broadcast
|
*.env
|
||||||
/broadcast/*/31337/
|
*.log
|
||||||
/broadcast/**/dry-run/
|
.DS_Store
|
||||||
|
.pnp.*
|
||||||
|
lcov.info
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
# Docs
|
# broadcasts
|
||||||
docs/
|
!broadcast
|
||||||
|
broadcast/*
|
||||||
|
broadcast/*/31337/
|
||||||
|
|
||||||
# Dotenv file
|
.certora_internal
|
||||||
.env
|
|
||||||
|
|||||||
18
.prettierignore
Normal file
18
.prettierignore
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# directories
|
||||||
|
broadcast
|
||||||
|
cache
|
||||||
|
lib
|
||||||
|
node_modules
|
||||||
|
out
|
||||||
|
|
||||||
|
# files
|
||||||
|
*.env
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.pnp.*
|
||||||
|
lcov.info
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
slither.config.json
|
||||||
7
.prettierrc.yml
Normal file
7
.prettierrc.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
bracketSpacing: true
|
||||||
|
printWidth: 120
|
||||||
|
proseWrap: "always"
|
||||||
|
singleQuote: false
|
||||||
|
tabWidth: 2
|
||||||
|
trailingComma: "all"
|
||||||
|
useTabs: false
|
||||||
13
.solhint.json
Normal file
13
.solhint.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "solhint:recommended",
|
||||||
|
"rules": {
|
||||||
|
"code-complexity": ["error", 8],
|
||||||
|
"compiler-version": ["error", ">=0.8.26"],
|
||||||
|
"func-name-mixedcase": "off",
|
||||||
|
"func-visibility": ["error", { "ignoreConstructors": true }],
|
||||||
|
"max-line-length": ["error", 120],
|
||||||
|
"named-parameters-mapping": "warn",
|
||||||
|
"no-console": "off",
|
||||||
|
"not-rely-on-time": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
16
LICENSE.md
Normal file
16
LICENSE.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Institute of Free Technology
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
15
PROPERTIES.md
Normal file
15
PROPERTIES.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
## Protocol properties and invariants
|
||||||
|
|
||||||
|
Below is a list of all documented properties and invariants of this project that must hold true.
|
||||||
|
|
||||||
|
- **Property** - Describes the property of the project / protocol that should ultimately be tested and formaly verified.
|
||||||
|
- **Type** - Properties are split into 5 main types: **Valid State**, **State Transition**, **Variable Transition**,
|
||||||
|
**High-Level Property**, **Unit Test**
|
||||||
|
- **Risk** - One of **High**, **Medium** and **Low**, depending on the property's risk factor
|
||||||
|
- **Tested** - Whether this property has been (fuzz) tested
|
||||||
|
|
||||||
|
| **Property** | **Type** | **Risk** | **Tested** |
|
||||||
|
| ------------ | -------- | -------- | ---------- |
|
||||||
|
| | | | |
|
||||||
|
| | | | |
|
||||||
|
| | | | |
|
||||||
125
README.md
125
README.md
@@ -12,19 +12,18 @@ It represents the accumulated rewards per staked token since the beginning of th
|
|||||||
Here's how it works:
|
Here's how it works:
|
||||||
|
|
||||||
1. Initial state: When the contract starts, rewardIndex is 0.
|
1. Initial state: When the contract starts, rewardIndex is 0.
|
||||||
2. Whenever new rewards are added to the contract (detected in updateRewardIndex()), the rewardIndex increases.
|
2. Whenever new rewards are added to the contract (detected in updateRewardIndex()), the rewardIndex increases. The
|
||||||
The increase is calculated as:
|
increase is calculated as: `rewardIndex += (newRewards * SCALE_FACTOR) / totalStaked` This calculation distributes
|
||||||
`rewardIndex += (newRewards * SCALE_FACTOR) / totalStaked`
|
the new rewards evenly across all staked tokens.
|
||||||
This calculation distributes the new rewards evenly across all staked tokens.
|
3. Each user has their own userRewardIndex, which represents the global rewardIndex at the time of their last
|
||||||
3. Each user has their own userRewardIndex, which represents the global rewardIndex at the time
|
interaction (stake, unstake, or reward claim).
|
||||||
of their last interaction (stake, unstake, or reward claim).
|
4. When a user wants to claim rewards, we calculate the difference between the current rewardIndex and the user's
|
||||||
4. When a user wants to claim rewards, we calculate the difference between the current rewardIndex
|
userRewardIndex, multiply it by their staked balance, and divide by SCALE_FACTOR
|
||||||
and the user's userRewardIndex, multiply it by their staked balance, and divide by SCALE_FACTOR
|
5. After a user stakes, unstakes, or claims rewards, their userRewardIndex is updated to the current global rewardIndex.
|
||||||
5. After a user stakes, unstakes, or claims rewards, their userRewardIndex is updated to the current
|
This "resets" their reward accumulation for the next period.
|
||||||
global rewardIndex. This "resets" their reward accumulation for the next period.
|
|
||||||
|
|
||||||
Instead of updating each user's rewards every time new rewards are added, we only need to update a
|
Instead of updating each user's rewards every time new rewards are added, we only need to update a single global
|
||||||
single global variable (rewardIndex).
|
variable (rewardIndex).
|
||||||
|
|
||||||
We don't need to assign Rewards to epochs, so we don't need to finalize Rewords for each epoch and each user.
|
We don't need to assign Rewards to epochs, so we don't need to finalize Rewords for each epoch and each user.
|
||||||
|
|
||||||
@@ -38,111 +37,111 @@ User-specific calculations are done only when a user interacts with the contract
|
|||||||
|
|
||||||
**Initial setup:**
|
**Initial setup:**
|
||||||
|
|
||||||
* rewardIndex: 0
|
- rewardIndex: 0
|
||||||
* accountedRewards: 0
|
- accountedRewards: 0
|
||||||
* Rewards in contract: 0
|
- Rewards in contract: 0
|
||||||
|
|
||||||
**T1: Alice stakes 10 tokens**
|
**T1: Alice stakes 10 tokens**
|
||||||
|
|
||||||
* Alice's userRewardIndex: 0
|
- Alice's userRewardIndex: 0
|
||||||
* Alice's staked tokens: 10
|
- Alice's staked tokens: 10
|
||||||
* totalStaked: 10 tokens
|
- totalStaked: 10 tokens
|
||||||
|
|
||||||
**T2: Bob stakes 30 tokens**
|
**T2: Bob stakes 30 tokens**
|
||||||
|
|
||||||
* Alice's userRewardIndex: 0
|
- Alice's userRewardIndex: 0
|
||||||
* Bob's userRewardIndex: 0
|
- Bob's userRewardIndex: 0
|
||||||
* Alice's staked tokens: 10
|
- Alice's staked tokens: 10
|
||||||
* Bob's staked tokens: 30
|
- Bob's staked tokens: 30
|
||||||
* totalStaked: 40 tokens
|
- totalStaked: 40 tokens
|
||||||
|
|
||||||
**T3: 1000 Rewards arrive**
|
**T3: 1000 Rewards arrive**
|
||||||
|
|
||||||
New rewardIndex calculation:
|
New rewardIndex calculation:
|
||||||
|
|
||||||
* newRewards = 1000
|
- newRewards = 1000
|
||||||
* rewardIndex increase = 1000 / 40 = 25
|
- rewardIndex increase = 1000 / 40 = 25
|
||||||
* rewardIndex = 0 + 25 = 25
|
- rewardIndex = 0 + 25 = 25
|
||||||
* accountedRewards: 1000
|
- accountedRewards: 1000
|
||||||
* Rewards in contract: 1000
|
- Rewards in contract: 1000
|
||||||
|
|
||||||
Potential Rewards for Alice and Bob:
|
Potential Rewards for Alice and Bob:
|
||||||
|
|
||||||
For Alice:
|
For Alice:
|
||||||
|
|
||||||
* Staked amount: 10 tokens
|
- Staked amount: 10 tokens
|
||||||
* Potential Rewards: 10 * (25 - 0) = 250
|
- Potential Rewards: 10 \* (25 - 0) = 250
|
||||||
|
|
||||||
For Bob:
|
For Bob:
|
||||||
|
|
||||||
* Staked amount: 30 tokens
|
- Staked amount: 30 tokens
|
||||||
* Potential Rewards: 30 * (25 - 0) = 750
|
- Potential Rewards: 30 \* (25 - 0) = 750
|
||||||
|
|
||||||
**T4: Alice withdraws her stake and Rewards**
|
**T4: Alice withdraws her stake and Rewards**
|
||||||
|
|
||||||
Alice's withdrawal:
|
Alice's withdrawal:
|
||||||
|
|
||||||
* tokens returned: 10
|
- tokens returned: 10
|
||||||
* Rewards: 250
|
- Rewards: 250
|
||||||
|
|
||||||
Update state:
|
Update state:
|
||||||
|
|
||||||
* totalStaked = 40 - 10 = 30 tokens
|
- totalStaked = 40 - 10 = 30 tokens
|
||||||
* Rewards in contract = 1000 - 250 = 750
|
- Rewards in contract = 1000 - 250 = 750
|
||||||
|
|
||||||
**T5: Charlie stakes 30 tokens**
|
**T5: Charlie stakes 30 tokens**
|
||||||
|
|
||||||
* Charlie's userRewardIndex: 25
|
- Charlie's userRewardIndex: 25
|
||||||
* totalStaked = 30 + 30 = 60 tokens
|
- totalStaked = 30 + 30 = 60 tokens
|
||||||
|
|
||||||
**T6: Another 1000 Rewards arrive**
|
**T6: Another 1000 Rewards arrive**
|
||||||
|
|
||||||
New rewardIndex calculation:
|
New rewardIndex calculation:
|
||||||
|
|
||||||
* newRewards = 1000
|
- newRewards = 1000
|
||||||
* rewardIndex increase = 1000 / 60 = 16.67
|
- rewardIndex increase = 1000 / 60 = 16.67
|
||||||
* new rewardIndex = 25 + 16.67 = 41.67
|
- new rewardIndex = 25 + 16.67 = 41.67
|
||||||
* accountedRewards: 1000 + 1000 = 2000
|
- accountedRewards: 1000 + 1000 = 2000
|
||||||
* Rewards in contract = 750 + 1000 = 1750
|
- Rewards in contract = 750 + 1000 = 1750
|
||||||
|
|
||||||
Rewards for Bob and Charlie:
|
Rewards for Bob and Charlie:
|
||||||
|
|
||||||
For Bob:
|
For Bob:
|
||||||
|
|
||||||
* Staked amount: 30 tokens
|
- Staked amount: 30 tokens
|
||||||
* Potential Rewards: 30 * (41.67 - 0) = 1250.1 // rounding error
|
- Potential Rewards: 30 \* (41.67 - 0) = 1250.1 // rounding error
|
||||||
* In bucket 1: 30 * (25 - 0) = 750
|
- In bucket 1: 30 \* (25 - 0) = 750
|
||||||
* In bucket 2: 30 * (16.67 - 0) = 500.1
|
- In bucket 2: 30 \* (16.67 - 0) = 500.1
|
||||||
* Total of b1 + b2: 750 + 500.1 = 1250.1
|
- Total of b1 + b2: 750 + 500.1 = 1250.1
|
||||||
* Which is equal to
|
- Which is equal to
|
||||||
* 30 * ( (25 - 0) + (41.67 - 25) )
|
- 30 \* ( (25 - 0) + (41.67 - 25) )
|
||||||
|
|
||||||
For Charlie:
|
For Charlie:
|
||||||
|
|
||||||
* Staked amount: 30 tokens
|
- Staked amount: 30 tokens
|
||||||
* Potential Rewards: 30 * (41.67 - 25) = 500.1 // rounding error
|
- Potential Rewards: 30 \* (41.67 - 25) = 500.1 // rounding error
|
||||||
|
|
||||||
If Bob and Charlie were to withdraw now:
|
If Bob and Charlie were to withdraw now:
|
||||||
|
|
||||||
Bob's withdrawal:
|
Bob's withdrawal:
|
||||||
|
|
||||||
* tokens returned: 30
|
- tokens returned: 30
|
||||||
* Rewards: 1250.1
|
- Rewards: 1250.1
|
||||||
* Rewards in contract after Bob's withdrawal: 1750 - 1250.1 = 499.9
|
- Rewards in contract after Bob's withdrawal: 1750 - 1250.1 = 499.9
|
||||||
|
|
||||||
Charlie's withdrawal:
|
Charlie's withdrawal:
|
||||||
|
|
||||||
* tokens returned: 30
|
- tokens returned: 30
|
||||||
* Rewards: 499.9
|
- Rewards: 499.9
|
||||||
* Rewards in contract after Charlie's withdrawal: 499.9 - 499.9 = 0
|
- Rewards in contract after Charlie's withdrawal: 499.9 - 499.9 = 0
|
||||||
|
|
||||||
**T7: Final state:**
|
**T7: Final state:**
|
||||||
|
|
||||||
* Alice received: 10 tokens and 250 Rewards
|
- Alice received: 10 tokens and 250 Rewards
|
||||||
* Bob received: 30 tokens and 1250.1 Rewards
|
- Bob received: 30 tokens and 1250.1 Rewards
|
||||||
* Charlie received: 30 tokens and 499.9 Rewards
|
- Charlie received: 30 tokens and 499.9 Rewards
|
||||||
* Total Rewards distributed: 2000 Rewards
|
- Total Rewards distributed: 2000 Rewards
|
||||||
* Rewards remaining in contract: 0
|
- Rewards remaining in contract: 0
|
||||||
|
|
||||||
## Rewards Streamer with Multiplier Points
|
## Rewards Streamer with Multiplier Points
|
||||||
|
|
||||||
|
|||||||
14
certora/certora.conf
Normal file
14
certora/certora.conf
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"files": ["src/RewardsStreamerMP.sol"],
|
||||||
|
"msg": "Verifying RewardsStreamerMP.sol",
|
||||||
|
"rule_sanity": "basic",
|
||||||
|
"verify": "RewardsStreamerMP:certora/specs/RewardsStreamerMP.spec",
|
||||||
|
"wait_for_results": "all",
|
||||||
|
"optimistic_loop": true,
|
||||||
|
"loop_iter": "3",
|
||||||
|
"packages": [
|
||||||
|
"forge-std=lib/forge-std/src",
|
||||||
|
"@openzeppelin=lib/openzeppelin-contracts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
3
certora/specs/RewardsStreamerMP.spec
Normal file
3
certora/specs/RewardsStreamerMP.spec
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
rule checkIdOutputIsAlwaysEqualToInput {
|
||||||
|
assert true;
|
||||||
|
}
|
||||||
28
codecov.yml
Normal file
28
codecov.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
codecov:
|
||||||
|
require_ci_to_pass: false
|
||||||
|
comment: false
|
||||||
|
ignore:
|
||||||
|
- "script"
|
||||||
|
- "test"
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
# advanced settings
|
||||||
|
|
||||||
|
# Prevents PR from being blocked with a reduction in coverage.
|
||||||
|
# Note, if we want to re-enable this, a `threshold` value can be used
|
||||||
|
# allow coverage to drop by x% while still posting a success status.
|
||||||
|
# `informational`: https://docs.codecov.com/docs/commit-status#informational
|
||||||
|
# `threshold`: https://docs.codecov.com/docs/commit-status#threshold
|
||||||
|
informational: true
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
# advanced settings
|
||||||
|
|
||||||
|
# Prevents PR from being blocked with a reduction in coverage.
|
||||||
|
# Note, if we want to re-enable this, a `threshold` value can be used
|
||||||
|
# allow coverage to drop by x% while still posting a success status.
|
||||||
|
# `informational`: https://docs.codecov.com/docs/commit-status#informational
|
||||||
|
# `threshold`: https://docs.codecov.com/docs/commit-status#threshold
|
||||||
|
informational: true
|
||||||
59
foundry.toml
59
foundry.toml
@@ -1,6 +1,55 @@
|
|||||||
[profile.default]
|
# Full reference https://github.com/foundry-rs/foundry/tree/master/config
|
||||||
src = "src"
|
|
||||||
out = "out"
|
|
||||||
libs = ["lib"]
|
|
||||||
|
|
||||||
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
|
[profile.default]
|
||||||
|
auto_detect_solc = false
|
||||||
|
block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT
|
||||||
|
bytecode_hash = "none"
|
||||||
|
cbor_metadata = false
|
||||||
|
evm_version = "paris"
|
||||||
|
fuzz = { runs = 1_000 }
|
||||||
|
gas_reports = ["*"]
|
||||||
|
libs = ["lib"]
|
||||||
|
optimizer = true
|
||||||
|
optimizer_runs = 10_000
|
||||||
|
out = "out"
|
||||||
|
script = "script"
|
||||||
|
solc = "0.8.26"
|
||||||
|
src = "src"
|
||||||
|
test = "test"
|
||||||
|
|
||||||
|
[profile.ci]
|
||||||
|
fuzz = { runs = 10_000 }
|
||||||
|
verbosity = 4
|
||||||
|
|
||||||
|
[etherscan]
|
||||||
|
arbitrum = { key = "${API_KEY_ARBISCAN}" }
|
||||||
|
avalanche = { key = "${API_KEY_SNOWTRACE}" }
|
||||||
|
bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" }
|
||||||
|
gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" }
|
||||||
|
goerli = { key = "${API_KEY_ETHERSCAN}" }
|
||||||
|
mainnet = { key = "${API_KEY_ETHERSCAN}" }
|
||||||
|
optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" }
|
||||||
|
polygon = { key = "${API_KEY_POLYGONSCAN}" }
|
||||||
|
sepolia = { key = "${API_KEY_ETHERSCAN}" }
|
||||||
|
|
||||||
|
[fmt]
|
||||||
|
bracket_spacing = true
|
||||||
|
int_types = "long"
|
||||||
|
line_length = 120
|
||||||
|
multiline_func_header = "all"
|
||||||
|
number_underscore = "thousands"
|
||||||
|
quote_style = "double"
|
||||||
|
tab_width = 4
|
||||||
|
wrap_comments = true
|
||||||
|
|
||||||
|
[rpc_endpoints]
|
||||||
|
arbitrum = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}"
|
||||||
|
avalanche = "https://avalanche-mainnet.infura.io/v3/${API_KEY_INFURA}"
|
||||||
|
bnb_smart_chain = "https://bsc-dataseed.binance.org"
|
||||||
|
gnosis_chain = "https://rpc.gnosischain.com"
|
||||||
|
goerli = "https://goerli.infura.io/v3/${API_KEY_INFURA}"
|
||||||
|
localhost = "http://localhost:8545"
|
||||||
|
mainnet = "https://eth-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}"
|
||||||
|
optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}"
|
||||||
|
polygon = "https://polygon-mainnet.infura.io/v3/${API_KEY_INFURA}"
|
||||||
|
sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}"
|
||||||
|
|||||||
21
githooks/pre-commit-adorno
Normal file
21
githooks/pre-commit-adorno
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
foundryup
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "foundryup failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pnpm run adorno
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "pnpm run adorno failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git add .
|
||||||
|
|
||||||
|
echo "Successfully ran pnpm run adorno and added modified files."
|
||||||
|
|
||||||
|
exit 0
|
||||||
34
package.json
Normal file
34
package.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "@vacp2p/staking-rewards-streamer",
|
||||||
|
"description": "",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": {
|
||||||
|
"url": "https://github.com/vacp2p"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"solhint-community": "^3.6.0",
|
||||||
|
"commit-and-tag-version": "^12.2.0"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"blockchain",
|
||||||
|
"ethereum",
|
||||||
|
"forge",
|
||||||
|
"foundry",
|
||||||
|
"smart-contracts",
|
||||||
|
"solidity",
|
||||||
|
"template"
|
||||||
|
],
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rm -rf cache out",
|
||||||
|
"lint": "pnpm lint:sol && pnpm prettier:check",
|
||||||
|
"verify": "certoraRun certora/certora.conf",
|
||||||
|
"lint:sol": "forge fmt --check && pnpm solhint {script,src,test,certora}/**/*.sol",
|
||||||
|
"prettier:check": "prettier --check **/*.{json,md,yml} --ignore-path=.prettierignore",
|
||||||
|
"prettier:write": "prettier --write **/*.{json,md,yml} --ignore-path=.prettierignore",
|
||||||
|
"gas-report": "forge snapshot --gas-report 2>&1 | (tee /dev/tty | awk '/Suite result:/ {found=1; buffer=\"\"; next} found && !/Ran/ {buffer=buffer $0 ORS} /Ran/ {found=0} END {printf \"%s\", buffer}' > .gas-report)",
|
||||||
|
"release": "commit-and-tag-version",
|
||||||
|
"adorno": "pnpm prettier:write && forge fmt && pnpm gas-report"
|
||||||
|
}
|
||||||
|
}
|
||||||
1801
pnpm-lock.yaml
generated
Normal file
1801
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1 +1,2 @@
|
|||||||
|
forge-std/=lib/forge-std/src/
|
||||||
@openzeppelin/contracts=./lib/openzeppelin-contracts/contracts
|
@openzeppelin/contracts=./lib/openzeppelin-contracts/contracts
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {Script, console} from "forge-std/Script.sol";
|
import { Script, console } from "forge-std/Script.sol";
|
||||||
import {RewardsStreamer} from "../src/RewardsStreamer.sol";
|
import { RewardsStreamer } from "../src/RewardsStreamer.sol";
|
||||||
|
|
||||||
contract RewardsStreamerScript is Script {
|
contract RewardsStreamerScript is Script {
|
||||||
RewardsStreamer public rewardsStreamer;
|
RewardsStreamer public rewardsStreamer;
|
||||||
|
|
||||||
function setUp() public {}
|
function setUp() public { }
|
||||||
|
|
||||||
function run() public {
|
function run() public {
|
||||||
vm.startBroadcast();
|
vm.startBroadcast();
|
||||||
|
|||||||
8
slither.config.json
Normal file
8
slither.config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"detectors_to_exclude": "naming-convention,reentrancy-events,solc-version,timestamp",
|
||||||
|
"filter_paths": "(lib|test)",
|
||||||
|
"solc_remaps": [
|
||||||
|
"@openzeppelin/contracts=lib/openzeppelin-contracts/contracts/",
|
||||||
|
"forge-std/=lib/forge-std/src/"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||||
|
|
||||||
contract RewardsStreamer is ReentrancyGuard {
|
contract RewardsStreamer is ReentrancyGuard {
|
||||||
error StakingManager__AmountCannotBeZero();
|
error StakingManager__AmountCannotBeZero();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||||
|
|
||||||
// Rewards Streamer with Multiplier Points
|
// Rewards Streamer with Multiplier Points
|
||||||
contract RewardsStreamerMP is ReentrancyGuard {
|
contract RewardsStreamerMP is ReentrancyGuard {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {Test, console} from "forge-std/Test.sol";
|
import { Test, console } from "forge-std/Test.sol";
|
||||||
import {RewardsStreamer} from "../src/RewardsStreamer.sol";
|
import { RewardsStreamer } from "../src/RewardsStreamer.sol";
|
||||||
import {MockToken} from "./mocks/MockToken.sol";
|
import { MockToken } from "./mocks/MockToken.sol";
|
||||||
|
|
||||||
contract RewardsStreamerTest is Test {
|
contract RewardsStreamerTest is Test {
|
||||||
MockToken rewardToken;
|
MockToken rewardToken;
|
||||||
@@ -122,8 +122,8 @@ contract RewardsStreamerTest is Test {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
checkUser(CheckUserParams({user: alice, rewardBalance: 0, stakedBalance: 10e18, rewardIndex: 0}));
|
checkUser(CheckUserParams({ user: alice, rewardBalance: 0, stakedBalance: 10e18, rewardIndex: 0 }));
|
||||||
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
|
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||||
|
|
||||||
// T4
|
// T4
|
||||||
vm.prank(alice);
|
vm.prank(alice);
|
||||||
@@ -139,8 +139,8 @@ contract RewardsStreamerTest is Test {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
checkUser(CheckUserParams({user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18}));
|
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18 }));
|
||||||
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
|
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||||
|
|
||||||
// T5
|
// T5
|
||||||
vm.prank(charlie);
|
vm.prank(charlie);
|
||||||
@@ -156,9 +156,9 @@ contract RewardsStreamerTest is Test {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
checkUser(CheckUserParams({user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18}));
|
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18 }));
|
||||||
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
|
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||||
checkUser(CheckUserParams({user: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18}));
|
checkUser(CheckUserParams({ user: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18 }));
|
||||||
|
|
||||||
// T6
|
// T6
|
||||||
vm.prank(admin);
|
vm.prank(admin);
|
||||||
@@ -170,14 +170,14 @@ contract RewardsStreamerTest is Test {
|
|||||||
totalStaked: 60e18,
|
totalStaked: 60e18,
|
||||||
stakingBalance: 60e18,
|
stakingBalance: 60e18,
|
||||||
rewardBalance: 1750e18,
|
rewardBalance: 1750e18,
|
||||||
rewardIndex: 41666666666666666666,
|
rewardIndex: 41_666_666_666_666_666_666,
|
||||||
accountedRewards: 1750e18
|
accountedRewards: 1750e18
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
checkUser(CheckUserParams({user: alice, rewardBalance: 250e18, stakedBalance: 0, rewardIndex: 25e18}));
|
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0, rewardIndex: 25e18 }));
|
||||||
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
|
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||||
checkUser(CheckUserParams({user: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18}));
|
checkUser(CheckUserParams({ user: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18 }));
|
||||||
|
|
||||||
//T7
|
//T7
|
||||||
vm.prank(bob);
|
vm.prank(bob);
|
||||||
@@ -188,18 +188,18 @@ contract RewardsStreamerTest is Test {
|
|||||||
totalStaked: 30e18,
|
totalStaked: 30e18,
|
||||||
stakingBalance: 30e18,
|
stakingBalance: 30e18,
|
||||||
rewardBalance: 500e18 + 20, // 500e18 (with rounding error of 20 wei)
|
rewardBalance: 500e18 + 20, // 500e18 (with rounding error of 20 wei)
|
||||||
rewardIndex: 41666666666666666666,
|
rewardIndex: 41_666_666_666_666_666_666,
|
||||||
accountedRewards: 500e18 + 20
|
accountedRewards: 500e18 + 20
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
checkUser(CheckUserParams({user: alice, rewardBalance: 250e18, stakedBalance: 0, rewardIndex: 25e18}));
|
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0, rewardIndex: 25e18 }));
|
||||||
checkUser(
|
checkUser(
|
||||||
CheckUserParams({
|
CheckUserParams({
|
||||||
user: bob,
|
user: bob,
|
||||||
rewardBalance: 1249999999999999999980, // 750e18 + 500e18 (with rounding error)
|
rewardBalance: 1_249_999_999_999_999_999_980, // 750e18 + 500e18 (with rounding error)
|
||||||
stakedBalance: 0,
|
stakedBalance: 0,
|
||||||
rewardIndex: 41666666666666666666
|
rewardIndex: 41_666_666_666_666_666_666
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {Test, console} from "forge-std/Test.sol";
|
import { Test, console } from "forge-std/Test.sol";
|
||||||
import {RewardsStreamerMP} from "../src/RewardsStreamerMP.sol";
|
import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol";
|
||||||
import {MockToken} from "./mocks/MockToken.sol";
|
import { MockToken } from "./mocks/MockToken.sol";
|
||||||
import "forge-std/console.sol";
|
import "forge-std/console.sol";
|
||||||
|
|
||||||
contract RewardsStreamerMPTest is Test {
|
contract RewardsStreamerMPTest is Test {
|
||||||
@@ -222,7 +222,8 @@ contract RewardsStreamerMPTest is Test {
|
|||||||
CheckStreamerParams({
|
CheckStreamerParams({
|
||||||
totalStaked: 30e18,
|
totalStaked: 30e18,
|
||||||
totalMP: 45e18, // 60 - 15 from Alice (10 + 6 months = 5)
|
totalMP: 45e18, // 60 - 15 from Alice (10 + 6 months = 5)
|
||||||
potentialMP: 105e18, // Alice's initial potential MP: 40. 5 already accrued in 6 months. new potentialMP = 140 - 35 = 105
|
potentialMP: 105e18, // Alice's initial potential MP: 40. 5 already accrued in 6 months. new potentialMP
|
||||||
|
// = 140 - 35 = 105
|
||||||
stakingBalance: 30e18,
|
stakingBalance: 30e18,
|
||||||
rewardBalance: 750e18,
|
rewardBalance: 750e18,
|
||||||
rewardIndex: 10e18,
|
rewardIndex: 10e18,
|
||||||
@@ -313,7 +314,7 @@ contract RewardsStreamerMPTest is Test {
|
|||||||
potentialMP: 225e18,
|
potentialMP: 225e18,
|
||||||
stakingBalance: 60e18,
|
stakingBalance: 60e18,
|
||||||
rewardBalance: 1750e18,
|
rewardBalance: 1750e18,
|
||||||
rewardIndex: 17407407407407407407,
|
rewardIndex: 17_407_407_407_407_407_407,
|
||||||
accountedRewards: 1750e18
|
accountedRewards: 1750e18
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -363,9 +364,9 @@ contract RewardsStreamerMPTest is Test {
|
|||||||
potentialMP: 120e18,
|
potentialMP: 120e18,
|
||||||
stakingBalance: 30e18,
|
stakingBalance: 30e18,
|
||||||
// 1750 - (750 + 555.55) = 444.44
|
// 1750 - (750 + 555.55) = 444.44
|
||||||
rewardBalance: 444444444444444444475,
|
rewardBalance: 444_444_444_444_444_444_475,
|
||||||
rewardIndex: 17407407407407407407,
|
rewardIndex: 17_407_407_407_407_407_407,
|
||||||
accountedRewards: 444444444444444444475
|
accountedRewards: 444_444_444_444_444_444_475
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -390,9 +391,9 @@ contract RewardsStreamerMPTest is Test {
|
|||||||
// total weight = 135
|
// total weight = 135
|
||||||
// bobs rewards = 1000 * 75 / 135 = 555.555555555555555555
|
// bobs rewards = 1000 * 75 / 135 = 555.555555555555555555
|
||||||
// bobs total rewards = 555.55 + 750 of the first bucket = 1305.55
|
// bobs total rewards = 555.55 + 750 of the first bucket = 1305.55
|
||||||
rewardBalance: 1305555555555555555525,
|
rewardBalance: 1_305_555_555_555_555_555_525,
|
||||||
stakedBalance: 0e18,
|
stakedBalance: 0e18,
|
||||||
rewardIndex: 17407407407407407407,
|
rewardIndex: 17_407_407_407_407_407_407,
|
||||||
userMP: 0,
|
userMP: 0,
|
||||||
userPotentialMP: 0
|
userPotentialMP: 0
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
|
|
||||||
contract MockToken is ERC20 {
|
contract MockToken is ERC20 {
|
||||||
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
|
constructor(string memory name, string memory symbol) ERC20(name, symbol) { }
|
||||||
|
|
||||||
function mint(address account, uint256 amount) external {
|
function mint(address account, uint256 amount) external {
|
||||||
_mint(account, amount);
|
_mint(account, amount);
|
||||||
|
|||||||
Reference in New Issue
Block a user