Skip to content

Commit

Permalink
feat: integrate rewards (#71)
Browse files Browse the repository at this point in the history
* add rewards

* added payments

* fixed imports

* create payments logic

* add root creating functions

* add submitRoot call

* fixed build

* switched to keccak

* added proof generation

* added json parsing

* separate import for SM

* added token to deployment data

* cleanup/restrucutring

* added lib

* filled in functions

* added test file

* added basic tests

* add unit tests

* added more tests

* fixed broken test

* add padding

* changes

* builds

* tests compile

* config errors resolved

* erc20mock error

* payment submission tests passing

* tests compile but without modifiers in RewardsCoordinator

* changes

* deleted commented out code

* added token approval

* fix: library use internal function

* cleanup

* fixed build

* fixed script

* script

* script

* fix: script

* fixed path:

---------

Co-authored-by: steven nevins <[email protected]>
Co-authored-by: steven <[email protected]>
  • Loading branch information
3 people authored Oct 21, 2024
1 parent bbd6ea7 commit 99a4ca7
Show file tree
Hide file tree
Showing 12 changed files with 686 additions and 8 deletions.
6 changes: 6 additions & 0 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ docs/

# Dotenv file
.env


# DS_store files
.DS_Store
lib/.DS_Store

66 changes: 66 additions & 0 deletions contracts/mocks/MockStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@eigenlayer/contracts/interfaces/IStrategy.sol";

contract MockStrategy is IStrategy {
IERC20 public override underlyingToken;
uint256 public override totalShares;
mapping(address => uint256) public userShares;
uint256 public constant EXCHANGE_RATE = 1e18; // 1:1 exchange rate for simplicity

constructor(IERC20 _underlyingToken) {
underlyingToken = _underlyingToken;
emit StrategyTokenSet(_underlyingToken, 18); // Assuming 18 decimals for simplicity
}

function deposit(IERC20 token, uint256 amount) external override returns (uint256) {
require(token == underlyingToken, "Invalid token");
uint256 newShares = amount;
totalShares += newShares;
userShares[msg.sender] += newShares;
emit ExchangeRateEmitted(EXCHANGE_RATE);
return newShares;
}

function withdraw(address recipient, IERC20 token, uint256 amountShares) external override {
require(token == underlyingToken, "Invalid token");
require(userShares[msg.sender] >= amountShares, "Insufficient shares");
userShares[msg.sender] -= amountShares;
totalShares -= amountShares;
underlyingToken.transfer(recipient, amountShares);
}

function sharesToUnderlying(uint256 amountShares) external pure override returns (uint256) {
return amountShares;
}

function underlyingToShares(uint256 amountUnderlying) external pure override returns (uint256) {
return amountUnderlying;
}

function userUnderlying(address user) external view override returns (uint256) {
return userShares[user];
}

function shares(address user) external view override returns (uint256) {
return userShares[user];
}

function sharesToUnderlyingView(uint256 amountShares) external pure override returns (uint256) {
return amountShares;
}

function underlyingToSharesView(uint256 amountUnderlying) external pure override returns (uint256) {
return amountUnderlying;
}

function userUnderlyingView(address user) external view override returns (uint256) {
return userShares[user];
}

function explanation() external pure override returns (string memory) {
return "Mock Strategy for testing purposes";
}
}
11 changes: 11 additions & 0 deletions contracts/payments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"leaves": [
"0x29036a1d92861ffd464a1e285030fad3978a36f953ae33c160e606d2ac746c42",
"0x29036a1d92861ffd464a1e285030fad3978a36f953ae33c160e606d2ac746c42",
"0x29036a1d92861ffd464a1e285030fad3978a36f953ae33c160e606d2ac746c42",
"0x29036a1d92861ffd464a1e285030fad3978a36f953ae33c160e606d2ac746c42"
],
"tokenLeaves": [
"0xf5d87050cb923194fe63c7ed2c45cbc913fa6ecf322f3631149c36d9460b3ad6"
]
}
19 changes: 17 additions & 2 deletions contracts/script/HelloWorldDeployer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import {console2} from "forge-std/Test.sol";
import {HelloWorldDeploymentLib} from "./utils/HelloWorldDeploymentLib.sol";
import {CoreDeploymentLib} from "./utils/CoreDeploymentLib.sol";
import {UpgradeableProxyLib} from "./utils/UpgradeableProxyLib.sol";
import {StrategyBase} from "@eigenlayer/contracts/strategies/StrategyBase.sol";
import {ERC20Mock} from "../test/ERC20Mock.sol";
import {TransparentUpgradeableProxy} from
"@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {StrategyFactory} from "@eigenlayer/contracts/strategies/StrategyFactory.sol";
import {StrategyManager} from "@eigenlayer/contracts/core/StrategyManager.sol";


import {
Quorum,
Expand All @@ -19,18 +26,23 @@ contract HelloWorldDeployer is Script {

address private deployer;
address proxyAdmin;
StrategyBase helloWorldStrategy;
StrategyBase helloWorldStrategyImpl;
CoreDeploymentLib.DeploymentData coreDeployment;
HelloWorldDeploymentLib.DeploymentData helloWorldDeployment;
Quorum internal quorum;

ERC20Mock token;
function setUp() public virtual {
deployer = vm.rememberKey(vm.envUint("PRIVATE_KEY"));
vm.label(deployer, "Deployer");

coreDeployment = CoreDeploymentLib.readDeploymentJson("deployments/core/", block.chainid);

token = new ERC20Mock();
IStrategy helloWorldStrategy = StrategyFactory(coreDeployment.strategyFactory).deployNewStrategy(token);

quorum.strategies.push(
StrategyParams({strategy: IStrategy(address(420)), multiplier: 10_000})
StrategyParams({strategy: helloWorldStrategy, multiplier: 10_000})
);
}

Expand All @@ -41,6 +53,8 @@ contract HelloWorldDeployer is Script {
helloWorldDeployment =
HelloWorldDeploymentLib.deployContracts(proxyAdmin, coreDeployment, quorum);

helloWorldDeployment.strategy = address(helloWorldStrategy);
helloWorldDeployment.token = address(token);
vm.stopBroadcast();

verifyDeployment();
Expand All @@ -55,6 +69,7 @@ contract HelloWorldDeployer is Script {
helloWorldDeployment.helloWorldServiceManager != address(0),
"HelloWorldServiceManager address cannot be zero"
);
require(helloWorldDeployment.strategy != address(0), "Strategy address cannot be zero");
require(proxyAdmin != address(0), "ProxyAdmin address cannot be zero");
require(
coreDeployment.delegationManager != address(0),
Expand Down
101 changes: 101 additions & 0 deletions contracts/script/SetupPayments.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {Script} from "forge-std/Script.sol";
import {HelloWorldDeploymentLib} from "./utils/HelloWorldDeploymentLib.sol";
import {CoreDeploymentLib} from "./utils/CoreDeploymentLib.sol";
import {SetupPaymentsLib} from "./utils/SetupPaymentsLib.sol";
import {IRewardsCoordinator} from "@eigenlayer/contracts/interfaces/IRewardsCoordinator.sol";

contract SetupPayments is Script {
struct PaymentInfo {
address[] earners;
bytes32[] earnerTokenRoots;
address recipient;
uint256 numPayments;
uint256 amountPerPayment;
uint32 duration;
uint32 startTimestamp;
uint32 endTimestamp;
uint256 indexToProve;
}

address private deployer;
CoreDeploymentLib.DeploymentData coreDeployment;
HelloWorldDeploymentLib.DeploymentData helloWorldDeployment;
string private filePath;

uint256 constant NUM_TOKEN_EARNINGS = 1;
uint256 constant DURATION = 1 weeks;

function setUp() public {
deployer = vm.rememberKey(vm.envUint("PRIVATE_KEY"));
vm.label(deployer, "Deployer");

coreDeployment = CoreDeploymentLib.readDeploymentJson("deployments/core/", block.chainid);
helloWorldDeployment = HelloWorldDeploymentLib.readDeploymentJson("deployments/hello-world/", block.chainid);

// TODO: Get the filePath from config
}

function run() external {
vm.startBroadcast(deployer);
IRewardsCoordinator(coreDeployment.rewardsCoordinator).setRewardsUpdater(deployer);
PaymentInfo memory info = abi.decode(vm.parseJson(vm.readFile(filePath)), (PaymentInfo));

createAVSRewardsSubmissions(info.numPayments, info.amountPerPayment, info.duration, info.startTimestamp);
submitPaymentRoot(info.earners, info.endTimestamp, uint32(info.numPayments), uint32(info.amountPerPayment));

IRewardsCoordinator.EarnerTreeMerkleLeaf memory earnerLeaf = IRewardsCoordinator.EarnerTreeMerkleLeaf({
earner: info.earners[info.indexToProve],
earnerTokenRoot: info.earnerTokenRoots[info.indexToProve]
});
processClaim(SetupPaymentsLib.getFilePath(), info.indexToProve, info.recipient, earnerLeaf);

vm.stopBroadcast();
}


function createAVSRewardsSubmissions(uint256 numPayments, uint256 amountPerPayment, uint32 duration, uint32 startTimestamp) public {
SetupPaymentsLib.createAVSRewardsSubmissions(
IRewardsCoordinator(coreDeployment.rewardsCoordinator),
helloWorldDeployment.strategy,
numPayments,
amountPerPayment,
duration,
startTimestamp
);
}

function processClaim(string memory filePath, uint256 indexToProve, address recipient, IRewardsCoordinator.EarnerTreeMerkleLeaf memory earnerLeaf) public {
SetupPaymentsLib.processClaim(
IRewardsCoordinator(coreDeployment.rewardsCoordinator),
filePath,
indexToProve,
recipient,
earnerLeaf,
NUM_TOKEN_EARNINGS,
helloWorldDeployment.strategy
);
}

function submitPaymentRoot(address[] memory earners, uint32 endTimestamp, uint32 numPayments, uint32 amountPerPayment) public {
bytes32[] memory tokenLeaves = SetupPaymentsLib.createTokenLeaves(
IRewardsCoordinator(coreDeployment.rewardsCoordinator),
NUM_TOKEN_EARNINGS,
amountPerPayment,
helloWorldDeployment.strategy
);
IRewardsCoordinator.EarnerTreeMerkleLeaf[] memory earnerLeaves = SetupPaymentsLib.createEarnerLeaves(earners, tokenLeaves);

SetupPaymentsLib.submitRoot(
IRewardsCoordinator(coreDeployment.rewardsCoordinator),
tokenLeaves,
earnerLeaves,
helloWorldDeployment.strategy,
endTimestamp,
numPayments,
NUM_TOKEN_EARNINGS
);
}
}
6 changes: 3 additions & 3 deletions contracts/script/utils/CoreDeploymentLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ library CoreDeploymentLib {
);

/// TODO: Get actual values
uint32 CALCULATION_INTERVAL_SECONDS = 10 days;
uint32 MAX_REWARDS_DURATION = 10;
uint32 CALCULATION_INTERVAL_SECONDS = 1 days;
uint32 MAX_REWARDS_DURATION = 1 days;
uint32 MAX_RETROACTIVE_LENGTH = 1;
uint32 MAX_FUTURE_LENGTH = 1;
uint32 GENESIS_REWARDS_TIMESTAMP = 100 days;
uint32 GENESIS_REWARDS_TIMESTAMP = 10 days;
address rewardsCoordinatorImpl = address(
new RewardsCoordinator(
IDelegationManager(result.delegationManager),
Expand Down
10 changes: 8 additions & 2 deletions contracts/script/utils/HelloWorldDeploymentLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ library HelloWorldDeploymentLib {
struct DeploymentData {
address helloWorldServiceManager;
address stakeRegistry;
address strategy;
address token;
}

function deployContracts(
Expand All @@ -43,7 +45,7 @@ library HelloWorldDeploymentLib {
address(new ECDSAStakeRegistry(IDelegationManager(core.delegationManager)));
address helloWorldServiceManagerImpl = address(
new HelloWorldServiceManager(
core.avsDirectory, result.stakeRegistry, core.delegationManager
core.avsDirectory, result.stakeRegistry, core.rewardsCoordinator, core.delegationManager
)
);
// Upgrade contracts
Expand Down Expand Up @@ -136,7 +138,11 @@ library HelloWorldDeploymentLib {
data.stakeRegistry.toHexString(),
'","stakeRegistryImpl":"',
data.stakeRegistry.getImplementation().toHexString(),
'"}'
'","strategy":"',
data.strategy.toHexString(),
'","token":"',
data.token.toHexString(),
'"}'
);
}
}
Loading

0 comments on commit 99a4ca7

Please sign in to comment.