@ballkidz/defifa 0.0.11 → 0.0.13
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/AUDIT_INSTRUCTIONS.md +2 -2
- package/CHANGE_LOG.md +60 -5
- package/CRYPTO_ECON.md +505 -270
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +438 -241
- package/RISKS.md +9 -1
- package/SKILLS.md +3 -2
- package/STYLE_GUIDE.md +2 -2
- package/foundry.toml +1 -1
- package/package.json +7 -7
- package/script/Deploy.s.sol +1 -1
- package/script/helpers/DefifaDeploymentLib.sol +1 -1
- package/src/DefifaDeployer.sol +129 -131
- package/src/DefifaGovernor.sol +279 -84
- package/src/DefifaHook.sol +159 -172
- package/src/DefifaProjectOwner.sol +1 -1
- package/src/DefifaTokenUriResolver.sol +1 -1
- package/src/enums/DefifaScorecardState.sol +1 -0
- package/src/interfaces/IDefifaGovernor.sol +41 -2
- package/src/libraries/DefifaFontImporter.sol +1 -1
- package/src/libraries/DefifaHookLib.sol +70 -63
- 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 +53 -38
- package/test/DefifaAuditLowGuards.t.sol +10 -6
- package/test/DefifaFeeAccounting.t.sol +3 -2
- package/test/DefifaGovernanceHardening.t.sol +1311 -0
- package/test/DefifaGovernor.t.sol +5 -3
- package/test/DefifaHookRegressions.t.sol +3 -2
- package/test/DefifaMintCostInvariant.t.sol +3 -2
- package/test/DefifaNoContest.t.sol +4 -3
- package/test/DefifaSecurity.t.sol +55 -42
- package/test/DefifaUSDC.t.sol +4 -3
- package/test/Fork.t.sol +12 -13
- package/test/SVG.t.sol +1 -1
- package/test/TestAuditGaps.sol +7 -5
- package/test/TestQALastMile.t.sol +5 -3
- package/test/audit/{CodexAttestationDoubleCount.t.sol → AttestationDoubleCount.t.sol} +4 -3
- 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/deployScript.t.sol +1 -1
- package/test/regression/AttestationDelegateBeneficiary.t.sol +3 -2
- package/test/regression/FulfillmentBlocksRatification.t.sol +3 -2
- package/test/regression/GracePeriodBypass.t.sol +3 -2
package/src/DefifaHook.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
|
|
5
5
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
@@ -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,52 @@ 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
|
+
beneficiaryReceivedTokens = _claimTokensFor({
|
|
674
|
+
beneficiary: context.holder, shareToBeneficiary: cumulativeMintPrice, outOfTotal: _totalMintCost
|
|
679
675
|
});
|
|
680
676
|
}
|
|
681
677
|
|
|
@@ -683,27 +679,27 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
683
679
|
// Tokens in 0-weight tiers (losing teams) cannot burn to reclaim fees if no fee tokens were
|
|
684
680
|
// distributed. This is correct behavior — 0-weight means the tier has no claim on the pot. Burning would
|
|
685
681
|
// return 0 value regardless.
|
|
686
|
-
if (context.reclaimedAmount.value == 0 && !
|
|
682
|
+
if (context.reclaimedAmount.value == 0 && !beneficiaryReceivedTokens) revert DefifaHook_NothingToClaim();
|
|
687
683
|
|
|
688
684
|
// Decrement the paid mint cost by the cumulative mint price of the tokens being burned.
|
|
689
|
-
_totalMintCost -=
|
|
685
|
+
_totalMintCost -= cumulativeMintPrice;
|
|
690
686
|
}
|
|
691
687
|
|
|
692
688
|
/// @notice Mint reserved tokens within the tier for the provided value.
|
|
693
689
|
/// @param mintReservesForTiersData Contains information about how many reserved tokens to mint for each tier.
|
|
694
690
|
function mintReservesFor(JB721TiersMintReservesConfig[] calldata mintReservesForTiersData) external override {
|
|
695
691
|
// Keep a reference to the number of tiers there are to mint reserves for.
|
|
696
|
-
uint256
|
|
692
|
+
uint256 numberOfTiers = mintReservesForTiersData.length;
|
|
697
693
|
|
|
698
|
-
for (uint256
|
|
694
|
+
for (uint256 i; i < numberOfTiers;) {
|
|
699
695
|
// Get a reference to the data being iterated on.
|
|
700
|
-
JB721TiersMintReservesConfig memory
|
|
696
|
+
JB721TiersMintReservesConfig memory data = mintReservesForTiersData[i];
|
|
701
697
|
|
|
702
698
|
// Mint for the tier.
|
|
703
|
-
mintReservesFor(
|
|
699
|
+
mintReservesFor(data.tierId, data.count);
|
|
704
700
|
|
|
705
701
|
unchecked {
|
|
706
|
-
++
|
|
702
|
+
++i;
|
|
707
703
|
}
|
|
708
704
|
}
|
|
709
705
|
}
|
|
@@ -713,10 +709,10 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
713
709
|
/// @param tierWeights The tier weights to set.
|
|
714
710
|
function setTierCashOutWeightsTo(DefifaTierCashOutWeight[] memory tierWeights) external override onlyOwner {
|
|
715
711
|
// Get a reference to the game phase.
|
|
716
|
-
DefifaGamePhase
|
|
712
|
+
DefifaGamePhase gamePhase = gamePhaseReporter.currentGamePhaseOf(PROJECT_ID);
|
|
717
713
|
|
|
718
714
|
// Make sure the game has ended.
|
|
719
|
-
if (
|
|
715
|
+
if (gamePhase != DefifaGamePhase.SCORING) {
|
|
720
716
|
revert DefifaHook_GameIsntScoringYet();
|
|
721
717
|
}
|
|
722
718
|
|
|
@@ -745,7 +741,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
745
741
|
revert DefifaHook_DelegateChangesUnavailableInThisPhase();
|
|
746
742
|
}
|
|
747
743
|
|
|
748
|
-
_delegateTier({
|
|
744
|
+
_delegateTier({account: msg.sender, delegatee: delegatee, tierId: tierId});
|
|
749
745
|
}
|
|
750
746
|
|
|
751
747
|
/// @notice Delegate attestations.
|
|
@@ -757,22 +753,22 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
757
753
|
}
|
|
758
754
|
|
|
759
755
|
// Keep a reference to the number of tier delegates.
|
|
760
|
-
uint256
|
|
756
|
+
uint256 numberOfTierDelegates = delegations.length;
|
|
761
757
|
|
|
762
758
|
// Keep a reference to the data being iterated on.
|
|
763
|
-
DefifaDelegation memory
|
|
759
|
+
DefifaDelegation memory data;
|
|
764
760
|
|
|
765
|
-
for (uint256
|
|
761
|
+
for (uint256 i; i < numberOfTierDelegates;) {
|
|
766
762
|
// Reference the data being iterated on.
|
|
767
|
-
|
|
763
|
+
data = delegations[i];
|
|
768
764
|
|
|
769
765
|
// Make sure a delegate is specified.
|
|
770
|
-
if (
|
|
766
|
+
if (data.delegatee == address(0)) revert DefifaHook_DelegateAddressZero();
|
|
771
767
|
|
|
772
|
-
_delegateTier({
|
|
768
|
+
_delegateTier({account: msg.sender, delegatee: data.delegatee, tierId: data.tierId});
|
|
773
769
|
|
|
774
770
|
unchecked {
|
|
775
|
-
++
|
|
771
|
+
++i;
|
|
776
772
|
}
|
|
777
773
|
}
|
|
778
774
|
}
|
|
@@ -782,12 +778,12 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
782
778
|
//*********************************************************************//
|
|
783
779
|
|
|
784
780
|
/// @notice Claims the defifa and base protocol tokens for a beneficiary.
|
|
785
|
-
/// @param
|
|
781
|
+
/// @param beneficiary The address to claim tokens for.
|
|
786
782
|
/// @param shareToBeneficiary The share relative to the `outOfTotal` to send the user.
|
|
787
783
|
/// @param outOfTotal The total share that the `shareToBeneficiary` is relative to.
|
|
788
784
|
/// @return beneficiaryReceivedTokens A flag indicating if the beneficiary received any tokens.
|
|
789
785
|
function _claimTokensFor(
|
|
790
|
-
address
|
|
786
|
+
address beneficiary,
|
|
791
787
|
uint256 shareToBeneficiary,
|
|
792
788
|
uint256 outOfTotal
|
|
793
789
|
)
|
|
@@ -795,7 +791,7 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
795
791
|
returns (bool beneficiaryReceivedTokens)
|
|
796
792
|
{
|
|
797
793
|
return DefifaHookLib.claimTokensFor({
|
|
798
|
-
beneficiary:
|
|
794
|
+
beneficiary: beneficiary,
|
|
799
795
|
shareToBeneficiary: shareToBeneficiary,
|
|
800
796
|
outOfTotal: outOfTotal,
|
|
801
797
|
defifaToken: DEFIFA_TOKEN,
|
|
@@ -804,130 +800,130 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
804
800
|
}
|
|
805
801
|
|
|
806
802
|
/// @notice Delegate all attestation units for the specified tier.
|
|
807
|
-
/// @param
|
|
808
|
-
/// @param
|
|
809
|
-
/// @param
|
|
810
|
-
function _delegateTier(address
|
|
803
|
+
/// @param account The account delegating tier attestation units.
|
|
804
|
+
/// @param delegatee The account to delegate tier attestation units to.
|
|
805
|
+
/// @param tierId The ID of the tier for which attestation units are being transferred.
|
|
806
|
+
function _delegateTier(address account, address delegatee, uint256 tierId) internal virtual {
|
|
811
807
|
// Get the current delegatee
|
|
812
|
-
address
|
|
808
|
+
address oldDelegate = _tierDelegation[account][tierId];
|
|
813
809
|
|
|
814
810
|
// Store the new delegatee
|
|
815
|
-
_tierDelegation[
|
|
811
|
+
_tierDelegation[account][tierId] = delegatee;
|
|
816
812
|
|
|
817
|
-
emit DelegateChanged(
|
|
813
|
+
emit DelegateChanged(account, oldDelegate, delegatee);
|
|
818
814
|
|
|
819
815
|
// Move the attestations.
|
|
820
816
|
_moveTierDelegateAttestations({
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
817
|
+
from: oldDelegate,
|
|
818
|
+
to: delegatee,
|
|
819
|
+
tierId: tierId,
|
|
820
|
+
amount: _getTierAttestationUnits({account: account, tierId: tierId})
|
|
825
821
|
});
|
|
826
822
|
}
|
|
827
823
|
|
|
828
824
|
/// @notice A function that will run when tokens are burned via cashOut.
|
|
829
|
-
/// @param
|
|
830
|
-
function _didBurn(uint256[] memory
|
|
825
|
+
/// @param tokenIds The IDs of the tokens that were burned.
|
|
826
|
+
function _didBurn(uint256[] memory tokenIds) internal virtual override {
|
|
831
827
|
// Add to burned counter.
|
|
832
|
-
store.recordBurn(
|
|
828
|
+
store.recordBurn(tokenIds);
|
|
833
829
|
}
|
|
834
830
|
|
|
835
831
|
/// @notice Gets the amount of attestation units an address has for a particular tier.
|
|
836
|
-
/// @param
|
|
837
|
-
/// @param
|
|
832
|
+
/// @param account The account to get attestation units for.
|
|
833
|
+
/// @param tierId The ID of the tier to get attestation units for.
|
|
838
834
|
/// @return The attestation units.
|
|
839
|
-
function _getTierAttestationUnits(address
|
|
840
|
-
return store.tierVotingUnitsOf({hook: address(this), account:
|
|
835
|
+
function _getTierAttestationUnits(address account, uint256 tierId) internal view virtual returns (uint256) {
|
|
836
|
+
return store.tierVotingUnitsOf({hook: address(this), account: account, tierId: tierId});
|
|
841
837
|
}
|
|
842
838
|
|
|
843
839
|
/// @notice Mints a token in all provided tiers.
|
|
844
|
-
/// @param
|
|
845
|
-
/// @param
|
|
846
|
-
/// @param
|
|
840
|
+
/// @param amount The amount to base the mints on. All mints' price floors must fit in this amount.
|
|
841
|
+
/// @param mintTierIds An array of tier IDs that are intended to be minted.
|
|
842
|
+
/// @param beneficiary The address to mint for.
|
|
847
843
|
/// @return leftoverAmount The amount leftover after the mint.
|
|
848
844
|
function _mintAll(
|
|
849
|
-
uint256
|
|
850
|
-
uint16[] memory
|
|
851
|
-
address
|
|
845
|
+
uint256 amount,
|
|
846
|
+
uint16[] memory mintTierIds,
|
|
847
|
+
address beneficiary
|
|
852
848
|
)
|
|
853
849
|
internal
|
|
854
850
|
returns (uint256 leftoverAmount)
|
|
855
851
|
{
|
|
856
852
|
// Keep a reference to the token ID.
|
|
857
|
-
uint256[] memory
|
|
853
|
+
uint256[] memory tokenIds;
|
|
858
854
|
|
|
859
855
|
// Record the mint. The returned token IDs correspond to the tiers passed in.
|
|
860
856
|
// slither-disable-next-line reentrancy-benign
|
|
861
|
-
(
|
|
862
|
-
amount:
|
|
863
|
-
tierIds:
|
|
857
|
+
(tokenIds, leftoverAmount) = store.recordMint({
|
|
858
|
+
amount: amount,
|
|
859
|
+
tierIds: mintTierIds,
|
|
864
860
|
isOwnerMint: false // Not a manual mint
|
|
865
861
|
});
|
|
866
862
|
|
|
867
863
|
// Get a reference to the number of mints.
|
|
868
|
-
uint256
|
|
864
|
+
uint256 mintsLength = tokenIds.length;
|
|
869
865
|
|
|
870
866
|
// Keep a reference to the token ID being iterated on.
|
|
871
|
-
uint256
|
|
867
|
+
uint256 tokenId;
|
|
872
868
|
|
|
873
869
|
// Increment the paid mint cost.
|
|
874
|
-
_totalMintCost +=
|
|
870
|
+
_totalMintCost += amount;
|
|
875
871
|
|
|
876
872
|
// Loop through each token ID and mint.
|
|
877
|
-
for (uint256
|
|
873
|
+
for (uint256 i; i < mintsLength;) {
|
|
878
874
|
// Get a reference to the tier being iterated on.
|
|
879
|
-
|
|
875
|
+
tokenId = tokenIds[i];
|
|
880
876
|
|
|
881
877
|
// Mint the tokens.
|
|
882
|
-
_mint({to:
|
|
878
|
+
_mint({to: beneficiary, tokenId: tokenId});
|
|
883
879
|
|
|
884
|
-
emit Mint(
|
|
880
|
+
emit Mint(tokenId, mintTierIds[i], beneficiary, amount, msg.sender);
|
|
885
881
|
|
|
886
882
|
unchecked {
|
|
887
|
-
++
|
|
883
|
+
++i;
|
|
888
884
|
}
|
|
889
885
|
}
|
|
890
886
|
}
|
|
891
887
|
|
|
892
888
|
/// @notice Moves delegated tier attestations from one delegate to another.
|
|
893
|
-
/// @param
|
|
894
|
-
/// @param
|
|
895
|
-
/// @param
|
|
896
|
-
/// @param
|
|
897
|
-
function _moveTierDelegateAttestations(address
|
|
889
|
+
/// @param from The account to transfer tier attestation units from.
|
|
890
|
+
/// @param to The account to transfer tier attestation units to.
|
|
891
|
+
/// @param tierId The ID of the tier for which attestation units are being transferred.
|
|
892
|
+
/// @param amount The amount of attestation units to delegate.
|
|
893
|
+
function _moveTierDelegateAttestations(address from, address to, uint256 tierId, uint256 amount) internal {
|
|
898
894
|
// Nothing to do if moving to the same account, or no amount is being moved.
|
|
899
|
-
if (
|
|
895
|
+
if (from == to || amount == 0) return;
|
|
900
896
|
|
|
901
897
|
// If not moving from the zero address, update the checkpoints to subtract the amount.
|
|
902
|
-
if (
|
|
898
|
+
if (from != address(0)) {
|
|
903
899
|
// Get the current amount for the sending delegate.
|
|
904
|
-
uint208
|
|
900
|
+
uint208 current = _delegateTierCheckpoints[from][tierId].latest();
|
|
905
901
|
// Set the new amount for the sending delegate.
|
|
906
902
|
// uint208 is sufficient for attestation values: each tier's attestation units are bounded by the NFT
|
|
907
903
|
// supply (max ~999_999_999 per tier * 128 tiers), well within uint208's ~4.1e62 range.
|
|
908
904
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
909
|
-
(uint256
|
|
905
|
+
(uint256 oldValue, uint256 newValue) = _delegateTierCheckpoints[from][tierId].push({
|
|
910
906
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
911
907
|
key: uint48(block.timestamp),
|
|
912
908
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
913
|
-
value:
|
|
909
|
+
value: current - uint208(amount)
|
|
914
910
|
});
|
|
915
|
-
emit TierDelegateAttestationsChanged(
|
|
911
|
+
emit TierDelegateAttestationsChanged(from, tierId, oldValue, newValue, msg.sender);
|
|
916
912
|
}
|
|
917
913
|
|
|
918
914
|
// If not moving to the zero address, update the checkpoints to add the amount.
|
|
919
|
-
if (
|
|
915
|
+
if (to != address(0)) {
|
|
920
916
|
// Get the current amount for the receiving delegate.
|
|
921
|
-
uint208
|
|
917
|
+
uint208 current = _delegateTierCheckpoints[to][tierId].latest();
|
|
922
918
|
// Set the new amount for the receiving delegate.
|
|
923
919
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
924
|
-
(uint256
|
|
920
|
+
(uint256 oldValue, uint256 newValue) = _delegateTierCheckpoints[to][tierId].push({
|
|
925
921
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
926
922
|
key: uint48(block.timestamp),
|
|
927
923
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
928
|
-
value:
|
|
924
|
+
value: current + uint208(amount)
|
|
929
925
|
});
|
|
930
|
-
emit TierDelegateAttestationsChanged(
|
|
926
|
+
emit TierDelegateAttestationsChanged(to, tierId, oldValue, newValue, msg.sender);
|
|
931
927
|
}
|
|
932
928
|
}
|
|
933
929
|
|
|
@@ -945,92 +941,83 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
945
941
|
if (!found) revert DefifaHook_NothingToMint();
|
|
946
942
|
|
|
947
943
|
// Decode the metadata.
|
|
948
|
-
(address
|
|
944
|
+
(address attestationDelegate, uint16[] memory tierIdsToMint) = abi.decode(metadata, (address, uint16[]));
|
|
949
945
|
|
|
950
946
|
// Set the beneficiary as the attestation delegate by default.
|
|
951
|
-
if (
|
|
952
|
-
|
|
947
|
+
if (attestationDelegate == address(0)) {
|
|
948
|
+
attestationDelegate =
|
|
953
949
|
defaultAttestationDelegate != address(0) ? defaultAttestationDelegate : context.beneficiary;
|
|
954
950
|
}
|
|
955
951
|
|
|
956
952
|
// Make sure something is being minted.
|
|
957
|
-
if (
|
|
953
|
+
if (tierIdsToMint.length == 0) revert DefifaHook_NothingToMint();
|
|
958
954
|
|
|
959
955
|
// Compute attestation units per unique tier (validates ascending order, reverts on bad order).
|
|
960
|
-
(uint256[] memory
|
|
961
|
-
tierIdsToMint:
|
|
962
|
-
});
|
|
956
|
+
(uint256[] memory tierIds, uint256[] memory attestationAmounts, uint256 uniqueTierCount) =
|
|
957
|
+
DefifaHookLib.computeAttestationUnits({tierIdsToMint: tierIdsToMint, hookStore: store, hook: address(this)});
|
|
963
958
|
|
|
964
959
|
// Apply attestation units for each unique tier.
|
|
965
|
-
for (uint256
|
|
966
|
-
uint256
|
|
960
|
+
for (uint256 i; i < uniqueTierCount;) {
|
|
961
|
+
uint256 tierId = tierIds[i];
|
|
967
962
|
|
|
968
963
|
// Get a reference to the old delegate.
|
|
969
|
-
address
|
|
964
|
+
address oldDelegate = _tierDelegation[context.beneficiary][tierId];
|
|
970
965
|
|
|
971
966
|
// If there's either a new delegate or old delegate, set delegation and transfer units.
|
|
972
|
-
if (
|
|
967
|
+
if (attestationDelegate != address(0) || oldDelegate != address(0)) {
|
|
973
968
|
// Switch delegates if needed.
|
|
974
|
-
if (
|
|
975
|
-
_delegateTier({
|
|
969
|
+
if (attestationDelegate != address(0) && attestationDelegate != oldDelegate) {
|
|
970
|
+
_delegateTier({account: context.beneficiary, delegatee: attestationDelegate, tierId: tierId});
|
|
976
971
|
}
|
|
977
972
|
|
|
978
973
|
// Transfer the attestation units.
|
|
979
974
|
_transferTierAttestationUnits({
|
|
980
|
-
|
|
975
|
+
from: address(0), to: context.beneficiary, tierId: tierId, amount: attestationAmounts[i]
|
|
981
976
|
});
|
|
982
977
|
}
|
|
983
978
|
|
|
984
979
|
unchecked {
|
|
985
|
-
++
|
|
980
|
+
++i;
|
|
986
981
|
}
|
|
987
982
|
}
|
|
988
983
|
|
|
989
984
|
// Mint tiers if they were specified.
|
|
990
|
-
uint256
|
|
991
|
-
_mintAll({
|
|
985
|
+
uint256 leftoverAmount =
|
|
986
|
+
_mintAll({amount: context.amount.value, mintTierIds: tierIdsToMint, beneficiary: context.beneficiary});
|
|
992
987
|
|
|
993
988
|
// Make sure the buyer isn't overspending.
|
|
994
|
-
if (
|
|
989
|
+
if (leftoverAmount != 0) revert DefifaHook_Overspending();
|
|
995
990
|
}
|
|
996
991
|
|
|
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)) {
|
|
992
|
+
/// @notice Transfers, mints, or burns tier attestation units. To register a mint, `from` should be zero. To
|
|
993
|
+
/// register a burn, `to` should be zero. Total supply of attestation units will be adjusted with mints and burns.
|
|
994
|
+
/// @param from The account to transfer tier attestation units from.
|
|
995
|
+
/// @param to The account to transfer tier attestation units to.
|
|
996
|
+
/// @param tierId The ID of the tier for which attestation units are being transferred.
|
|
997
|
+
/// @param amount The amount of attestation units to delegate.
|
|
998
|
+
function _transferTierAttestationUnits(address from, address to, uint256 tierId, uint256 amount) internal virtual {
|
|
999
|
+
if (from == address(0) || to == address(0)) {
|
|
1013
1000
|
// Get the current total for the tier.
|
|
1014
|
-
uint208
|
|
1001
|
+
uint208 current = _totalTierCheckpoints[tierId].latest();
|
|
1015
1002
|
|
|
1016
1003
|
// If minting, add to the total tier checkpoints.
|
|
1017
|
-
if (
|
|
1004
|
+
if (from == address(0)) {
|
|
1018
1005
|
// Casting to uint208/uint48 is safe because attestation unit amounts are bounded by NFT supply counts.
|
|
1019
1006
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1020
|
-
uint208 newValue =
|
|
1007
|
+
uint208 newValue = current + uint208(amount);
|
|
1021
1008
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1022
1009
|
// slither-disable-next-line unused-return
|
|
1023
|
-
_totalTierCheckpoints[
|
|
1010
|
+
_totalTierCheckpoints[tierId].push({key: uint48(block.timestamp), value: newValue});
|
|
1024
1011
|
}
|
|
1025
1012
|
|
|
1026
1013
|
// If burning, subtract from the total tier checkpoints.
|
|
1027
|
-
if (
|
|
1014
|
+
if (to == address(0)) {
|
|
1028
1015
|
// Casting to uint208/uint48 is safe because attestation unit amounts are bounded by NFT supply counts.
|
|
1029
1016
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1030
|
-
uint208 newValue =
|
|
1017
|
+
uint208 newValue = current - uint208(amount);
|
|
1031
1018
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1032
1019
|
// slither-disable-next-line unused-return
|
|
1033
|
-
_totalTierCheckpoints[
|
|
1020
|
+
_totalTierCheckpoints[tierId].push({key: uint48(block.timestamp), value: newValue});
|
|
1034
1021
|
}
|
|
1035
1022
|
}
|
|
1036
1023
|
|
|
@@ -1040,16 +1027,16 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1040
1027
|
// to Carol, Carol's attestation units auto-delegate to Carol (not Bob). However, Alice's delegation
|
|
1041
1028
|
// to Bob persists — if Alice later receives another token, her units still go to Bob. This matches
|
|
1042
1029
|
// ERC5805Votes behavior where delegation is an account-level setting, not a token-level one.
|
|
1043
|
-
address
|
|
1044
|
-
if (
|
|
1045
|
-
|
|
1046
|
-
_tierDelegation[
|
|
1047
|
-
emit DelegateChanged(
|
|
1030
|
+
address toDelegate = _tierDelegation[to][tierId];
|
|
1031
|
+
if (toDelegate == address(0) && to != address(0)) {
|
|
1032
|
+
toDelegate = to;
|
|
1033
|
+
_tierDelegation[to][tierId] = to;
|
|
1034
|
+
emit DelegateChanged(to, address(0), to);
|
|
1048
1035
|
}
|
|
1049
1036
|
|
|
1050
1037
|
// Move delegated attestations.
|
|
1051
1038
|
_moveTierDelegateAttestations({
|
|
1052
|
-
|
|
1039
|
+
from: _tierDelegation[from][tierId], to: toDelegate, tierId: tierId, amount: amount
|
|
1053
1040
|
});
|
|
1054
1041
|
}
|
|
1055
1042
|
|
|
@@ -1093,6 +1080,6 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
1093
1080
|
if (from == address(0)) return from;
|
|
1094
1081
|
|
|
1095
1082
|
// Transfer the attestation units.
|
|
1096
|
-
_transferTierAttestationUnits({
|
|
1083
|
+
_transferTierAttestationUnits({from: from, to: to, tierId: tier.id, amount: tier.votingUnits});
|
|
1097
1084
|
}
|
|
1098
1085
|
}
|