import foundry template (#1)

This commit is contained in:
Andrea Franz
2024-09-25 09:39:14 +02:00
committed by GitHub
parent 197262ba79
commit 4ef75621a3
27 changed files with 2367 additions and 116 deletions

11
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View 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`?

View 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 }}

View 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
View 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
View File

@@ -1,14 +1,19 @@
# Compiler files
cache/
out/
# directories
cache
node_modules
out
# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
# files
*.env
*.log
.DS_Store
.pnp.*
lcov.info
yarn.lock
# Docs
docs/
# broadcasts
!broadcast
broadcast/*
broadcast/*/31337/
# Dotenv file
.env
.certora_internal

18
.prettierignore Normal file
View 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
View File

@@ -0,0 +1,7 @@
bracketSpacing: true
printWidth: 120
proseWrap: "always"
singleQuote: false
tabWidth: 2
trailingComma: "all"
useTabs: false

13
.solhint.json Normal file
View 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
View File

16
LICENSE.md Normal file
View 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
View 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
View File

@@ -12,19 +12,18 @@ It represents the accumulated rewards per staked token since the beginning of th
Here's how it works:
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.
The increase is calculated as:
`rewardIndex += (newRewards * SCALE_FACTOR) / totalStaked`
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 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 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. This "resets" their reward accumulation for the next period.
2. Whenever new rewards are added to the contract (detected in updateRewardIndex()), the rewardIndex increases. The
increase is calculated as: `rewardIndex += (newRewards * SCALE_FACTOR) / totalStaked` 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
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
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.
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
single global variable (rewardIndex).
Instead of updating each user's rewards every time new rewards are added, we only need to update a single global
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.
@@ -38,111 +37,111 @@ User-specific calculations are done only when a user interacts with the contract
**Initial setup:**
* rewardIndex: 0
* accountedRewards: 0
* Rewards in contract: 0
- rewardIndex: 0
- accountedRewards: 0
- Rewards in contract: 0
**T1: Alice stakes 10 tokens**
* Alice's userRewardIndex: 0
* Alice's staked tokens: 10
* totalStaked: 10 tokens
- Alice's userRewardIndex: 0
- Alice's staked tokens: 10
- totalStaked: 10 tokens
**T2: Bob stakes 30 tokens**
* Alice's userRewardIndex: 0
* Bob's userRewardIndex: 0
* Alice's staked tokens: 10
* Bob's staked tokens: 30
* totalStaked: 40 tokens
- Alice's userRewardIndex: 0
- Bob's userRewardIndex: 0
- Alice's staked tokens: 10
- Bob's staked tokens: 30
- totalStaked: 40 tokens
**T3: 1000 Rewards arrive**
New rewardIndex calculation:
* newRewards = 1000
* rewardIndex increase = 1000 / 40 = 25
* rewardIndex = 0 + 25 = 25
* accountedRewards: 1000
* Rewards in contract: 1000
- newRewards = 1000
- rewardIndex increase = 1000 / 40 = 25
- rewardIndex = 0 + 25 = 25
- accountedRewards: 1000
- Rewards in contract: 1000
Potential Rewards for Alice and Bob:
For Alice:
* Staked amount: 10 tokens
* Potential Rewards: 10 * (25 - 0) = 250
- Staked amount: 10 tokens
- Potential Rewards: 10 \* (25 - 0) = 250
For Bob:
* Staked amount: 30 tokens
* Potential Rewards: 30 * (25 - 0) = 750
- Staked amount: 30 tokens
- Potential Rewards: 30 \* (25 - 0) = 750
**T4: Alice withdraws her stake and Rewards**
Alice's withdrawal:
* tokens returned: 10
* Rewards: 250
- tokens returned: 10
- Rewards: 250
Update state:
* totalStaked = 40 - 10 = 30 tokens
* Rewards in contract = 1000 - 250 = 750
- totalStaked = 40 - 10 = 30 tokens
- Rewards in contract = 1000 - 250 = 750
**T5: Charlie stakes 30 tokens**
* Charlie's userRewardIndex: 25
* totalStaked = 30 + 30 = 60 tokens
- Charlie's userRewardIndex: 25
- totalStaked = 30 + 30 = 60 tokens
**T6: Another 1000 Rewards arrive**
New rewardIndex calculation:
* newRewards = 1000
* rewardIndex increase = 1000 / 60 = 16.67
* new rewardIndex = 25 + 16.67 = 41.67
* accountedRewards: 1000 + 1000 = 2000
* Rewards in contract = 750 + 1000 = 1750
- newRewards = 1000
- rewardIndex increase = 1000 / 60 = 16.67
- new rewardIndex = 25 + 16.67 = 41.67
- accountedRewards: 1000 + 1000 = 2000
- Rewards in contract = 750 + 1000 = 1750
Rewards for Bob and Charlie:
For Bob:
* Staked amount: 30 tokens
* Potential Rewards: 30 * (41.67 - 0) = 1250.1 // rounding error
* In bucket 1: 30 * (25 - 0) = 750
* In bucket 2: 30 * (16.67 - 0) = 500.1
* Total of b1 + b2: 750 + 500.1 = 1250.1
* Which is equal to
* 30 * ( (25 - 0) + (41.67 - 25) )
- Staked amount: 30 tokens
- Potential Rewards: 30 \* (41.67 - 0) = 1250.1 // rounding error
- In bucket 1: 30 \* (25 - 0) = 750
- In bucket 2: 30 \* (16.67 - 0) = 500.1
- Total of b1 + b2: 750 + 500.1 = 1250.1
- Which is equal to
- 30 \* ( (25 - 0) + (41.67 - 25) )
For Charlie:
* Staked amount: 30 tokens
* Potential Rewards: 30 * (41.67 - 25) = 500.1 // rounding error
- Staked amount: 30 tokens
- Potential Rewards: 30 \* (41.67 - 25) = 500.1 // rounding error
If Bob and Charlie were to withdraw now:
Bob's withdrawal:
* tokens returned: 30
* Rewards: 1250.1
* Rewards in contract after Bob's withdrawal: 1750 - 1250.1 = 499.9
- tokens returned: 30
- Rewards: 1250.1
- Rewards in contract after Bob's withdrawal: 1750 - 1250.1 = 499.9
Charlie's withdrawal:
* tokens returned: 30
* Rewards: 499.9
* Rewards in contract after Charlie's withdrawal: 499.9 - 499.9 = 0
- tokens returned: 30
- Rewards: 499.9
- Rewards in contract after Charlie's withdrawal: 499.9 - 499.9 = 0
**T7: Final state:**
* Alice received: 10 tokens and 250 Rewards
* Bob received: 30 tokens and 1250.1 Rewards
* Charlie received: 30 tokens and 499.9 Rewards
* Total Rewards distributed: 2000 Rewards
* Rewards remaining in contract: 0
- Alice received: 10 tokens and 250 Rewards
- Bob received: 30 tokens and 1250.1 Rewards
- Charlie received: 30 tokens and 499.9 Rewards
- Total Rewards distributed: 2000 Rewards
- Rewards remaining in contract: 0
## Rewards Streamer with Multiplier Points

14
certora/certora.conf Normal file
View 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"
]
}

View File

@@ -0,0 +1,3 @@
rule checkIdOutputIsAlwaysEqualToInput {
assert true;
}

28
codecov.yml Normal file
View 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

View File

@@ -1,6 +1,55 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
# Full reference https://github.com/foundry-rs/foundry/tree/master/config
# 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}"

View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,2 @@
forge-std/=lib/forge-std/src/
@openzeppelin/contracts=./lib/openzeppelin-contracts/contracts

View File

@@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Script, console} from "forge-std/Script.sol";
import {RewardsStreamer} from "../src/RewardsStreamer.sol";
import { Script, console } from "forge-std/Script.sol";
import { RewardsStreamer } from "../src/RewardsStreamer.sol";
contract RewardsStreamerScript is Script {
RewardsStreamer public rewardsStreamer;
function setUp() public {}
function setUp() public { }
function run() public {
vm.startBroadcast();

8
slither.config.json Normal file
View 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/"
]
}

View File

@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract RewardsStreamer is ReentrancyGuard {
error StakingManager__AmountCannotBeZero();

View File

@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
// Rewards Streamer with Multiplier Points
contract RewardsStreamerMP is ReentrancyGuard {

View File

@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {RewardsStreamer} from "../src/RewardsStreamer.sol";
import {MockToken} from "./mocks/MockToken.sol";
import { Test, console } from "forge-std/Test.sol";
import { RewardsStreamer } from "../src/RewardsStreamer.sol";
import { MockToken } from "./mocks/MockToken.sol";
contract RewardsStreamerTest is Test {
MockToken rewardToken;
@@ -122,8 +122,8 @@ contract RewardsStreamerTest is Test {
})
);
checkUser(CheckUserParams({user: alice, rewardBalance: 0, stakedBalance: 10e18, rewardIndex: 0}));
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
checkUser(CheckUserParams({ user: alice, rewardBalance: 0, stakedBalance: 10e18, rewardIndex: 0 }));
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
// T4
vm.prank(alice);
@@ -139,8 +139,8 @@ contract RewardsStreamerTest is Test {
})
);
checkUser(CheckUserParams({user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18}));
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18 }));
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
// T5
vm.prank(charlie);
@@ -156,9 +156,9 @@ contract RewardsStreamerTest is Test {
})
);
checkUser(CheckUserParams({user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18}));
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
checkUser(CheckUserParams({user: charlie, rewardBalance: 0, stakedBalance: 30e18, 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: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18 }));
// T6
vm.prank(admin);
@@ -170,14 +170,14 @@ contract RewardsStreamerTest is Test {
totalStaked: 60e18,
stakingBalance: 60e18,
rewardBalance: 1750e18,
rewardIndex: 41666666666666666666,
rewardIndex: 41_666_666_666_666_666_666,
accountedRewards: 1750e18
})
);
checkUser(CheckUserParams({user: alice, rewardBalance: 250e18, stakedBalance: 0, rewardIndex: 25e18}));
checkUser(CheckUserParams({user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0}));
checkUser(CheckUserParams({user: charlie, rewardBalance: 0, stakedBalance: 30e18, 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: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18 }));
//T7
vm.prank(bob);
@@ -188,18 +188,18 @@ contract RewardsStreamerTest is Test {
totalStaked: 30e18,
stakingBalance: 30e18,
rewardBalance: 500e18 + 20, // 500e18 (with rounding error of 20 wei)
rewardIndex: 41666666666666666666,
rewardIndex: 41_666_666_666_666_666_666,
accountedRewards: 500e18 + 20
})
);
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: 1249999999999999999980, // 750e18 + 500e18 (with rounding error)
rewardBalance: 1_249_999_999_999_999_999_980, // 750e18 + 500e18 (with rounding error)
stakedBalance: 0,
rewardIndex: 41666666666666666666
rewardIndex: 41_666_666_666_666_666_666
})
);
}

View File

@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {RewardsStreamerMP} from "../src/RewardsStreamerMP.sol";
import {MockToken} from "./mocks/MockToken.sol";
import { Test, console } from "forge-std/Test.sol";
import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol";
import { MockToken } from "./mocks/MockToken.sol";
import "forge-std/console.sol";
contract RewardsStreamerMPTest is Test {
@@ -222,7 +222,8 @@ contract RewardsStreamerMPTest is Test {
CheckStreamerParams({
totalStaked: 30e18,
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,
rewardBalance: 750e18,
rewardIndex: 10e18,
@@ -313,7 +314,7 @@ contract RewardsStreamerMPTest is Test {
potentialMP: 225e18,
stakingBalance: 60e18,
rewardBalance: 1750e18,
rewardIndex: 17407407407407407407,
rewardIndex: 17_407_407_407_407_407_407,
accountedRewards: 1750e18
})
);
@@ -363,9 +364,9 @@ contract RewardsStreamerMPTest is Test {
potentialMP: 120e18,
stakingBalance: 30e18,
// 1750 - (750 + 555.55) = 444.44
rewardBalance: 444444444444444444475,
rewardIndex: 17407407407407407407,
accountedRewards: 444444444444444444475
rewardBalance: 444_444_444_444_444_444_475,
rewardIndex: 17_407_407_407_407_407_407,
accountedRewards: 444_444_444_444_444_444_475
})
);
@@ -390,9 +391,9 @@ contract RewardsStreamerMPTest is Test {
// total weight = 135
// bobs rewards = 1000 * 75 / 135 = 555.555555555555555555
// 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,
rewardIndex: 17407407407407407407,
rewardIndex: 17_407_407_407_407_407_407,
userMP: 0,
userPotentialMP: 0
})

View File

@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MIT
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 {
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 {
_mint(account, amount);