Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5.6: Added variable minAmount to nftclaim in order to facilitate exte… #186

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 37 additions & 26 deletions src/FluxToken.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: GPL-3
pragma solidity ^0.8.15;

import "lib/forge-std/src/console2.sol";

import "src/interfaces/IFluxToken.sol";
import "src/interfaces/IVotingEscrow.sol";
import "src/interfaces/IAlchemechNFT.sol";
Expand Down Expand Up @@ -112,35 +110,22 @@ contract FluxToken is ERC20("Flux", "FLUX"), IFluxToken {
}

/// @inheritdoc IFluxToken
function nftClaim(address _nft, uint256 _tokenId) external {
function nftClaim(address _nft, uint256 _tokenId, uint256 _minAmount) external {
// require claim to be within a year of deploy date
require(block.timestamp < deployDate + oneYear, "claim period has passed");

require(!claimed[_nft][_tokenId], "already claimed");

// value of the NFT
uint256 tokenData = 0;

// determine which nft is being claimed
if (_nft == alchemechNFT) {
// require sender to be owner of the NFT
require(IAlchemechNFT(_nft).ownerOf(_tokenId) == msg.sender, "not owner of Alchemech NFT");

tokenData = IAlchemechNFT(_nft).tokenData(_tokenId);
} else if (_nft == patronNFT) {
// require sender to be owner of the NFT
require(IAlEthNFT(_nft).ownerOf(_tokenId) == msg.sender, "not owner of Patron NFT");

tokenData = IAlEthNFT(_nft).tokenData(_tokenId);
} else {
revert("invalid NFT");
}
uint256 tokenData = getNFTValue(_nft, _tokenId);

// mark the token as claimed
claimed[_nft][_tokenId] = true;

uint256 amount = getClaimableFlux(tokenData, _nft);

require(amount >= _minAmount, "Slippage Exceeded");

_mint(msg.sender, amount);
}

Expand Down Expand Up @@ -196,21 +181,47 @@ contract FluxToken is ERC20("Flux", "FLUX"), IFluxToken {
function getClaimableFlux(uint256 _amount, address _nft) public view returns (uint256 claimableFlux) {
uint256 bpt = _calculateBPT(_amount);

uint256 veMul = IVotingEscrow(veALCX).MULTIPLIER();
uint256 veMax = IVotingEscrow(veALCX).MAXTIME();
uint256 fluxPerVe = IVotingEscrow(veALCX).fluxPerVeALCX();
uint256 fluxMul = IVotingEscrow(veALCX).fluxMultiplier();
uint256 slope = (bpt * IVotingEscrow(veALCX).MULTIPLIER()) / IVotingEscrow(veALCX).MAXTIME();

// Calculate as if time is maxtime
uint256 bias = (slope * ((block.timestamp + IVotingEscrow(veALCX).MAXTIME()) - block.timestamp));

// Total amount of flux that would be earned from the amount
uint256 totalFlux = (bias * (IVotingEscrow(veALCX).fluxPerVeALCX() + BPS)) / BPS;

// Amount of flux earned in 1 yr from _amount assuming it was deposited for maxtime
claimableFlux = (((bpt * veMul) / veMax) * veMax * (fluxPerVe + BPS)) / BPS / fluxMul;
// Amount of flux that would be claimable
claimableFlux = totalFlux / IVotingEscrow(veALCX).fluxMultiplier();

// Claimable flux for alchemechNFT is different than patronNFT
if (_nft == alchemechNFT) {
claimableFlux = (claimableFlux * alchemechMultiplier) / BPS;
}
}

function _calculateBPT(uint256 _amount) public view returns (uint256 bptOut) {
// Retrieves tokenData from the specified nft
function getNFTValue(address _nft, uint256 _tokenId) public view returns (uint256) {
// value of the NFT
uint256 tokenData = 0;

// determine which nft is being claimed
if (_nft == alchemechNFT) {
// require sender to be owner of the NFT
require(IAlchemechNFT(_nft).ownerOf(_tokenId) == msg.sender, "not owner of Alchemech NFT");

tokenData = IAlchemechNFT(_nft).tokenData(_tokenId);
} else if (_nft == patronNFT) {
// require sender to be owner of the NFT
require(IAlEthNFT(_nft).ownerOf(_tokenId) == msg.sender, "not owner of Patron NFT");

tokenData = IAlEthNFT(_nft).tokenData(_tokenId);
} else {
revert("invalid NFT");
}

return tokenData;
}

function _calculateBPT(uint256 _amount) internal view returns (uint256 bptOut) {
address distributor = IVotingEscrow(veALCX).distributor();

(bytes32 balancerPoolId, address balancerPool, address balancerVault) = IRewardsDistributor(distributor)
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/IFluxToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ interface IFluxToken is IERC20 {
* @notice Mints tokens to a recipient.
* @param _nft NFT contract address to claim from.
* @param _tokenId NFT tokenId to claim from.
* @param _minAmount Minimum tokens to accept from the claim.
* @dev This will revert after set claim period, if the NFT has already been claimed, or if the caller is not the owner of the NFT
* @dev Amount of FLUX minted is one year of potential FLUX rewards given the NFTs value.
*/
function nftClaim(address _nft, uint256 _tokenId) external;
function nftClaim(address _nft, uint256 _tokenId, uint256 _minAmount) external;

/**
* @dev Burns `amount` tokens from `account`, deducting from the caller's allowance.
Expand Down
20 changes: 12 additions & 8 deletions src/test/FluxToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ contract FluxTokenTest is BaseTest {
uint256 alchemechTotalNoMultiplier = flux.getClaimableFlux(tokenData2, patronNFT);

assertGt(alchemechTotalNoMultiplier, alchemechTotal, "non multiplier calc should be higher");

uint256 expectedAmount = flux.getClaimableFlux(flux.getNFTValue(patronNFT, tokenId), patronNFT);

hevm.prank(ownerOfPatronNFT);
flux.nftClaim(patronNFT, tokenId);
flux.nftClaim(patronNFT, tokenId, expectedAmount * 9_900 / 10_000);

expectedAmount = flux.getClaimableFlux(flux.getNFTValue(alchemechNFT, tokenId), alchemechNFT);

hevm.prank(ownerOfAlchemechNFT);
flux.nftClaim(alchemechNFT, tokenId);
flux.nftClaim(alchemechNFT, tokenId, expectedAmount * 9_900 / 10_000);

assertEq(flux.balanceOf(ownerOfPatronNFT), patronTotal, "owner should have patron flux");
assertEq(flux.balanceOf(ownerOfAlchemechNFT), alchemechTotal, "owner should have alchemech flux");
Expand All @@ -43,28 +47,28 @@ contract FluxTokenTest is BaseTest {

hevm.expectRevert(abi.encodePacked("invalid NFT"));
hevm.prank(ownerOfPatronNFT);
flux.nftClaim(address(0), tokenId);
flux.nftClaim(address(0), tokenId, 0);

hevm.expectRevert(abi.encodePacked("not owner of Patron NFT"));
hevm.prank(ownerOfAlchemechNFT);
flux.nftClaim(patronNFT, tokenId);
flux.nftClaim(patronNFT, tokenId, 0);

hevm.expectRevert(abi.encodePacked("not owner of Alchemech NFT"));
hevm.prank(ownerOfPatronNFT);
flux.nftClaim(alchemechNFT, tokenId);
flux.nftClaim(alchemechNFT, tokenId, 0);

// Attempt to claim twice
hevm.startPrank(ownerOfPatronNFT);
flux.nftClaim(patronNFT, tokenId);
flux.nftClaim(patronNFT, tokenId, 0);

hevm.expectRevert(abi.encodePacked("already claimed"));
flux.nftClaim(patronNFT, tokenId);
flux.nftClaim(patronNFT, tokenId, 0);
hevm.stopPrank();

// Attempt to claim after claim period (1 year)
hevm.warp(block.timestamp + 366 days);
hevm.expectRevert(abi.encodePacked("claim period has passed"));
flux.nftClaim(patronNFT, tokenId);
flux.nftClaim(patronNFT, tokenId, 0);
}

function testFluxAccrual() external {
Expand Down
Loading