Skip to content

Commit

Permalink
Add HardHat compatibility and improve on use within Foundry
Browse files Browse the repository at this point in the history
This commit makes it possible to use staker as a dependency in a HardHat Solidity project. It also makes using staker easier and more natural in the context of a Foundry project as well. Changes include:

* Adding a package.json with definitions for use in npm based environments like HardHat
* Change all imports in the project to relative imports, to avoid import issues in both Foundry and HardHat based projects
* Move the script directory into the src directory; in Foundry, this means the automatic remappings for staker will now switch to `staker/=lib/staker/src/` instead of just `staker/=/lib/staker/`, so imports do not have to include the `src/` extension
* Add a `remappings.txt` file that defines a `staker-test/=test/` remapping, making it easier for downstream Foundry projects to import test files they may want to extend from or leverage.
* Update the README to make it clearer how to use this repository as a dependency in various kinds of projects

---------

Co-authored-by: Ben DiFrancesco <ben@scopelift.co>
  • Loading branch information
jferas and apbendi authored Mar 4, 2025
1 parent 11d0a57 commit c1550aa
Show file tree
Hide file tree
Showing 44 changed files with 250 additions and 137 deletions.
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,96 @@ stateDiagram-v2
PF --> Staker: Returns earning power to staking system
```

## Usage

The Staker repository is designed to be used as a library. Once added to your project, it can be used to assemble, configure, and deploy an instance of Staker appropriate for your project.

### Installation

#### Foundry

Add Staker as a dependency:

```bash
forge install withtally/staker
```

#### Hardhat

Staker is not currently distributed as a package on npm, but it can still be added to a HardHat project by referencing the github repository directly.

Add the following lines to to your `package.json` in the `dependencies` key:

```json
"dependencies": {
"@openzeppelin/contracts": "^5.0.2",
"staker": "github:withtally/staker#v1.0.1"
}
```

Note that because the `staker` package is pinned to a version branch on GitHub. To upgrade to a new version of Staker in the future, you cannot expect an upgrade to a new version simply by running `npm upgrade`. Instead, you will have to update the tag referenced in the `pacakage.json` and re-run `npm install`.

### Import and Assemble a Staker

To create a concrete implementation, import Staker and the desired extensions. Create a new contract that inherits from Staker and the extensions of choice, and implement the constructor, along with any method overrides in accordance with your desired set of features and customizations.

For example, here is a concrete implementation of the Staker that:

* Uses an [ERC20Votes](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#ERC20Votes) style governance token as its staking token (via the `StakerDelegateSurrogateVotes` extension)
* Uses a staking token that includes permit functionality, and provides convenience staking methods that use this functionality (via the `StakerPermitAndStake` extension)
* Allows users to take actions via EIP712 signatures (via the `StakerOnBehalf` extension)
* Allows a maximum claim fee of 1 (18 decimal) reward token, but configures the Staker with claim fees turned off to start

```solidity
pragma solidity 0.8.28;
import {Staker} staker/Staker.sol;
import {StakerDelegateSurrogateVotes} from "staker/extensions/StakerDelegateSurrogateVotes.sol";
import {StakerPermitAndStake} from "staker/extensions/StakerPermitAndStake.sol";
import {StakerOnBehalf} from "staker/extensions/StakerOnBehalf.sol";
contract MyStaker is
Staker,
StakerDelegateSurrogateVotes,
StakerPermitAndStake
StakerOnBehalf
{
constructor(
IERC20 _rewardsToken,
IERC20Staking _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin
)
Staker(_rewardsToken, _stakeToken, _earningPowerCalculator, _maxBumpTip, _admin)
StakerPermitAndStake(_stakeToken)
StakerDelegateSurrogateVotes(_stakeToken)
EIP712("MyStaker", "1")
{
// Override the maximum reward token fee for claiming rewards
MAX_CLAIM_FEE = 1e18;
// At deployment, there should be no reward claiming fee
_setClaimFeeParameters(ClaimFeeParameters({feeAmount: 0, feeCollector: address(0)}));
}
}
```

### Testing

Foundry users can also import test-related contracts into their own tests if desired:

```solidity
pragma solidity 0.8.28;
import {StakerTestBase} from "staker-test/StakerTestBase.sol";
contract MyStakerTest is StakerTestBase {
// ... Your test implementations
}
```

Hardhat users cannot, by definition, import test contracts into their own tests. Any testing infrastructure for JavaScript tests must be written by the downstream consumer of the Staker package.

## Development

These contracts were built and tested with care by the team at [ScopeLift](https://scopelift.co).
Expand Down
1 change: 0 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
]
optimizer = true
optimizer_runs = 10_000_000
remappings = ["openzeppelin/=lib/openzeppelin-contracts/contracts"]
solc_version = "0.8.28"
verbosity = 3

Expand Down
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "staker",
"version": "1.0.1",
"description": "Staker is a flexible, configurable staking contract. Staker makes it easy to distribute onchain staking rewards for any ERC20 token, including DAO governance tokens. This file is included for compatibility with npm based development frameworks such as Hardhat.",
"author": "Contributors to https://github.com/ScopeLift/staker",
"files": [
"src/*"
],
"repository": {
"type": "git",
"url": "git+https://github.com/ScopeLift/staker.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/ScopeLift/staker/issues"
},
"homepage": "https://github.com/ScopeLift/staker#readme",
"dependencies": {
"@openzeppelin/contracts": "^5.0.2"
}
}
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
staker-test/=test/
2 changes: 1 addition & 1 deletion src/DelegationSurrogate.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title DelegationSurrogate
/// @author [ScopeLift](https://scopelift.co)
Expand Down
4 changes: 2 additions & 2 deletions src/DelegationSurrogateVotes.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {DelegationSurrogate} from "src/DelegationSurrogate.sol";
import {IERC20Delegates} from "src/interfaces/IERC20Delegates.sol";
import {DelegationSurrogate} from "./DelegationSurrogate.sol";
import {IERC20Delegates} from "./interfaces/IERC20Delegates.sol";

/// @title DelegationSurrogateVotes
/// @author [ScopeLift](https://scopelift.co)
Expand Down
14 changes: 7 additions & 7 deletions src/Staker.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {DelegationSurrogate} from "src/DelegationSurrogate.sol";
import {INotifiableRewardReceiver} from "src/interfaces/INotifiableRewardReceiver.sol";
import {IEarningPowerCalculator} from "src/interfaces/IEarningPowerCalculator.sol";
import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {Multicall} from "openzeppelin/utils/Multicall.sol";
import {SafeCast} from "openzeppelin/utils/math/SafeCast.sol";
import {DelegationSurrogate} from "./DelegationSurrogate.sol";
import {INotifiableRewardReceiver} from "./interfaces/INotifiableRewardReceiver.sol";
import {IEarningPowerCalculator} from "./interfaces/IEarningPowerCalculator.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

/// @title Staker
/// @author [ScopeLift](https://scopelift.co)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.23;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IEarningPowerCalculator} from "src/interfaces/IEarningPowerCalculator.sol";
import {IEarningPowerCalculator} from "../interfaces/IEarningPowerCalculator.sol";

/// @title BinaryEligibilityOracleEarningPowerCalculator
/// @author [ScopeLift](https://scopelift.co)
Expand Down
2 changes: 1 addition & 1 deletion src/calculators/IdentityEarningPowerCalculator.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IEarningPowerCalculator} from "src/interfaces/IEarningPowerCalculator.sol";
import {IEarningPowerCalculator} from "../interfaces/IEarningPowerCalculator.sol";

/// @title IdentityEarningPowerCalculator
/// @author [ScopeLift](https://scopelift.co)
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/StakerCapDeposits.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {Staker} from "src/Staker.sol";
import {Staker} from "../Staker.sol";

/// @title StakerCapDeposits
/// @author [ScopeLift](https://scopelift.co)
Expand Down
8 changes: 4 additions & 4 deletions src/extensions/StakerDelegateSurrogateVotes.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {DelegationSurrogate} from "src/DelegationSurrogate.sol";
import {DelegationSurrogateVotes} from "src/DelegationSurrogateVotes.sol";
import {Staker} from "src/Staker.sol";
import {IERC20Delegates} from "src/interfaces/IERC20Delegates.sol";
import {DelegationSurrogate} from "../DelegationSurrogate.sol";
import {DelegationSurrogateVotes} from "../DelegationSurrogateVotes.sol";
import {Staker} from "../Staker.sol";
import {IERC20Delegates} from "../interfaces/IERC20Delegates.sol";

/// @title StakerDelegateSurrogateVotes
/// @author [ScopeLift](https://scopelift.co)
Expand Down
8 changes: 4 additions & 4 deletions src/extensions/StakerOnBehalf.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {Staker} from "src/Staker.sol";
import {SignatureChecker} from "openzeppelin/utils/cryptography/SignatureChecker.sol";
import {EIP712} from "openzeppelin/utils/cryptography/EIP712.sol";
import {Nonces} from "openzeppelin/utils/Nonces.sol";
import {Staker} from "../Staker.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

/// @title StakerOnBehalf
/// @author [ScopeLift](https://scopelift.co)
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/StakerPermitAndStake.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {Staker} from "src/Staker.sol";
import {IERC20Permit} from "openzeppelin/token/ERC20/extensions/IERC20Permit.sol";
import {Staker} from "../Staker.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";

/// @title StakerPermitAndStake
/// @author [ScopeLift](https://scopelift.co)
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/IERC20Delegates.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {IDelegates} from "src/interfaces/IDelegates.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDelegates} from "./IDelegates.sol";

/// @notice A subset of the ERC20Votes-style governance token to which a staking token should
/// conform. Methods related to standard ERC20 functionality and to delegation are included.
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/IERC20Staking.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IERC20Permit} from "openzeppelin/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20Delegates} from "src/interfaces/IERC20Delegates.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20Delegates} from "./IERC20Delegates.sol";

/// @notice The interface of an ERC20 that supports a governor staker and all of the created
/// extensions.
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/INotifiableRewardReceiver.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title INotifiableRewardReceiver
/// @author [ScopeLift](https://scopelift.co)
Expand Down
8 changes: 4 additions & 4 deletions src/notifiers/MintRewardNotifier.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {INotifiableRewardReceiver} from "src/interfaces/INotifiableRewardReceiver.sol";
import {IMintable} from "src/interfaces/IMintable.sol";
import {RewardTokenNotifierBase} from "src/notifiers/RewardTokenNotifierBase.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";
import {INotifiableRewardReceiver} from "../interfaces/INotifiableRewardReceiver.sol";
import {IMintable} from "../interfaces/IMintable.sol";
import {RewardTokenNotifierBase} from "./RewardTokenNotifierBase.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/// @title MintRewardNotifier
/// @author [ScopeLift](https://scopelift.co)
Expand Down
4 changes: 2 additions & 2 deletions src/notifiers/RewardTokenNotifierBase.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {INotifiableRewardReceiver, IERC20} from "src/interfaces/INotifiableRewardReceiver.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";
import {INotifiableRewardReceiver, IERC20} from "../interfaces/INotifiableRewardReceiver.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/// @title RewardTokenNotifierBase
/// @author [ScopeLift](https://scopelift.co)
Expand Down
8 changes: 4 additions & 4 deletions src/notifiers/TransferFromRewardNotifier.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {INotifiableRewardReceiver, IERC20} from "src/interfaces/INotifiableRewardReceiver.sol";
import {RewardTokenNotifierBase} from "src/notifiers/RewardTokenNotifierBase.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {INotifiableRewardReceiver, IERC20} from "../interfaces/INotifiableRewardReceiver.sol";
import {RewardTokenNotifierBase} from "../notifiers/RewardTokenNotifierBase.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @title TransferFromRewardNotifier
/// @author [ScopeLift](https://scopelift.co)
Expand Down
8 changes: 4 additions & 4 deletions src/notifiers/TransferRewardNotifier.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {INotifiableRewardReceiver, IERC20} from "src/interfaces/INotifiableRewardReceiver.sol";
import {RewardTokenNotifierBase} from "src/notifiers/RewardTokenNotifierBase.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {INotifiableRewardReceiver, IERC20} from "../interfaces/INotifiableRewardReceiver.sol";
import {RewardTokenNotifierBase} from "../notifiers/RewardTokenNotifierBase.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @title TransferRewardNotifier
/// @author [ScopeLift](https://scopelift.co)
Expand Down
11 changes: 5 additions & 6 deletions script/Deploy.s.sol → src/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@

pragma solidity ^0.8.23;

import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Script} from "forge-std/Script.sol";

import {DeployInput} from "script/DeployInput.sol";
import {StakerHarness} from "test/harnesses/StakerHarness.sol";
import {IERC20Staking} from "src/interfaces/IERC20Staking.sol";
import {INotifiableRewardReceiver} from "src/interfaces/INotifiableRewardReceiver.sol";
import {IEarningPowerCalculator} from "src/interfaces/IEarningPowerCalculator.sol";
import {DeployInput} from "./DeployInput.sol";
import {StakerHarness} from "../../test/harnesses/StakerHarness.sol";
import {IERC20Staking} from "../interfaces/IERC20Staking.sol";
import {IEarningPowerCalculator} from "../interfaces/IEarningPowerCalculator.sol";

contract Deploy is Script, DeployInput {
uint256 deployerPrivateKey;
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion test/BinaryEligibilityOracleEarningPowerCalculator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Test, console2} from "forge-std/Test.sol";
import {
BinaryEligibilityOracleEarningPowerCalculator as EarningPowerCalculator,
Ownable
} from "src/calculators/BinaryEligibilityOracleEarningPowerCalculator.sol";
} from "../src/calculators/BinaryEligibilityOracleEarningPowerCalculator.sol";

contract EarningPowerCalculatorTest is Test {
address public owner;
Expand Down
4 changes: 2 additions & 2 deletions test/DelegationSurrogate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
pragma solidity ^0.8.23;

import {Test, console2} from "forge-std/Test.sol";
import {DelegationSurrogateVotes} from "src/DelegationSurrogateVotes.sol";
import {ERC20VotesMock} from "test/mocks/MockERC20Votes.sol";
import {DelegationSurrogateVotes} from "../src/DelegationSurrogateVotes.sol";
import {ERC20VotesMock} from "./mocks/MockERC20Votes.sol";

contract DelegationSurrogateVotesTest is Test {
ERC20VotesMock govToken;
Expand Down
2 changes: 1 addition & 1 deletion test/IdentityEarningPowerCalculator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.23;

import {Test} from "forge-std/Test.sol";
import {IdentityEarningPowerCalculator} from "src/calculators/IdentityEarningPowerCalculator.sol";
import {IdentityEarningPowerCalculator} from "../src/calculators/IdentityEarningPowerCalculator.sol";

contract IdentityEarningPowerCalculatorTest is Test {
IdentityEarningPowerCalculator calculator;
Expand Down
16 changes: 8 additions & 8 deletions test/MintRewardNotifier.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
pragma solidity ^0.8.23;

import {Vm, Test, stdStorage, StdStorage, console2, console, stdError} from "forge-std/Test.sol";
import {MintRewardNotifier, IMintable} from "src/notifiers/MintRewardNotifier.sol";
import {RewardTokenNotifierBase} from "src/notifiers/RewardTokenNotifierBase.sol";
import {INotifiableRewardReceiver} from "src/interfaces/INotifiableRewardReceiver.sol";
import {MockNotifiableRewardReceiver} from "test/mocks/MockNotifiableRewardReceiver.sol";
import {ERC20VotesMock} from "test/mocks/MockERC20Votes.sol";
import {FakeMinter} from "test/fakes/FakeMinter.sol";
import {TestHelpers} from "test/helpers/TestHelpers.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";
import {MintRewardNotifier, IMintable} from "../src/notifiers/MintRewardNotifier.sol";
import {RewardTokenNotifierBase} from "../src/notifiers/RewardTokenNotifierBase.sol";
import {INotifiableRewardReceiver} from "../src/interfaces/INotifiableRewardReceiver.sol";
import {MockNotifiableRewardReceiver} from "./mocks/MockNotifiableRewardReceiver.sol";
import {ERC20VotesMock} from "./mocks/MockERC20Votes.sol";
import {FakeMinter} from "./fakes/FakeMinter.sol";
import {TestHelpers} from "./helpers/TestHelpers.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract MintRewardNotifierTest is Test, TestHelpers {
ERC20VotesMock token;
Expand Down
Loading

0 comments on commit c1550aa

Please sign in to comment.