-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add deploy scripts and deployments
- Loading branch information
Showing
19 changed files
with
1,803 additions
and
258 deletions.
There are no files selected for viewing
233 changes: 28 additions & 205 deletions
233
crosschain-rewards/contracts/interfaces/ISonneMerkleDistributor.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,224 +1,47 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity 0.8.19; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {SecureMerkleTrie} from "@eth-optimism/contracts-bedrock/contracts/libraries/trie/SecureMerkleTrie.sol"; | ||
import {RLPReader} from "@eth-optimism/contracts-bedrock/contracts/libraries/rlp/RLPReader.sol"; | ||
import {ISonneMerkleDistributor} from "./interfaces/ISonneMerkleDistributor.sol"; | ||
import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||
|
||
/// @title Sonne Finance Merkle tree-based rewards distributor | ||
/// @notice Contract to distribute rewards on BASE network to Sonne Finance Optimism stakers | ||
contract SonneMerkleDistributor is | ||
ReentrancyGuard, | ||
Ownable, | ||
ISonneMerkleDistributor | ||
{ | ||
using SafeERC20 for IERC20; | ||
|
||
struct Reward { | ||
uint256 balance; // amount of reward tokens held in this reward | ||
bytes32 merkleRoot; // root of claims merkle tree | ||
uint256 withdrawUnlockTime; // time after which owner can withdraw remaining rewards | ||
uint256 ratio; // ratio of rewards to be distributed per one staked token on OP | ||
mapping(bytes32 => bool) leafClaimed; // mapping of leafes that already claimed | ||
} | ||
interface ISonneMerkleDistributor { | ||
event NewMerkle( | ||
address indexed creator, | ||
address indexed rewardToken, | ||
uint256 amount, | ||
bytes32 indexed merkleRoot, | ||
uint256 blockNr, | ||
uint256 withdrawUnlockTime | ||
); | ||
event MerkleFundUpdate( | ||
address indexed funder, | ||
bytes32 indexed merkleRoot, | ||
uint256 blockNr, | ||
uint256 amount, | ||
bool withdrawal | ||
); | ||
event MerkleClaim(address indexed claimer, address indexed rewardToken, uint256 indexed blockNr, uint256 amount); | ||
|
||
IERC20 public immutable rewardToken; | ||
uint256[] public rewards; // a list of all rewards | ||
function rewardToken() external view returns (IERC20); | ||
|
||
mapping(uint256 => Reward) public Rewards; // mapping between blockNumber => Reward | ||
mapping(address => address) public delegatorAddresses; // mapping to allow msg.sender to claim on behalf of a delegators address | ||
function Rewards( | ||
uint256 blockNumber | ||
) external view returns (uint256 balance, bytes32 merkleRoot, uint256 withdrawUnlockTime, uint256 ratio); | ||
|
||
/// @notice Contract constructor to initialize rewardToken | ||
/// @param _rewardToken The reward token to be distributed | ||
constructor(IERC20 _rewardToken) { | ||
require(address(_rewardToken) != address(0), "Token cannot be zero"); | ||
rewardToken = _rewardToken; | ||
} | ||
function delegatorAddresses(address _delegator) external view returns (address originalRecipient); | ||
|
||
/// @notice Sets a delegator address for a given recipient | ||
/// @param _recipient original eligible recipient address | ||
/// @param _delegator The address that sould claim on behalf of the owner | ||
function setDelegator( | ||
address _recipient, | ||
address _delegator | ||
) external onlyOwner { | ||
require( | ||
_recipient != address(0) && _delegator != address(0), | ||
"Invalid address provided" | ||
); | ||
delegatorAddresses[_delegator] = _recipient; | ||
} | ||
function setDelegator(address _recipient, address _delegator) external; | ||
|
||
/// @notice Creates a new Reward struct for a rewards distribution | ||
/// @param amount The amount of reward tokens to deposit | ||
/// @param merkleRoot The merkle root of the distribution tree | ||
/// @param blockNumber The block number for the Reward | ||
/// @param withdrawUnlockTime The timestamp after which withdrawals by owner are allowed | ||
/// @param totalStakedBalance Total staked balance of the merkleRoot (computed off-chain) | ||
function addReward( | ||
uint256 amount, | ||
bytes32 merkleRoot, | ||
uint256 blockNumber, | ||
uint256 withdrawUnlockTime, | ||
uint256 totalStakedBalance | ||
) external onlyOwner { | ||
require(merkleRoot != bytes32(0), "Merkle root cannot be zero"); | ||
|
||
// creates a new reward struct tied to the blocknumber the merkleProof was created at | ||
Reward storage reward = Rewards[blockNumber]; | ||
|
||
require( | ||
reward.merkleRoot == bytes32(0), | ||
"Merkle root was already posted" | ||
); | ||
uint256 balance = rewardToken.balanceOf(msg.sender); | ||
require( | ||
amount > 0 && amount <= balance, | ||
"Invalid amount or insufficient balance" | ||
); | ||
|
||
// transfer rewardToken from the distributor to the contract | ||
rewardToken.safeTransferFrom(msg.sender, address(this), amount); | ||
|
||
// record Reward in stable storage | ||
reward.balance = amount; | ||
reward.merkleRoot = merkleRoot; | ||
reward.withdrawUnlockTime = withdrawUnlockTime; | ||
reward.ratio = (amount * 1e18) / (totalStakedBalance); | ||
rewards.push(blockNumber); | ||
emit NewMerkle( | ||
msg.sender, | ||
address(rewardToken), | ||
amount, | ||
merkleRoot, | ||
blockNumber, | ||
withdrawUnlockTime | ||
); | ||
} | ||
|
||
/// @notice Allows to withdraw available funds to owner after unlock time | ||
/// @param blockNumber The block number for the Reward | ||
/// @param amount The amount to withdraw | ||
function withdrawFunds( | ||
uint256 blockNumber, | ||
uint256 amount | ||
) external onlyOwner { | ||
Reward storage reward = Rewards[blockNumber]; | ||
require( | ||
block.timestamp >= reward.withdrawUnlockTime, | ||
"Rewards may not be withdrawn" | ||
); | ||
require(amount <= reward.balance, "Insufficient balance"); | ||
|
||
// update Rewards record | ||
reward.balance = reward.balance -= amount; | ||
|
||
// transfer rewardToken back to owner | ||
rewardToken.safeTransfer(msg.sender, amount); | ||
emit MerkleFundUpdate( | ||
msg.sender, | ||
reward.merkleRoot, | ||
blockNumber, | ||
amount, | ||
true | ||
); | ||
} | ||
|
||
/// @notice Claims the specified amount for an account if valid | ||
/// @dev Checks proofs and claims tracking before transferring rewardTokens | ||
/// @param blockNumber The block number for the Reward | ||
/// @param proof The merkle proof for the claim | ||
function claim( | ||
uint256 blockNumber, | ||
bytes[] calldata proof | ||
) external nonReentrant { | ||
Reward storage reward = Rewards[blockNumber]; | ||
require(reward.merkleRoot != bytes32(0), "Reward not found"); | ||
) external; | ||
|
||
// Check if the delegatorAddresses includes the account | ||
// The delegatorAddresses mapping allows for an account to delegate its claim ability to another address | ||
// This can be useful in scenarios where the target recipient might not have the ability to directly interact | ||
// with the BASE network contract (e.g. a smart contract with a different address) | ||
address recipient = delegatorAddresses[msg.sender] != address(0) | ||
? delegatorAddresses[msg.sender] | ||
: msg.sender; | ||
function withdrawFunds(uint256 blockNumber, uint256 amount) external; | ||
|
||
// Assuming slotNr is 2 as per your previous function | ||
bytes32 key = keccak256(abi.encode(recipient, uint256(2))); | ||
|
||
//Get the amount of the key from the merkel tree | ||
uint256 amount = _getValueFromMerkleTree(reward.merkleRoot, key, proof); | ||
|
||
// calculate the reward based on the ratio | ||
uint256 rewardAmount = (amount * reward.ratio) / 1e18; // TODO check if there is a loss of precision possible here | ||
|
||
require( | ||
reward.balance >= rewardAmount, | ||
"Claim under-funded by funder." | ||
); | ||
require( | ||
Rewards[blockNumber].leafClaimed[key] == false, | ||
"Already claimed" | ||
); | ||
|
||
// marks the leaf as claimed | ||
reward.leafClaimed[key] = true; | ||
|
||
// Subtract the rewardAmount, not the amount | ||
reward.balance = reward.balance - rewardAmount; | ||
|
||
//Send reward tokens to the recipient | ||
rewardToken.safeTransfer(recipient, rewardAmount); | ||
|
||
emit MerkleClaim( | ||
recipient, | ||
address(rewardToken), | ||
blockNumber, | ||
rewardAmount | ||
); | ||
} | ||
|
||
/// @notice Checks if a claim is valid and claimable | ||
/// @param blockNumber The block number for the Reward | ||
/// @param account The address of the account claiming | ||
/// @param proof The merkle proof for the claim | ||
/// @return A bool indicating if the claim is valid and claimable | ||
function isClaimable( | ||
uint256 blockNumber, | ||
address account, | ||
bytes[] calldata proof | ||
) external view returns (bool) { | ||
bytes32 merkleRoot = Rewards[blockNumber].merkleRoot; | ||
|
||
// At the staking contract, the balances are stored in a mapping (address => uint256) at storage slot 2 | ||
bytes32 leaf = keccak256(abi.encode(account, uint256(2))); | ||
|
||
if (merkleRoot == 0) return false; | ||
return | ||
!Rewards[blockNumber].leafClaimed[leaf] && | ||
_getValueFromMerkleTree(merkleRoot, leaf, proof) > 0; | ||
} | ||
|
||
/// @dev Uses SecureMerkleTrie Library to extract the value from the Merkle proof provided by the user | ||
/// @param merkleRoot the merkle root | ||
/// @param key the key of the leaf => keccak256(address,2) | ||
/// @return result The converted uint256 value as stored in the slot on OP | ||
function _getValueFromMerkleTree( | ||
bytes32 merkleRoot, | ||
bytes32 key, | ||
bytes[] calldata proof | ||
) internal pure returns (uint256 result) { | ||
// Uses SecureMerkleTrie Library to extract the value from the Merkle proof provided by the user | ||
// Reverts if Merkle proof verification fails | ||
bytes memory data = RLPReader.readBytes( | ||
SecureMerkleTrie.get(abi.encodePacked(key), proof, merkleRoot) | ||
); | ||
function claim(uint256 blockNumber, bytes[] calldata proof) external; | ||
|
||
for (uint256 i = 0; i < data.length; i++) { | ||
result = result * 256 + uint8(data[i]); | ||
} | ||
} | ||
function isClaimable(uint256 blockNumber, address account, bytes[] calldata proof) external view returns (bool); | ||
} |
22 changes: 22 additions & 0 deletions
22
crosschain-rewards/deploy/00-sonne-merkle-distributor.base.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { DeployFunction } from 'hardhat-deploy/types'; | ||
|
||
const func: DeployFunction = async ({ | ||
getNamedAccounts, | ||
deployments: { deploy }, | ||
ethers, | ||
network, | ||
}: HardhatRuntimeEnvironment) => { | ||
const { deployer } = await getNamedAccounts(); | ||
|
||
const sonne = '0x22a2488fE295047Ba13BD8cCCdBC8361DBD8cf7c'; | ||
|
||
const distributorDeploy = await deploy('SonneMerkleDistributor', { | ||
from: deployer, | ||
log: true, | ||
contract: 'contracts/SonneMerkleDistributor.sol:SonneMerkleDistributor', | ||
args: [sonne], | ||
}); | ||
}; | ||
|
||
export default func; |
22 changes: 22 additions & 0 deletions
22
crosschain-rewards/deploy/01-usdc-merkle-distributor.base copy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { DeployFunction } from 'hardhat-deploy/types'; | ||
|
||
const func: DeployFunction = async ({ | ||
getNamedAccounts, | ||
deployments: { deploy }, | ||
ethers, | ||
network, | ||
}: HardhatRuntimeEnvironment) => { | ||
const { deployer } = await getNamedAccounts(); | ||
|
||
const usdc = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; | ||
|
||
const distributorDeploy = await deploy('UsdcMerkleDistributor', { | ||
from: deployer, | ||
log: true, | ||
contract: 'contracts/SonneMerkleDistributor.sol:SonneMerkleDistributor', | ||
args: [usdc], | ||
}); | ||
}; | ||
|
||
export default func; |
22 changes: 22 additions & 0 deletions
22
crosschain-rewards/deploy/02-aero-merkle-distributor.base.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { DeployFunction } from 'hardhat-deploy/types'; | ||
|
||
const func: DeployFunction = async ({ | ||
getNamedAccounts, | ||
deployments: { deploy }, | ||
ethers, | ||
network, | ||
}: HardhatRuntimeEnvironment) => { | ||
const { deployer } = await getNamedAccounts(); | ||
|
||
const aero = '0x940181a94A35A4569E4529A3CDfB74e38FD98631'; | ||
|
||
const distributorDeploy = await deploy('AeroMerkleDistributor', { | ||
from: deployer, | ||
log: true, | ||
contract: 'contracts/SonneMerkleDistributor.sol:SonneMerkleDistributor', | ||
args: [aero], | ||
}); | ||
}; | ||
|
||
export default func; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
8453 |
Oops, something went wrong.