@ballkidz/defifa 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADMINISTRATION.md +3 -3
- package/ARCHITECTURE.md +3 -2
- package/AUDIT_INSTRUCTIONS.md +5 -5
- package/CHANGE_LOG.md +62 -5
- package/CRYPTO_ECON.md +506 -271
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +438 -241
- package/RISKS.md +13 -1
- package/SKILLS.md +5 -3
- package/USER_JOURNEYS.md +4 -3
- package/package.json +6 -6
- package/src/DefifaDeployer.sol +128 -130
- package/src/DefifaGovernor.sol +304 -83
- package/src/DefifaHook.sol +184 -171
- package/src/enums/DefifaScorecardState.sol +1 -0
- package/src/interfaces/IDefifaGovernor.sol +42 -2
- package/src/libraries/DefifaHookLib.sol +69 -62
- package/src/structs/DefifaAttestations.sol +3 -3
- package/src/structs/DefifaLaunchProjectData.sol +1 -0
- package/src/structs/DefifaScorecard.sol +2 -0
- package/test/BWAFunctionComparison.t.sol +1320 -0
- package/test/DefifaAdversarialQuorum.t.sol +52 -37
- package/test/DefifaAuditLowGuards.t.sol +9 -5
- package/test/DefifaFeeAccounting.t.sol +2 -1
- package/test/DefifaGovernanceHardening.t.sol +1315 -0
- package/test/DefifaGovernor.t.sol +8 -4
- package/test/DefifaHookRegressions.t.sol +2 -1
- package/test/DefifaMintCostInvariant.t.sol +2 -1
- package/test/DefifaNoContest.t.sol +3 -2
- package/test/DefifaSecurity.t.sol +55 -47
- package/test/DefifaUSDC.t.sol +3 -2
- package/test/Fork.t.sol +37 -32
- package/test/TestAuditGaps.sol +6 -4
- package/test/TestQALastMile.t.sol +6 -3
- package/test/audit/{CodexAttestationDoubleCount.t.sol → AttestationDoubleCount.t.sol} +3 -2
- package/test/audit/FixPendingReserveDilution.t.sol +366 -0
- package/test/audit/PendingReserveDilution.t.sol +298 -0
- package/test/audit/PendingReserveQuorumGrief.t.sol +355 -0
- package/test/audit/PendingReserveSnapshotBypass.t.sol +279 -0
- package/test/regression/AttestationDelegateBeneficiary.t.sol +2 -1
- package/test/regression/FulfillmentBlocksRatification.t.sol +2 -1
- package/test/regression/GracePeriodBypass.t.sol +2 -1
- package/test/SVG.t.sol +0 -164
- package/test/deployScript.t.sol +0 -144
package/src/DefifaHook.sol
CHANGED
|
@@ -161,10 +161,10 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
161
161
|
/// @return The first owner of the token.
|
|
162
162
|
function firstOwnerOf(uint256 tokenId) external view override returns (address) {
|
|
163
163
|
// Get a reference to the first owner.
|
|
164
|
-
address
|
|
164
|
+
address storedFirstOwner = _firstOwnerOf[tokenId];
|
|
165
165
|
|
|
166
166
|
// If the stored first owner is set, return it.
|
|
167
|
-
if (
|
|
167
|
+
if (storedFirstOwner != address(0)) return storedFirstOwner;
|
|
168
168
|
|
|
169
169
|
// Otherwise, the first owner must be the current owner.
|
|
170
170
|
return _owners[tokenId];
|
|
@@ -275,23 +275,22 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
275
275
|
if (metadataExists) decodedTokenIds = abi.decode(metadata, (uint256[]));
|
|
276
276
|
|
|
277
277
|
// Get the current game phase.
|
|
278
|
-
DefifaGamePhase
|
|
278
|
+
DefifaGamePhase gamePhase = gamePhaseReporter.currentGamePhaseOf(context.projectId);
|
|
279
279
|
|
|
280
280
|
// Calculate the amount paid to mint the tokens that are being burned.
|
|
281
|
-
uint256
|
|
281
|
+
uint256 cumulativeMintPrice = DefifaHookLib.computeCumulativeMintPrice({
|
|
282
282
|
tokenIds: decodedTokenIds, hookStore: store, hook: address(this)
|
|
283
283
|
});
|
|
284
284
|
|
|
285
285
|
// Use this contract as the only cash out hook.
|
|
286
286
|
hookSpecifications = new JBCashOutHookSpecification[](1);
|
|
287
|
-
hookSpecifications[0] =
|
|
288
|
-
hook: this, noop: false, amount: 0, metadata: abi.encode(
|
|
289
|
-
});
|
|
287
|
+
hookSpecifications[0] =
|
|
288
|
+
JBCashOutHookSpecification({hook: this, noop: false, amount: 0, metadata: abi.encode(cumulativeMintPrice)});
|
|
290
289
|
|
|
291
290
|
// Compute the cash out count based on the game phase.
|
|
292
291
|
cashOutCount = DefifaHookLib.computeCashOutCount({
|
|
293
|
-
gamePhase:
|
|
294
|
-
cumulativeMintPrice:
|
|
292
|
+
gamePhase: gamePhase,
|
|
293
|
+
cumulativeMintPrice: cumulativeMintPrice,
|
|
295
294
|
surplusValue: context.surplus.value,
|
|
296
295
|
totalAmountRedeemed: amountRedeemed,
|
|
297
296
|
cumulativeCashOutWeight: cashOutWeightOf(decodedTokenIds)
|
|
@@ -516,15 +515,15 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
516
515
|
_store.recordAddTiers(_tiers);
|
|
517
516
|
|
|
518
517
|
// Keep a reference to the number of tier names.
|
|
519
|
-
uint256
|
|
518
|
+
uint256 numberOfTierNames = _tierNames.length;
|
|
520
519
|
|
|
521
520
|
// Set the name for each tier.
|
|
522
|
-
for (uint256
|
|
521
|
+
for (uint256 i; i < numberOfTierNames;) {
|
|
523
522
|
// Set the tier name.
|
|
524
|
-
_tierNameOf[
|
|
523
|
+
_tierNameOf[i + 1] = _tierNames[i];
|
|
525
524
|
|
|
526
525
|
unchecked {
|
|
527
|
-
++
|
|
526
|
+
++i;
|
|
528
527
|
}
|
|
529
528
|
}
|
|
530
529
|
|
|
@@ -543,57 +542,54 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
543
542
|
)) revert DefifaHook_ReservedTokenMintingPaused();
|
|
544
543
|
|
|
545
544
|
// Keep a reference to the reserved token beneficiary.
|
|
546
|
-
address
|
|
545
|
+
address reservedTokenBeneficiary = store.reserveBeneficiaryOf({hook: address(this), tierId: tierId});
|
|
547
546
|
|
|
548
547
|
// Get a reference to the old delegate.
|
|
549
|
-
address
|
|
548
|
+
address oldDelegate = _tierDelegation[reservedTokenBeneficiary][tierId];
|
|
550
549
|
|
|
551
550
|
// Set the delegate as the beneficiary if the beneficiary hasn't already set a delegate.
|
|
552
|
-
if (
|
|
551
|
+
if (oldDelegate == address(0)) {
|
|
553
552
|
_delegateTier({
|
|
554
|
-
|
|
555
|
-
|
|
553
|
+
account: reservedTokenBeneficiary,
|
|
554
|
+
delegatee: defaultAttestationDelegate != address(0)
|
|
556
555
|
? defaultAttestationDelegate
|
|
557
|
-
:
|
|
558
|
-
|
|
556
|
+
: reservedTokenBeneficiary,
|
|
557
|
+
tierId: tierId
|
|
559
558
|
});
|
|
560
559
|
}
|
|
561
560
|
|
|
562
561
|
// Record the minted reserves for the tier.
|
|
563
|
-
uint256[] memory
|
|
562
|
+
uint256[] memory tokenIds = store.recordMintReservesFor({tierId: tierId, count: count});
|
|
564
563
|
|
|
565
564
|
// Keep a reference to the token ID being iterated on.
|
|
566
|
-
uint256
|
|
565
|
+
uint256 tokenId;
|
|
567
566
|
|
|
568
567
|
// Fetch the tier details (needed for votingUnits below).
|
|
569
|
-
JB721Tier memory
|
|
568
|
+
JB721Tier memory tier = store.tierOf({hook: address(this), id: tierId, includeResolvedUri: false});
|
|
570
569
|
|
|
571
570
|
// Increment _totalMintCost so reserved recipients can claim their share of fee tokens ($DEFIFA/$NANA).
|
|
572
571
|
// Note: reserved mints dilute existing fee token claimants because they increase the total mint cost
|
|
573
572
|
// denominator without contributing new funds to the fee token balances. This is the intended design —
|
|
574
573
|
// reserved recipients receive a proportional claim on fee tokens as if they had paid to mint.
|
|
575
|
-
_totalMintCost +=
|
|
574
|
+
_totalMintCost += tier.price * count;
|
|
576
575
|
|
|
577
|
-
for (uint256
|
|
576
|
+
for (uint256 i; i < count;) {
|
|
578
577
|
// Set the token ID.
|
|
579
|
-
|
|
578
|
+
tokenId = tokenIds[i];
|
|
580
579
|
|
|
581
580
|
// Mint the token.
|
|
582
|
-
_mint({to:
|
|
581
|
+
_mint({to: reservedTokenBeneficiary, tokenId: tokenId});
|
|
583
582
|
|
|
584
|
-
emit MintReservedToken(
|
|
583
|
+
emit MintReservedToken(tokenId, tierId, reservedTokenBeneficiary, msg.sender);
|
|
585
584
|
|
|
586
585
|
unchecked {
|
|
587
|
-
++
|
|
586
|
+
++i;
|
|
588
587
|
}
|
|
589
588
|
}
|
|
590
589
|
|
|
591
590
|
// Transfer the attestation units to the delegate.
|
|
592
591
|
_transferTierAttestationUnits({
|
|
593
|
-
|
|
594
|
-
_to: _reservedTokenBeneficiary,
|
|
595
|
-
_tierId: tierId,
|
|
596
|
-
_amount: _tier.votingUnits * _tokenIds.length
|
|
592
|
+
from: address(0), to: reservedTokenBeneficiary, tierId: tierId, amount: tier.votingUnits * tokenIds.length
|
|
597
593
|
});
|
|
598
594
|
}
|
|
599
595
|
|
|
@@ -630,52 +626,56 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
630
626
|
}
|
|
631
627
|
|
|
632
628
|
// Decode the CashOut metadata.
|
|
633
|
-
(uint256[] memory
|
|
629
|
+
(uint256[] memory decodedTokenIds) = abi.decode(metadata, (uint256[]));
|
|
634
630
|
|
|
635
631
|
// Get a reference to the number of token IDs being checked.
|
|
636
|
-
uint256
|
|
632
|
+
uint256 numberOfTokenIds = decodedTokenIds.length;
|
|
637
633
|
|
|
638
634
|
// Keep a reference to the token ID being iterated on.
|
|
639
|
-
uint256
|
|
635
|
+
uint256 tokenId;
|
|
640
636
|
|
|
641
637
|
// Keep track of whether the cashOut is happening during the complete phase.
|
|
642
|
-
bool
|
|
638
|
+
bool isComplete = gamePhaseReporter.currentGamePhaseOf(PROJECT_ID) == DefifaGamePhase.COMPLETE;
|
|
643
639
|
|
|
644
640
|
// Iterate through all tokens, burning them if the owner is correct.
|
|
645
|
-
for (uint256
|
|
641
|
+
for (uint256 i; i < numberOfTokenIds; i++) {
|
|
646
642
|
// Set the token's ID.
|
|
647
|
-
|
|
643
|
+
tokenId = decodedTokenIds[i];
|
|
648
644
|
|
|
649
645
|
// Make sure the token's owner is correct.
|
|
650
|
-
address
|
|
651
|
-
if (
|
|
652
|
-
revert DefifaHook_Unauthorized(
|
|
646
|
+
address tokenOwner = _ownerOf(tokenId);
|
|
647
|
+
if (tokenOwner != context.holder) {
|
|
648
|
+
revert DefifaHook_Unauthorized(tokenId, tokenOwner, context.holder);
|
|
653
649
|
}
|
|
654
650
|
|
|
655
651
|
// Burn the token.
|
|
656
|
-
_burn(
|
|
652
|
+
_burn(tokenId);
|
|
657
653
|
|
|
658
|
-
if (
|
|
654
|
+
if (isComplete) {
|
|
659
655
|
unchecked {
|
|
660
|
-
++tokensRedeemedFrom[store.tierIdOfToken(
|
|
656
|
+
++tokensRedeemedFrom[store.tierIdOfToken(tokenId)];
|
|
661
657
|
}
|
|
662
658
|
}
|
|
663
659
|
}
|
|
664
660
|
|
|
665
661
|
// Call the hook.
|
|
666
|
-
_didBurn(
|
|
662
|
+
_didBurn(decodedTokenIds);
|
|
667
663
|
|
|
668
664
|
// Decode the metadata passed by the hook.
|
|
669
|
-
(uint256
|
|
665
|
+
(uint256 cumulativeMintPrice) = abi.decode(context.hookMetadata, (uint256));
|
|
670
666
|
|
|
671
667
|
// Increment the amount redeemed if this is the complete phase.
|
|
672
|
-
bool
|
|
673
|
-
if (
|
|
668
|
+
bool beneficiaryReceivedTokens;
|
|
669
|
+
if (isComplete) {
|
|
674
670
|
amountRedeemed += context.reclaimedAmount.value;
|
|
675
671
|
|
|
676
672
|
// Claim the $DEFIFA and $NANA tokens for the user.
|
|
677
|
-
|
|
678
|
-
|
|
673
|
+
// Include pending reserve mint cost in the denominator so that unminted reserves
|
|
674
|
+
// are accounted for, preventing paid holders from claiming a disproportionate share.
|
|
675
|
+
beneficiaryReceivedTokens = _claimTokensFor({
|
|
676
|
+
beneficiary: context.holder,
|
|
677
|
+
shareToBeneficiary: cumulativeMintPrice,
|
|
678
|
+
outOfTotal: _totalMintCost + _pendingReserveMintCost()
|
|
679
679
|
});
|
|
680
680
|
}
|
|
681
681
|
|
|
@@ -683,27 +683,27 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
683
683
|
// Tokens in 0-weight tiers (losing teams) cannot burn to reclaim fees if no fee tokens were
|
|
684
684
|
// distributed. This is correct behavior — 0-weight means the tier has no claim on the pot. Burning would
|
|
685
685
|
// return 0 value regardless.
|
|
686
|
-
if (context.reclaimedAmount.value == 0 && !
|
|
686
|
+
if (context.reclaimedAmount.value == 0 && !beneficiaryReceivedTokens) revert DefifaHook_NothingToClaim();
|
|
687
687
|
|
|
688
688
|
// Decrement the paid mint cost by the cumulative mint price of the tokens being burned.
|
|
689
|
-
_totalMintCost -=
|
|
689
|
+
_totalMintCost -= cumulativeMintPrice;
|
|
690
690
|
}
|
|
691
691
|
|
|
692
692
|
/// @notice Mint reserved tokens within the tier for the provided value.
|
|
693
693
|
/// @param mintReservesForTiersData Contains information about how many reserved tokens to mint for each tier.
|
|
694
694
|
function mintReservesFor(JB721TiersMintReservesConfig[] calldata mintReservesForTiersData) external override {
|
|
695
695
|
// Keep a reference to the number of tiers there are to mint reserves for.
|
|
696
|
-
uint256
|
|
696
|
+
uint256 numberOfTiers = mintReservesForTiersData.length;
|
|
697
697
|
|
|
698
|
-
for (uint256
|
|
698
|
+
for (uint256 i; i < numberOfTiers;) {
|
|
699
699
|
// Get a reference to the data being iterated on.
|
|
700
|
-
JB721TiersMintReservesConfig memory
|
|
700
|
+
JB721TiersMintReservesConfig memory data = mintReservesForTiersData[i];
|
|
701
701
|
|
|
702
702
|
// Mint for the tier.
|
|
703
|
-
mintReservesFor(
|
|
703
|
+
mintReservesFor(data.tierId, data.count);
|
|
704
704
|
|
|
705
705
|
unchecked {
|
|
706
|
-
++
|
|
706
|
+
++i;
|
|
707
707
|
}
|
|
708
708
|
}
|
|
709
709
|
}
|
|
@@ -713,10 +713,10 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
713
713
|
/// @param tierWeights The tier weights to set.
|
|
714
714
|
function setTierCashOutWeightsTo(DefifaTierCashOutWeight[] memory tierWeights) external override onlyOwner {
|
|
715
715
|
// Get a reference to the game phase.
|
|
716
|
-
DefifaGamePhase
|
|
716
|
+
DefifaGamePhase gamePhase = gamePhaseReporter.currentGamePhaseOf(PROJECT_ID);
|
|
717
717
|
|
|
718
718
|
// Make sure the game has ended.
|
|
719
|
-
if (
|
|
719
|
+
if (gamePhase != DefifaGamePhase.SCORING) {
|
|
720
720
|
revert DefifaHook_GameIsntScoringYet();
|
|
721
721
|
}
|
|
722
722
|
|
|
@@ -745,7 +745,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
745
745
|
revert DefifaHook_DelegateChangesUnavailableInThisPhase();
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
-
_delegateTier({
|
|
748
|
+
_delegateTier({account: msg.sender, delegatee: delegatee, tierId: tierId});
|
|
749
749
|
}
|
|
750
750
|
|
|
751
751
|
/// @notice Delegate attestations.
|
|
@@ -757,22 +757,22 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
757
757
|
}
|
|
758
758
|
|
|
759
759
|
// Keep a reference to the number of tier delegates.
|
|
760
|
-
uint256
|
|
760
|
+
uint256 numberOfTierDelegates = delegations.length;
|
|
761
761
|
|
|
762
762
|
// Keep a reference to the data being iterated on.
|
|
763
|
-
DefifaDelegation memory
|
|
763
|
+
DefifaDelegation memory data;
|
|
764
764
|
|
|
765
|
-
for (uint256
|
|
765
|
+
for (uint256 i; i < numberOfTierDelegates;) {
|
|
766
766
|
// Reference the data being iterated on.
|
|
767
|
-
|
|
767
|
+
data = delegations[i];
|
|
768
768
|
|
|
769
769
|
// Make sure a delegate is specified.
|
|
770
|
-
if (
|
|
770
|
+
if (data.delegatee == address(0)) revert DefifaHook_DelegateAddressZero();
|
|
771
771
|
|
|
772
|
-
_delegateTier({
|
|
772
|
+
_delegateTier({account: msg.sender, delegatee: data.delegatee, tierId: data.tierId});
|
|
773
773
|
|
|
774
774
|
unchecked {
|
|
775
|
-
++
|
|
775
|
+
++i;
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
778
|
}
|
|
@@ -781,13 +781,35 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
781
781
|
// ------------------------ internal functions ----------------------- //
|
|
782
782
|
//*********************************************************************//
|
|
783
783
|
|
|
784
|
+
/// @notice Computes the total mint cost of all pending (unminted) reserve NFTs across all tiers.
|
|
785
|
+
/// @dev Used to include pending reserves in the fee token claim denominator so that paid holders
|
|
786
|
+
/// cannot claim a disproportionate share before reserves are minted.
|
|
787
|
+
/// @return cost The total mint cost of pending reserves.
|
|
788
|
+
function _pendingReserveMintCost() internal view returns (uint256 cost) {
|
|
789
|
+
IJB721TiersHookStore _store = store;
|
|
790
|
+
address hook = address(this);
|
|
791
|
+
uint256 numberOfTiers = _store.maxTierIdOf(hook);
|
|
792
|
+
|
|
793
|
+
for (uint256 i; i < numberOfTiers;) {
|
|
794
|
+
uint256 tierId = i + 1;
|
|
795
|
+
uint256 pendingReserves = _store.numberOfPendingReservesFor({hook: hook, tierId: tierId});
|
|
796
|
+
if (pendingReserves != 0) {
|
|
797
|
+
JB721Tier memory tier = _store.tierOf({hook: hook, id: tierId, includeResolvedUri: false});
|
|
798
|
+
cost += pendingReserves * tier.price;
|
|
799
|
+
}
|
|
800
|
+
unchecked {
|
|
801
|
+
++i;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
784
806
|
/// @notice Claims the defifa and base protocol tokens for a beneficiary.
|
|
785
|
-
/// @param
|
|
807
|
+
/// @param beneficiary The address to claim tokens for.
|
|
786
808
|
/// @param shareToBeneficiary The share relative to the `outOfTotal` to send the user.
|
|
787
809
|
/// @param outOfTotal The total share that the `shareToBeneficiary` is relative to.
|
|
788
810
|
/// @return beneficiaryReceivedTokens A flag indicating if the beneficiary received any tokens.
|
|
789
811
|
function _claimTokensFor(
|
|
790
|
-
address
|
|
812
|
+
address beneficiary,
|
|
791
813
|
uint256 shareToBeneficiary,
|
|
792
814
|
uint256 outOfTotal
|
|
793
815
|
)
|
|
@@ -795,7 +817,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
795
817
|
returns (bool beneficiaryReceivedTokens)
|
|
796
818
|
{
|
|
797
819
|
return DefifaHookLib.claimTokensFor({
|
|
798
|
-
beneficiary:
|
|
820
|
+
beneficiary: beneficiary,
|
|
799
821
|
shareToBeneficiary: shareToBeneficiary,
|
|
800
822
|
outOfTotal: outOfTotal,
|
|
801
823
|
defifaToken: DEFIFA_TOKEN,
|
|
@@ -804,130 +826,130 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
804
826
|
}
|
|
805
827
|
|
|
806
828
|
/// @notice Delegate all attestation units for the specified tier.
|
|
807
|
-
/// @param
|
|
808
|
-
/// @param
|
|
809
|
-
/// @param
|
|
810
|
-
function _delegateTier(address
|
|
829
|
+
/// @param account The account delegating tier attestation units.
|
|
830
|
+
/// @param delegatee The account to delegate tier attestation units to.
|
|
831
|
+
/// @param tierId The ID of the tier for which attestation units are being transferred.
|
|
832
|
+
function _delegateTier(address account, address delegatee, uint256 tierId) internal virtual {
|
|
811
833
|
// Get the current delegatee
|
|
812
|
-
address
|
|
834
|
+
address oldDelegate = _tierDelegation[account][tierId];
|
|
813
835
|
|
|
814
836
|
// Store the new delegatee
|
|
815
|
-
_tierDelegation[
|
|
837
|
+
_tierDelegation[account][tierId] = delegatee;
|
|
816
838
|
|
|
817
|
-
emit DelegateChanged(
|
|
839
|
+
emit DelegateChanged(account, oldDelegate, delegatee);
|
|
818
840
|
|
|
819
841
|
// Move the attestations.
|
|
820
842
|
_moveTierDelegateAttestations({
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
843
|
+
from: oldDelegate,
|
|
844
|
+
to: delegatee,
|
|
845
|
+
tierId: tierId,
|
|
846
|
+
amount: _getTierAttestationUnits({account: account, tierId: tierId})
|
|
825
847
|
});
|
|
826
848
|
}
|
|
827
849
|
|
|
828
850
|
/// @notice A function that will run when tokens are burned via cashOut.
|
|
829
|
-
/// @param
|
|
830
|
-
function _didBurn(uint256[] memory
|
|
851
|
+
/// @param tokenIds The IDs of the tokens that were burned.
|
|
852
|
+
function _didBurn(uint256[] memory tokenIds) internal virtual override {
|
|
831
853
|
// Add to burned counter.
|
|
832
|
-
store.recordBurn(
|
|
854
|
+
store.recordBurn(tokenIds);
|
|
833
855
|
}
|
|
834
856
|
|
|
835
857
|
/// @notice Gets the amount of attestation units an address has for a particular tier.
|
|
836
|
-
/// @param
|
|
837
|
-
/// @param
|
|
858
|
+
/// @param account The account to get attestation units for.
|
|
859
|
+
/// @param tierId The ID of the tier to get attestation units for.
|
|
838
860
|
/// @return The attestation units.
|
|
839
|
-
function _getTierAttestationUnits(address
|
|
840
|
-
return store.tierVotingUnitsOf({hook: address(this), account:
|
|
861
|
+
function _getTierAttestationUnits(address account, uint256 tierId) internal view virtual returns (uint256) {
|
|
862
|
+
return store.tierVotingUnitsOf({hook: address(this), account: account, tierId: tierId});
|
|
841
863
|
}
|
|
842
864
|
|
|
843
865
|
/// @notice Mints a token in all provided tiers.
|
|
844
|
-
/// @param
|
|
845
|
-
/// @param
|
|
846
|
-
/// @param
|
|
866
|
+
/// @param amount The amount to base the mints on. All mints' price floors must fit in this amount.
|
|
867
|
+
/// @param mintTierIds An array of tier IDs that are intended to be minted.
|
|
868
|
+
/// @param beneficiary The address to mint for.
|
|
847
869
|
/// @return leftoverAmount The amount leftover after the mint.
|
|
848
870
|
function _mintAll(
|
|
849
|
-
uint256
|
|
850
|
-
uint16[] memory
|
|
851
|
-
address
|
|
871
|
+
uint256 amount,
|
|
872
|
+
uint16[] memory mintTierIds,
|
|
873
|
+
address beneficiary
|
|
852
874
|
)
|
|
853
875
|
internal
|
|
854
876
|
returns (uint256 leftoverAmount)
|
|
855
877
|
{
|
|
856
878
|
// Keep a reference to the token ID.
|
|
857
|
-
uint256[] memory
|
|
879
|
+
uint256[] memory tokenIds;
|
|
858
880
|
|
|
859
881
|
// Record the mint. The returned token IDs correspond to the tiers passed in.
|
|
860
882
|
// slither-disable-next-line reentrancy-benign
|
|
861
|
-
(
|
|
862
|
-
amount:
|
|
863
|
-
tierIds:
|
|
883
|
+
(tokenIds, leftoverAmount) = store.recordMint({
|
|
884
|
+
amount: amount,
|
|
885
|
+
tierIds: mintTierIds,
|
|
864
886
|
isOwnerMint: false // Not a manual mint
|
|
865
887
|
});
|
|
866
888
|
|
|
867
889
|
// Get a reference to the number of mints.
|
|
868
|
-
uint256
|
|
890
|
+
uint256 mintsLength = tokenIds.length;
|
|
869
891
|
|
|
870
892
|
// Keep a reference to the token ID being iterated on.
|
|
871
|
-
uint256
|
|
893
|
+
uint256 tokenId;
|
|
872
894
|
|
|
873
895
|
// Increment the paid mint cost.
|
|
874
|
-
_totalMintCost +=
|
|
896
|
+
_totalMintCost += amount;
|
|
875
897
|
|
|
876
898
|
// Loop through each token ID and mint.
|
|
877
|
-
for (uint256
|
|
899
|
+
for (uint256 i; i < mintsLength;) {
|
|
878
900
|
// Get a reference to the tier being iterated on.
|
|
879
|
-
|
|
901
|
+
tokenId = tokenIds[i];
|
|
880
902
|
|
|
881
903
|
// Mint the tokens.
|
|
882
|
-
_mint({to:
|
|
904
|
+
_mint({to: beneficiary, tokenId: tokenId});
|
|
883
905
|
|
|
884
|
-
emit Mint(
|
|
906
|
+
emit Mint(tokenId, mintTierIds[i], beneficiary, amount, msg.sender);
|
|
885
907
|
|
|
886
908
|
unchecked {
|
|
887
|
-
++
|
|
909
|
+
++i;
|
|
888
910
|
}
|
|
889
911
|
}
|
|
890
912
|
}
|
|
891
913
|
|
|
892
914
|
/// @notice Moves delegated tier attestations from one delegate to another.
|
|
893
|
-
/// @param
|
|
894
|
-
/// @param
|
|
895
|
-
/// @param
|
|
896
|
-
/// @param
|
|
897
|
-
function _moveTierDelegateAttestations(address
|
|
915
|
+
/// @param from The account to transfer tier attestation units from.
|
|
916
|
+
/// @param to The account to transfer tier attestation units to.
|
|
917
|
+
/// @param tierId The ID of the tier for which attestation units are being transferred.
|
|
918
|
+
/// @param amount The amount of attestation units to delegate.
|
|
919
|
+
function _moveTierDelegateAttestations(address from, address to, uint256 tierId, uint256 amount) internal {
|
|
898
920
|
// Nothing to do if moving to the same account, or no amount is being moved.
|
|
899
|
-
if (
|
|
921
|
+
if (from == to || amount == 0) return;
|
|
900
922
|
|
|
901
923
|
// If not moving from the zero address, update the checkpoints to subtract the amount.
|
|
902
|
-
if (
|
|
924
|
+
if (from != address(0)) {
|
|
903
925
|
// Get the current amount for the sending delegate.
|
|
904
|
-
uint208
|
|
926
|
+
uint208 current = _delegateTierCheckpoints[from][tierId].latest();
|
|
905
927
|
// Set the new amount for the sending delegate.
|
|
906
928
|
// uint208 is sufficient for attestation values: each tier's attestation units are bounded by the NFT
|
|
907
929
|
// supply (max ~999_999_999 per tier * 128 tiers), well within uint208's ~4.1e62 range.
|
|
908
930
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
909
|
-
(uint256
|
|
931
|
+
(uint256 oldValue, uint256 newValue) = _delegateTierCheckpoints[from][tierId].push({
|
|
910
932
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
911
933
|
key: uint48(block.timestamp),
|
|
912
934
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
913
|
-
value:
|
|
935
|
+
value: current - uint208(amount)
|
|
914
936
|
});
|
|
915
|
-
emit TierDelegateAttestationsChanged(
|
|
937
|
+
emit TierDelegateAttestationsChanged(from, tierId, oldValue, newValue, msg.sender);
|
|
916
938
|
}
|
|
917
939
|
|
|
918
940
|
// If not moving to the zero address, update the checkpoints to add the amount.
|
|
919
|
-
if (
|
|
941
|
+
if (to != address(0)) {
|
|
920
942
|
// Get the current amount for the receiving delegate.
|
|
921
|
-
uint208
|
|
943
|
+
uint208 current = _delegateTierCheckpoints[to][tierId].latest();
|
|
922
944
|
// Set the new amount for the receiving delegate.
|
|
923
945
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
924
|
-
(uint256
|
|
946
|
+
(uint256 oldValue, uint256 newValue) = _delegateTierCheckpoints[to][tierId].push({
|
|
925
947
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
926
948
|
key: uint48(block.timestamp),
|
|
927
949
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
928
|
-
value:
|
|
950
|
+
value: current + uint208(amount)
|
|
929
951
|
});
|
|
930
|
-
emit TierDelegateAttestationsChanged(
|
|
952
|
+
emit TierDelegateAttestationsChanged(to, tierId, oldValue, newValue, msg.sender);
|
|
931
953
|
}
|
|
932
954
|
}
|
|
933
955
|
|
|
@@ -945,92 +967,83 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
945
967
|
if (!found) revert DefifaHook_NothingToMint();
|
|
946
968
|
|
|
947
969
|
// Decode the metadata.
|
|
948
|
-
(address
|
|
970
|
+
(address attestationDelegate, uint16[] memory tierIdsToMint) = abi.decode(metadata, (address, uint16[]));
|
|
949
971
|
|
|
950
972
|
// Set the beneficiary as the attestation delegate by default.
|
|
951
|
-
if (
|
|
952
|
-
|
|
973
|
+
if (attestationDelegate == address(0)) {
|
|
974
|
+
attestationDelegate =
|
|
953
975
|
defaultAttestationDelegate != address(0) ? defaultAttestationDelegate : context.beneficiary;
|
|
954
976
|
}
|
|
955
977
|
|
|
956
978
|
// Make sure something is being minted.
|
|
957
|
-
if (
|
|
979
|
+
if (tierIdsToMint.length == 0) revert DefifaHook_NothingToMint();
|
|
958
980
|
|
|
959
981
|
// Compute attestation units per unique tier (validates ascending order, reverts on bad order).
|
|
960
|
-
(uint256[] memory
|
|
961
|
-
tierIdsToMint:
|
|
962
|
-
});
|
|
982
|
+
(uint256[] memory tierIds, uint256[] memory attestationAmounts, uint256 uniqueTierCount) =
|
|
983
|
+
DefifaHookLib.computeAttestationUnits({tierIdsToMint: tierIdsToMint, hookStore: store, hook: address(this)});
|
|
963
984
|
|
|
964
985
|
// Apply attestation units for each unique tier.
|
|
965
|
-
for (uint256
|
|
966
|
-
uint256
|
|
986
|
+
for (uint256 i; i < uniqueTierCount;) {
|
|
987
|
+
uint256 tierId = tierIds[i];
|
|
967
988
|
|
|
968
989
|
// Get a reference to the old delegate.
|
|
969
|
-
address
|
|
990
|
+
address oldDelegate = _tierDelegation[context.beneficiary][tierId];
|
|
970
991
|
|
|
971
992
|
// If there's either a new delegate or old delegate, set delegation and transfer units.
|
|
972
|
-
if (
|
|
993
|
+
if (attestationDelegate != address(0) || oldDelegate != address(0)) {
|
|
973
994
|
// Switch delegates if needed.
|
|
974
|
-
if (
|
|
975
|
-
_delegateTier({
|
|
995
|
+
if (attestationDelegate != address(0) && attestationDelegate != oldDelegate) {
|
|
996
|
+
_delegateTier({account: context.beneficiary, delegatee: attestationDelegate, tierId: tierId});
|
|
976
997
|
}
|
|
977
998
|
|
|
978
999
|
// Transfer the attestation units.
|
|
979
1000
|
_transferTierAttestationUnits({
|
|
980
|
-
|
|
1001
|
+
from: address(0), to: context.beneficiary, tierId: tierId, amount: attestationAmounts[i]
|
|
981
1002
|
});
|
|
982
1003
|
}
|
|
983
1004
|
|
|
984
1005
|
unchecked {
|
|
985
|
-
++
|
|
1006
|
+
++i;
|
|
986
1007
|
}
|
|
987
1008
|
}
|
|
988
1009
|
|
|
989
1010
|
// Mint tiers if they were specified.
|
|
990
|
-
uint256
|
|
991
|
-
_mintAll({
|
|
1011
|
+
uint256 leftoverAmount =
|
|
1012
|
+
_mintAll({amount: context.amount.value, mintTierIds: tierIdsToMint, beneficiary: context.beneficiary});
|
|
992
1013
|
|
|
993
1014
|
// Make sure the buyer isn't overspending.
|
|
994
|
-
if (
|
|
1015
|
+
if (leftoverAmount != 0) revert DefifaHook_Overspending();
|
|
995
1016
|
}
|
|
996
1017
|
|
|
997
|
-
/// @notice Transfers, mints, or burns tier attestation units. To register a mint, `
|
|
998
|
-
/// register a burn, `
|
|
999
|
-
/// @param
|
|
1000
|
-
/// @param
|
|
1001
|
-
/// @param
|
|
1002
|
-
/// @param
|
|
1003
|
-
function _transferTierAttestationUnits(
|
|
1004
|
-
address
|
|
1005
|
-
address _to,
|
|
1006
|
-
uint256 _tierId,
|
|
1007
|
-
uint256 _amount
|
|
1008
|
-
)
|
|
1009
|
-
internal
|
|
1010
|
-
virtual
|
|
1011
|
-
{
|
|
1012
|
-
if (_from == address(0) || _to == address(0)) {
|
|
1018
|
+
/// @notice Transfers, mints, or burns tier attestation units. To register a mint, `from` should be zero. To
|
|
1019
|
+
/// register a burn, `to` should be zero. Total supply of attestation units will be adjusted with mints and burns.
|
|
1020
|
+
/// @param from The account to transfer tier attestation units from.
|
|
1021
|
+
/// @param to The account to transfer tier attestation units to.
|
|
1022
|
+
/// @param tierId The ID of the tier for which attestation units are being transferred.
|
|
1023
|
+
/// @param amount The amount of attestation units to delegate.
|
|
1024
|
+
function _transferTierAttestationUnits(address from, address to, uint256 tierId, uint256 amount) internal virtual {
|
|
1025
|
+
if (from == address(0) || to == address(0)) {
|
|
1013
1026
|
// Get the current total for the tier.
|
|
1014
|
-
uint208
|
|
1027
|
+
uint208 current = _totalTierCheckpoints[tierId].latest();
|
|
1015
1028
|
|
|
1016
1029
|
// If minting, add to the total tier checkpoints.
|
|
1017
|
-
if (
|
|
1030
|
+
if (from == address(0)) {
|
|
1018
1031
|
// Casting to uint208/uint48 is safe because attestation unit amounts are bounded by NFT supply counts.
|
|
1019
1032
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1020
|
-
uint208 newValue =
|
|
1033
|
+
uint208 newValue = current + uint208(amount);
|
|
1021
1034
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1022
1035
|
// slither-disable-next-line unused-return
|
|
1023
|
-
_totalTierCheckpoints[
|
|
1036
|
+
_totalTierCheckpoints[tierId].push({key: uint48(block.timestamp), value: newValue});
|
|
1024
1037
|
}
|
|
1025
1038
|
|
|
1026
1039
|
// If burning, subtract from the total tier checkpoints.
|
|
1027
|
-
if (
|
|
1040
|
+
if (to == address(0)) {
|
|
1028
1041
|
// Casting to uint208/uint48 is safe because attestation unit amounts are bounded by NFT supply counts.
|
|
1029
1042
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1030
|
-
uint208 newValue =
|
|
1043
|
+
uint208 newValue = current - uint208(amount);
|
|
1031
1044
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1032
1045
|
// slither-disable-next-line unused-return
|
|
1033
|
-
_totalTierCheckpoints[
|
|
1046
|
+
_totalTierCheckpoints[tierId].push({key: uint48(block.timestamp), value: newValue});
|
|
1034
1047
|
}
|
|
1035
1048
|
}
|
|
1036
1049
|
|
|
@@ -1040,16 +1053,16 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1040
1053
|
// to Carol, Carol's attestation units auto-delegate to Carol (not Bob). However, Alice's delegation
|
|
1041
1054
|
// to Bob persists — if Alice later receives another token, her units still go to Bob. This matches
|
|
1042
1055
|
// ERC5805Votes behavior where delegation is an account-level setting, not a token-level one.
|
|
1043
|
-
address
|
|
1044
|
-
if (
|
|
1045
|
-
|
|
1046
|
-
_tierDelegation[
|
|
1047
|
-
emit DelegateChanged(
|
|
1056
|
+
address toDelegate = _tierDelegation[to][tierId];
|
|
1057
|
+
if (toDelegate == address(0) && to != address(0)) {
|
|
1058
|
+
toDelegate = to;
|
|
1059
|
+
_tierDelegation[to][tierId] = to;
|
|
1060
|
+
emit DelegateChanged(to, address(0), to);
|
|
1048
1061
|
}
|
|
1049
1062
|
|
|
1050
1063
|
// Move delegated attestations.
|
|
1051
1064
|
_moveTierDelegateAttestations({
|
|
1052
|
-
|
|
1065
|
+
from: _tierDelegation[from][tierId], to: toDelegate, tierId: tierId, amount: amount
|
|
1053
1066
|
});
|
|
1054
1067
|
}
|
|
1055
1068
|
|
|
@@ -1093,6 +1106,6 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1093
1106
|
if (from == address(0)) return from;
|
|
1094
1107
|
|
|
1095
1108
|
// Transfer the attestation units.
|
|
1096
|
-
_transferTierAttestationUnits({
|
|
1109
|
+
_transferTierAttestationUnits({from: from, to: to, tierId: tier.id, amount: tier.votingUnits});
|
|
1097
1110
|
}
|
|
1098
1111
|
}
|