Skip to content

Commit

Permalink
improved token distro
Browse files Browse the repository at this point in the history
  • Loading branch information
mejango committed Jun 22, 2023
1 parent beac850 commit 8878c7b
Show file tree
Hide file tree
Showing 25 changed files with 33,416 additions and 29,546 deletions.
108 changes: 63 additions & 45 deletions contracts/DefifaDelegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,6 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
error TRANSFERS_PAUSED();
error UNAUTHORIZED();

//*********************************************************************//
// --------------------- internal stored properties ------------------ //
//*********************************************************************//

/// @notice The first owner of each token ID, stored on first transfer out.
/// _tokenId The ID of the token to get the stored first owner of.
mapping(uint256 => address) internal _firstOwnerOf;

/// @notice The names of each tier.
/// @dev _tierId The ID of the tier to get a name for.
mapping(uint256 => string) internal _tierNameOf;

//*********************************************************************//
// --------------------- public constant properties ------------------ //
//*********************************************************************//
Expand Down Expand Up @@ -103,13 +91,27 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
/// _tierId The ID of the tier being checked.
mapping(uint256 => Checkpoints.History) internal _totalTierCheckpoints;

/// @notice The amount of $DEFIFA and $JBX tokens this game was allocated from paying the network fee, packed into a uint256.
uint256 internal _packedTokenAllocation;

/// @notice The first owner of each token ID, stored on first transfer out.
/// _tokenId The ID of the token to get the stored first owner of.
mapping(uint256 => address) internal _firstOwnerOf;

/// @notice The names of each tier.
/// @dev _tierId The ID of the tier to get a name for.
mapping(uint256 => string) internal _tierNameOf;

//*********************************************************************//
// ---------------- public immutable stored properties --------------- //
//*********************************************************************//

/// @notice The $DEFIFA token that is expected to be issued from paying fees.
IERC20 public immutable override defifaToken;

/// @notice The $JBX token that is expected to be issued from paying fees.
IERC20 public immutable override jbxToken;

//*********************************************************************//
// --------------------- public stored properties -------------------- //
//*********************************************************************//
Expand Down Expand Up @@ -147,9 +149,6 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
/// @notice The amount that has been redeemed from ths game, refunds are not counted.
uint256 public override amountRedeemed;

/// @notice The amount of $DEFIFA tokens this game was allocated from paying the network fee.
uint256 public override defifaTokenAllocation;

/// @notice The amount of tokens that have been redeemed from a tier, refunds are not counted.
/// @custom:param The tier from which tokens have been redeemed.
mapping(uint256 => uint256) public override tokensRedeemedFrom;
Expand Down Expand Up @@ -229,10 +228,6 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
return _owners[_tokenId];
}

//*********************************************************************//
// ------------------------- external views -------------------------- //
//*********************************************************************//

/// @notice The name of the tier with the specified ID.
function tierNameOf(uint256 _tierId) external view override returns (string memory) {
return _tierNameOf[_tierId];
Expand Down Expand Up @@ -310,6 +305,24 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
return TOTAL_REDEMPTION_WEIGHT;
}

/// @notice The amount of $DEFIFA and $JBX tokens this game was allocated from paying the network fee.
/// @return defifaTokenAllocation The $DEFIFA token allocation.
/// @return jbxTokenAllocation The $JBX token allocation.
function tokenAllocations() public view returns (uint256 defifaTokenAllocation, uint256 jbxTokenAllocation) {
// Get a reference to the pakced token allocation.
uint256 _packed = _packedTokenAllocation;

// defifa token allocation in bits 0-127 (128 bits).
uint256 _defifaTokenAllocation = uint256(uint128(_packed));

// jbx token allocation in bits 128-255 (128 bits).
uint256 _jbxTokenAllocation = uint256(uint128(_packed >> 128));

// Return the values.
defifaTokenAllocation = (_defifaTokenAllocation != 0) ? _defifaTokenAllocation : defifaToken.balanceOf(address(this));
jbxTokenAllocation = (_jbxTokenAllocation != 0) ? _jbxTokenAllocation : jbxToken.balanceOf(address(this));
}

/// @notice Part of IJBFundingCycleDataSource, this function gets called when a project's token holders redeem.
/// @param _data The Juicebox standard project redemption data.
/// @return reclaimAmount The amount that should be reclaimed from the treasury.
Expand Down Expand Up @@ -368,29 +381,24 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
);
}

/// @notice The amount of $DEFIFA tokens claimable for a set of token IDs.
/// @notice The amount of $DEFIFA and $JBX tokens claimable for a set of token IDs.
/// @param _tokenIds The IDs of the tokens that justify a $DEFIFA claim.
/// @return amount The amount of $DEFIFA that can be claimed.
function defifaTokensClaimableFor(uint256[] memory _tokenIds) public view returns (uint256 amount) {
// Get a reference to the $DEFIFA token.
IERC20 _defifaToken = defifaToken;

// Get a reference to the current $DEFIFA balance in this contract.
uint256 _currentDefifaTokenBalance = _defifaToken.balanceOf(address(this));
/// @return defifaTokenAmount The amount of $DEFIFA that can be claimed.
/// @return jbxTokenAmount The amount of $JBX that can be claimed.
function tokensClaimableFor(uint256[] memory _tokenIds) public view returns (uint256 defifaTokenAmount, uint256 jbxTokenAmount) {
// Set the amount of total $DEFIFA and $JBX token allocation if it hasn't been set yet.
(uint256 _defifaTokenAllocation, uint256 _jbxTokenAllocation) = tokenAllocations();

// If there's no $DEFIFA in this contract, return 0.
if (_currentDefifaTokenBalance == 0) return 0;

// Set the amount of total $DEFIFA token allocation if it hasn't been set yet.
uint256 _defifaTokenAllocation =
(defifaTokenAllocation != 0) ? defifaTokenAllocation : _currentDefifaTokenBalance;
if (_defifaTokenAllocation == 0 && _jbxTokenAllocation == 0) return (0,0);

// Get a reference to the game's current pot, including any fulfilled commitments.
(uint256 _pot,,) = gamePotReporter.currentGamePotOf(projectId, true);

// If there's no usable pot left, the rest of the $DEFIFA is available.
// If there's no usable pot left, the rest of the $DEFIFA and $JBX is available.
if (_pot - gamePotReporter.fulfilledCommitmentsOf(projectId) == 0) {
amount = _currentDefifaTokenBalance;
defifaTokenAmount = defifaToken.balanceOf(address(this));
jbxTokenAmount = jbxToken.balanceOf(address(this));
} else {
// Keep a reference to the number of tokens being used for claims.
uint256 _numberOfTokens = _tokenIds.length;
Expand All @@ -410,8 +418,9 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
}
}

// The amount of $DEFIFA to send is the same proportion as the amount being redeemed to the total pot before any amount redeemed.
amount = PRBMath.mulDiv(_defifaTokenAllocation, _cumulativePrice, _pot + amountRedeemed);
// The amount of $DEFIFA and $JBX to send is the same proportion as the amount being redeemed to the total pot before any amount redeemed.
defifaTokenAmount = PRBMath.mulDiv(_defifaTokenAllocation, _cumulativePrice, _pot + amountRedeemed);
jbxTokenAmount = PRBMath.mulDiv(_jbxTokenAllocation, _cumulativePrice, _pot + amountRedeemed);
}
}

Expand All @@ -427,9 +436,11 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
//*********************************************************************//

/// @notice The $DEFIFA token that is expected to be issued from paying fees.
constructor(IERC20 _defifaToken) {
/// @notice The $JBX token that is expected to be issued from paying fees.
constructor(IERC20 _defifaToken, IERC20 _jbxToken) {
codeOrigin = address(this);
defifaToken = _defifaToken;
jbxToken = _jbxToken;
}

//*********************************************************************//
Expand Down Expand Up @@ -969,22 +980,29 @@ contract DefifaDelegate is JB721Delegate, Ownable, IDefifaDelegate {
}
}

/// @notice Claim $DEFIFA tokens to an account for a certain redeemed amount.
/// @notice Claim $DEFIFA and $JBX tokens to an account for a certain redeemed amount.
/// @param _beneficiary The beneficiary of the $DEFIFA tokens.
/// @param _tokenIds The IDs of the tokens being redeemed that are justifying a $DEFIFA claim.
/// @return amount The amount of $DEFIFA being claimed.
function _claimDefifaTokensFor(address _beneficiary, uint256[] memory _tokenIds)
internal
returns (uint256 amount)
{
// Set the amount of total $DEFIFA token allocation if it hasn't been set yet.
if (defifaTokenAllocation == 0) defifaTokenAllocation = defifaToken.balanceOf(address(this));

// Get a reference to the amount to send.
amount = defifaTokensClaimableFor(_tokenIds);
if (_packedTokenAllocation == 0) {
uint256 _packed;
// defifa token allocation in bits 0-127 (128 bits).
_packed |= defifaToken.balanceOf(address(this));
// jbx token allocation in bits 128-255 (48 bits).
_packed |= uint256(uint128(jbxToken.balanceOf(address(this)))) << 128;
// Store the packed values.
_packedTokenAllocation = _packed;
}

// Get a reference to the amounts to send.
(uint256 _defifaTokenAmount, uint256 _jbxTokenAmount) = tokensClaimableFor(_tokenIds);

// Send the tokens.
defifaToken.transfer(_beneficiary, amount);
defifaToken.transfer(_beneficiary, _defifaTokenAmount);
jbxToken.transfer(_beneficiary, _jbxTokenAmount);
}

/// @notice User the hook to register the first owner if it's not yet registered.
Expand Down
61 changes: 22 additions & 39 deletions contracts/DefifaDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {JBSplit} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBSpli
import {IJBAllowanceTerminal3_1} from
"@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBAllowanceTerminal3_1.sol";
import {IJBDirectory} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBDirectory.sol";
import {IJBFeeHoldingTerminal} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFeeHoldingTerminal.sol";
import {IJBPayoutRedemptionPaymentTerminal3_1} from
"@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal3_1.sol";
import {IJBSplitAllocator} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSplitAllocator.sol";
Expand Down Expand Up @@ -77,16 +78,6 @@ contract DefifaDeployer is
error SPLITS_DONT_ADD_UP();
error UNEXPECTED_TERMINAL_CURRENCY();

//*********************************************************************//
// ----------------------- internal constants ------------------------ //
//*********************************************************************//

/// @notice The ID of the project that takes fees upon distribution.
uint256 internal constant _PROTOCOL_FEE_PROJECT = 1;

/// @notice Useful for the deploy flow to get memory management right.
uint256 internal constant _DEPLOY_BYTECODE_LENGTH = 13;

//*********************************************************************//
// ----------------------- internal properties ----------------------- //
//*********************************************************************//
Expand Down Expand Up @@ -470,19 +461,6 @@ contract DefifaDeployer is
}
}

/// @notice Move accumulated protocol project tokens from paying fees into the recipient.
/// @dev This contract accumulated JBX as games distribute payouts.
function claimProtocolProjectToken() external override {
// Get the number of protocol project tokens this contract has allocated.
// Send the token from the protocol project to the specified account.
controller.tokenStore().transferFrom(
address(this),
_PROTOCOL_FEE_PROJECT,
protocolFeeProjectTokenAccount,
controller.tokenStore().unclaimedBalanceOf(address(this), _PROTOCOL_FEE_PROJECT)
);
}

/// @notice Fulfill split amounts between all splits for a game.
/// @param _gameId The ID of the game to fulfill splits for.
function fulfillCommitmentsOf(uint256 _gameId) external virtual override {
Expand Down Expand Up @@ -583,6 +561,7 @@ contract DefifaDeployer is
_token,
_splitAmount,
_terminal.decimalsForToken(_token),
true,
string.concat("Payout from Defifa game #", _gameId.toString()),
bytes("")
);
Expand Down Expand Up @@ -634,35 +613,32 @@ contract DefifaDeployer is

if (_leftoverAmount != 0) {
// Add leftover amount back into the game's pot.
_addToBalanceOf(_directory, _gameId, _token, _leftoverAmount, _decimals, "Pot", bytes(""));
_addToBalanceOf(_directory, _gameId, _token, _leftoverAmount, _decimals, true, "Game pot", bytes(""));

// Process any held fees.
IJBPayoutRedemptionPaymentTerminal3_1(address(_terminal)).processFees(_gameId);
}

// Get the game's current metadata.
(, JBFundingCycleMetadata memory _metadata) = controller.currentFundingCycleOf(_gameId);

// Get a reference to the $DEFIFA token.
// Get a reference to the $DEFIFA and $JBX tokens.
IERC20 _defifaToken = IDefifaDelegate(_metadata.dataSource).defifaToken();
IERC20 _jbxToken = IDefifaDelegate(_metadata.dataSource).jbxToken();

// Transfer the amount of $DEFIFA tokens aquired to the delegate.
if (_defifaToken.balanceOf(address(this)) != 0) {
_defifaToken.transferFrom(msg.sender, _metadata.dataSource, _defifaToken.balanceOf(address(this)));
}
// Transfer the amount of $JBX tokens aquired to the delegate.
if (_jbxToken.balanceOf(address(this)) != 0) {
_jbxToken.transferFrom(msg.sender, _metadata.dataSource, _jbxToken.balanceOf(address(this)));
}

// Set the amount of fulfillments for this game.
fulfilledCommitmentsOf[_gameId] = _pot - _leftoverAmount;
}

/// @notice Allow this contract's owner to change the publishing fee.
/// @dev The max fee is %5.
/// @param _percent The percent fee to charge.
function changeFee(uint256 _percent) external onlyOwner {
// Make sure the fee is not greater than 5%.
if (_percent > 10) revert INVALID_FEE_PERCENT();

// Set the fee divisor.
feeDivisor = 100 / _percent;
}

/// @notice Allows this contract to receive 721s.
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
Expand Down Expand Up @@ -841,7 +817,8 @@ contract DefifaDeployer is
allowMinting: false,
allowTerminalMigration: false,
allowControllerMigration: false,
holdFees: false,
// Hold fees. They will be processed once commitments have been fulfilled.
holdFees: true,
preferClaimedTokenOverride: false,
useTotalOverflowForRedemptions: false,
useDataSourceForPay: true,
Expand Down Expand Up @@ -994,6 +971,7 @@ contract DefifaDeployer is
/// @param _token The token being paid in.
/// @param _amount The amount of tokens being paid, as a fixed point number. If the token is ETH, this is ignored and msg.value is used in its place.
/// @param _decimals The number of decimals in the `_amount` fixed point number. If the token is ETH, this is ignored and 18 is used in its place, which corresponds to the amount of decimals expected in msg.value.
/// @param _preferRefundHeldFees A flag indicating if held fees should be refunded based on the amount being added.
/// @param _memo A memo to pass along to the emitted event.
/// @param _metadata Extra data to pass along to the terminal.
function _addToBalanceOf(
Expand All @@ -1002,6 +980,7 @@ contract DefifaDeployer is
address _token,
uint256 _amount,
uint256 _decimals,
bool _preferRefundHeldFees,
string memory _memo,
bytes memory _metadata
) internal virtual {
Expand All @@ -1020,7 +999,11 @@ contract DefifaDeployer is
// If the token is ETH, send it in msg.value.
uint256 _payableValue = _token == JBTokens.ETH ? _amount : 0;

// Add to balance so tokens don't get issued.
_terminal.addToBalanceOf{value: _payableValue}(_projectId, _amount, _token, _memo, _metadata);
// Add to balance so tokens don't get issued.
if (_preferRefundHeldFees && _terminal.supportsInterface(type(IJBFeeHoldingTerminal).interfaceId)) {
IJBFeeHoldingTerminal(address(_terminal)).addToBalanceOf{value: _payableValue}(_projectId, _amount, _token, true, _memo, _metadata);
} else {
_terminal.addToBalanceOf{value: _payableValue}(_projectId, _amount, _token, _memo, _metadata);
}
}
}
4 changes: 2 additions & 2 deletions contracts/forge-test/SVG.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ contract SVGTest is Test {
}

function testWithTierImage() public {
IDefifaDelegate _delegate = DefifaDelegate(Clones.clone(address(new DefifaDelegate(IERC20(address(0))))));
IDefifaDelegate _delegate = DefifaDelegate(Clones.clone(address(new DefifaDelegate(IERC20(address(0)), IERC20(address(0))))));
IJB721TokenUriResolver _resolver = new DefifaTokenUriResolver(_typeface);
IDefifaGamePhaseReporter _gamePhaseReporter = new GamePhaseReporter();
IDefifaGamePotReporter _gamePotReporter = new GamePotReporter();
Expand Down Expand Up @@ -115,7 +115,7 @@ contract SVGTest is Test {
event K(bytes4 k);

function testWithOutTierImage() public {
IDefifaDelegate _delegate = DefifaDelegate(Clones.clone(address(new DefifaDelegate(IERC20(address(0))))));
IDefifaDelegate _delegate = DefifaDelegate(Clones.clone(address(new DefifaDelegate(IERC20(address(0)), IERC20(address(0))))));
DefifaTokenUriResolver _resolver = new DefifaTokenUriResolver(_typeface);
IDefifaGamePhaseReporter _gamePhaseReporter = new GamePhaseReporter();
IDefifaGamePotReporter _gamePotReporter = new GamePotReporter();
Expand Down
Loading

0 comments on commit 8878c7b

Please sign in to comment.