@ballkidz/defifa 0.0.9 → 0.0.11
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 +26 -15
- package/ARCHITECTURE.md +35 -3
- package/AUDIT_INSTRUCTIONS.md +127 -45
- package/CHANGE_LOG.md +107 -0
- package/CRYPTO_ECON.md +2 -2
- package/README.md +120 -2
- package/RISKS.md +21 -4
- package/SKILLS.md +174 -59
- package/STYLE_GUIDE.md +1 -1
- package/USER_JOURNEYS.md +482 -139
- package/package.json +7 -7
- package/script/Deploy.s.sol +1 -1
- package/script/helpers/DefifaDeploymentLib.sol +2 -2
- package/src/DefifaDeployer.sol +6 -2
- package/src/DefifaGovernor.sol +2 -1
- package/src/DefifaHook.sol +7 -6
- package/src/DefifaProjectOwner.sol +1 -1
- package/src/DefifaTokenUriResolver.sol +1 -1
- package/test/DefifaAdversarialQuorum.t.sol +1 -1
- package/test/DefifaAuditLowGuards.t.sol +1 -1
- package/test/DefifaFeeAccounting.t.sol +1 -1
- package/test/DefifaGovernor.t.sol +1 -1
- package/test/DefifaHookRegressions.t.sol +39 -1
- package/test/DefifaMintCostInvariant.t.sol +1 -1
- package/test/DefifaNoContest.t.sol +1 -1
- package/test/DefifaSecurity.t.sol +1 -1
- package/test/DefifaUSDC.t.sol +1 -1
- package/test/Fork.t.sol +1 -1
- package/test/SVG.t.sol +1 -1
- package/test/TestAuditGaps.sol +1 -1
- package/test/TestQALastMile.t.sol +1 -1
- package/test/audit/CodexAttestationDoubleCount.t.sol +217 -0
- package/test/deployScript.t.sol +1 -1
- package/test/regression/AttestationDelegateBeneficiary.t.sol +272 -0
- package/test/regression/FulfillmentBlocksRatification.t.sol +1 -1
- package/test/regression/GracePeriodBypass.t.sol +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ballkidz/defifa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"url": "https://github.com/BallKidz/defifa-collection-deployer"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
17
|
-
"@bananapus/address-registry-v6": "^0.0.
|
|
18
|
-
"@bananapus/core-v6": "^0.0.
|
|
19
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
20
|
-
"@croptop/core-v6": "^0.0.
|
|
16
|
+
"@bananapus/721-hook-v6": "^0.0.20",
|
|
17
|
+
"@bananapus/address-registry-v6": "^0.0.14",
|
|
18
|
+
"@bananapus/core-v6": "^0.0.26",
|
|
19
|
+
"@bananapus/permission-ids-v6": "^0.0.12",
|
|
20
|
+
"@croptop/core-v6": "^0.0.21",
|
|
21
21
|
"@openzeppelin/contracts": "^5.6.1",
|
|
22
22
|
"@prb/math": "^4.1.1",
|
|
23
|
-
"@rev-net/core-v6": "^0.0.
|
|
23
|
+
"@rev-net/core-v6": "^0.0.16",
|
|
24
24
|
"scripty.sol": "^2.1.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
package/script/Deploy.s.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.26;
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
3
|
|
|
4
4
|
import {stdJson} from "forge-std/Script.sol";
|
|
5
5
|
import {Vm} from "forge-std/Vm.sol";
|
|
@@ -22,7 +22,7 @@ library DefifaDeploymentLib {
|
|
|
22
22
|
// Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D.
|
|
23
23
|
address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
|
|
24
24
|
Vm internal constant VM = Vm(VM_ADDRESS);
|
|
25
|
-
string constant PROJECT_NAME = "defifa-
|
|
25
|
+
string constant PROJECT_NAME = "defifa-v6";
|
|
26
26
|
|
|
27
27
|
function getDeployment(string memory path) internal returns (DefifaDeployment memory deployment) {
|
|
28
28
|
// Get chainId for which we need to get the deployment.
|
package/src/DefifaDeployer.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.26;
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
3
|
|
|
4
4
|
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
5
5
|
import {
|
|
@@ -331,7 +331,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
331
331
|
|
|
332
332
|
// Send only the fee portion as payouts. The remaining balance stays as surplus for cash-outs.
|
|
333
333
|
// Wrapped in try-catch so the final ruleset is always queued even if payout fails.
|
|
334
|
-
// slither-disable-next-line unused-return
|
|
334
|
+
// slither-disable-next-line unused-return,reentrancy-no-eth
|
|
335
335
|
try _terminal.sendPayoutsOf({
|
|
336
336
|
projectId: gameId,
|
|
337
337
|
token: _token,
|
|
@@ -345,12 +345,14 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
345
345
|
// Payout failed — fee stays in pot. Reset to sentinel (1) so currentGamePotOf
|
|
346
346
|
// doesn't double-count the fee, while preserving the reentrancy guard.
|
|
347
347
|
fulfilledCommitmentsOf[gameId] = 1;
|
|
348
|
+
// slither-disable-next-line reentrancy-events
|
|
348
349
|
emit CommitmentPayoutFailed({gameId: gameId, amount: _feeAmount, reason: reason});
|
|
349
350
|
}
|
|
350
351
|
|
|
351
352
|
// Queue the final ruleset and emit.
|
|
352
353
|
_queueFinalRuleset({gameId: gameId, metadata: _metadata});
|
|
353
354
|
|
|
355
|
+
// slither-disable-next-line reentrancy-events
|
|
354
356
|
emit FulfilledCommitments({gameId: gameId, pot: _pot, caller: msg.sender});
|
|
355
357
|
}
|
|
356
358
|
|
|
@@ -543,6 +545,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
543
545
|
// Add the hook to the registry, contract nonce starts at 1
|
|
544
546
|
REGISTRY.registerAddress({deployer: address(this), nonce: _currentNonce});
|
|
545
547
|
|
|
548
|
+
// slither-disable-next-line reentrancy-events
|
|
546
549
|
emit LaunchGame(gameId, _hook, GOVERNOR, _uriResolver, msg.sender);
|
|
547
550
|
}
|
|
548
551
|
|
|
@@ -619,6 +622,7 @@ contract DefifaDeployer is IDefifaDeployer, IDefifaGamePhaseReporter, IDefifaGam
|
|
|
619
622
|
projectId: gameId, rulesetConfigurations: rulesetConfigs, memo: "Defifa game: no contest."
|
|
620
623
|
});
|
|
621
624
|
|
|
625
|
+
// slither-disable-next-line reentrancy-events
|
|
622
626
|
emit QueuedNoContest(gameId, msg.sender);
|
|
623
627
|
}
|
|
624
628
|
|
package/src/DefifaGovernor.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.26;
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
3
|
|
|
4
4
|
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
5
5
|
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
@@ -174,6 +174,7 @@ contract DefifaGovernor is Ownable, IDefifaGovernor {
|
|
|
174
174
|
// handles sendPayoutsOf failures, ensuring the final ruleset is always queued.
|
|
175
175
|
IDefifaDeployer(CONTROLLER.PROJECTS().ownerOf(gameId)).fulfillCommitmentsOf(gameId);
|
|
176
176
|
|
|
177
|
+
// slither-disable-next-line reentrancy-events
|
|
177
178
|
emit ScorecardRatified(gameId, scorecardId, msg.sender);
|
|
178
179
|
}
|
|
179
180
|
|
package/src/DefifaHook.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.26;
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
3
|
|
|
4
4
|
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
|
|
5
5
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
@@ -947,9 +947,10 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
947
947
|
// Decode the metadata.
|
|
948
948
|
(address _attestationDelegate, uint16[] memory _tierIdsToMint) = abi.decode(metadata, (address, uint16[]));
|
|
949
949
|
|
|
950
|
-
// Set the
|
|
950
|
+
// Set the beneficiary as the attestation delegate by default.
|
|
951
951
|
if (_attestationDelegate == address(0)) {
|
|
952
|
-
_attestationDelegate =
|
|
952
|
+
_attestationDelegate =
|
|
953
|
+
defaultAttestationDelegate != address(0) ? defaultAttestationDelegate : context.beneficiary;
|
|
953
954
|
}
|
|
954
955
|
|
|
955
956
|
// Make sure something is being minted.
|
|
@@ -965,18 +966,18 @@ contract DefifaHook is JB721Hook, Ownable, IDefifaHook {
|
|
|
965
966
|
uint256 _tierId = _tierIds[_i];
|
|
966
967
|
|
|
967
968
|
// Get a reference to the old delegate.
|
|
968
|
-
address _oldDelegate = _tierDelegation[context.
|
|
969
|
+
address _oldDelegate = _tierDelegation[context.beneficiary][_tierId];
|
|
969
970
|
|
|
970
971
|
// If there's either a new delegate or old delegate, set delegation and transfer units.
|
|
971
972
|
if (_attestationDelegate != address(0) || _oldDelegate != address(0)) {
|
|
972
973
|
// Switch delegates if needed.
|
|
973
974
|
if (_attestationDelegate != address(0) && _attestationDelegate != _oldDelegate) {
|
|
974
|
-
_delegateTier({_account: context.
|
|
975
|
+
_delegateTier({_account: context.beneficiary, _delegatee: _attestationDelegate, _tierId: _tierId});
|
|
975
976
|
}
|
|
976
977
|
|
|
977
978
|
// Transfer the attestation units.
|
|
978
979
|
_transferTierAttestationUnits({
|
|
979
|
-
_from: address(0), _to: context.
|
|
980
|
+
_from: address(0), _to: context.beneficiary, _tierId: _tierId, _amount: _attestationAmounts[_i]
|
|
980
981
|
});
|
|
981
982
|
}
|
|
982
983
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.26;
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
3
|
|
|
4
4
|
import {IJBPermissions, JBPermissionsData} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
5
5
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
-
pragma solidity 0.8.26;
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
3
|
|
|
4
4
|
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
5
5
|
|
|
@@ -312,6 +312,44 @@ contract DefifaHookRegressions is JBTest, TestBaseWorkflow {
|
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/// @notice Paying for another account mints the NFT to the beneficiary and defaults attestation power to the
|
|
316
|
+
/// beneficiary (not the payer) when no explicit delegate is provided.
|
|
317
|
+
/// @dev When the metadata leaves the attestation delegate as address(0) and defaultAttestationDelegate is unset,
|
|
318
|
+
/// the source code falls back to context.beneficiary. This proves that NFT ownership and attestation power
|
|
319
|
+
/// both land on the beneficiary by default, and the payer retains neither.
|
|
320
|
+
function test_attestationUnitsFollowBeneficiaryByDefault() public {
|
|
321
|
+
DefifaLaunchProjectData memory defifaData = _getBasicLaunchData(2);
|
|
322
|
+
(uint256 projectId, DefifaHook nft, DefifaGovernor _governor) = _createProject(defifaData);
|
|
323
|
+
|
|
324
|
+
address payer = address(bytes20(keccak256("payer")));
|
|
325
|
+
address beneficiary = address(bytes20(keccak256("beneficiary")));
|
|
326
|
+
|
|
327
|
+
// Phase 1: Mint.
|
|
328
|
+
vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
|
|
329
|
+
|
|
330
|
+
vm.deal(payer, 1 ether);
|
|
331
|
+
|
|
332
|
+
uint16[] memory rawMetadata = new uint16[](1);
|
|
333
|
+
rawMetadata[0] = 1;
|
|
334
|
+
bytes memory metadata = _buildPayMetadata(abi.encode(address(0), rawMetadata));
|
|
335
|
+
|
|
336
|
+
vm.prank(payer);
|
|
337
|
+
jbMultiTerminal().pay{value: 1 ether}(
|
|
338
|
+
projectId, JBConstants.NATIVE_TOKEN, 1 ether, beneficiary, 0, "", metadata
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
assertEq(nft.balanceOf(beneficiary), 1, "beneficiary should receive the NFT");
|
|
342
|
+
assertEq(nft.balanceOf(payer), 0, "payer should not receive the NFT");
|
|
343
|
+
|
|
344
|
+
vm.warp(_tsReader.timestamp() + 1);
|
|
345
|
+
|
|
346
|
+
uint256 payerWeight = _governor.getAttestationWeight(projectId, payer, uint48(block.timestamp));
|
|
347
|
+
uint256 beneficiaryWeight = _governor.getAttestationWeight(projectId, beneficiary, uint48(block.timestamp));
|
|
348
|
+
|
|
349
|
+
assertEq(payerWeight, 0, "payer receives no attestation power when delegate defaults to beneficiary");
|
|
350
|
+
assertGt(beneficiaryWeight, 0, "beneficiary receives the default attestation power");
|
|
351
|
+
}
|
|
352
|
+
|
|
315
353
|
// ----- Internal helpers ------
|
|
316
354
|
|
|
317
355
|
function _getBasicLaunchData(uint8 nTiers) internal returns (DefifaLaunchProjectData memory) {
|
package/test/DefifaUSDC.t.sol
CHANGED
package/test/Fork.t.sol
CHANGED
package/test/SVG.t.sol
CHANGED
package/test/TestAuditGaps.sol
CHANGED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
|
+
|
|
4
|
+
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
5
|
+
|
|
6
|
+
import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
|
|
7
|
+
import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
|
|
8
|
+
import {DefifaHook} from "../../src/DefifaHook.sol";
|
|
9
|
+
import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
|
|
10
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
11
|
+
|
|
12
|
+
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
13
|
+
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
14
|
+
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
15
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
16
|
+
|
|
17
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
18
|
+
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
19
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
20
|
+
import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
|
|
21
|
+
import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
|
|
22
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
23
|
+
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
24
|
+
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
25
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
26
|
+
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
27
|
+
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
28
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
29
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
30
|
+
import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
31
|
+
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
|
|
32
|
+
|
|
33
|
+
contract CodexAttestationDoubleCount is JBTest, TestBaseWorkflow {
|
|
34
|
+
using JBRulesetMetadataResolver for JBRuleset;
|
|
35
|
+
|
|
36
|
+
uint256 internal _protocolFeeProjectId;
|
|
37
|
+
uint256 internal _defifaProjectId;
|
|
38
|
+
|
|
39
|
+
DefifaDeployer internal deployer;
|
|
40
|
+
DefifaHook internal hook;
|
|
41
|
+
DefifaGovernor internal governor;
|
|
42
|
+
|
|
43
|
+
uint256 internal _mintPhaseStart;
|
|
44
|
+
|
|
45
|
+
function setUp() public virtual override {
|
|
46
|
+
super.setUp();
|
|
47
|
+
|
|
48
|
+
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
49
|
+
_tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
50
|
+
|
|
51
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
52
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
|
|
53
|
+
|
|
54
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
55
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
56
|
+
mustStartAtOrAfter: 0,
|
|
57
|
+
duration: 10 days,
|
|
58
|
+
weight: 1e18,
|
|
59
|
+
weightCutPercent: 0,
|
|
60
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
61
|
+
metadata: JBRulesetMetadata({
|
|
62
|
+
reservedPercent: 0,
|
|
63
|
+
cashOutTaxRate: 0,
|
|
64
|
+
baseCurrency: JBCurrencyIds.ETH,
|
|
65
|
+
pausePay: false,
|
|
66
|
+
pauseCreditTransfers: false,
|
|
67
|
+
allowOwnerMinting: false,
|
|
68
|
+
allowSetCustomToken: false,
|
|
69
|
+
allowTerminalMigration: false,
|
|
70
|
+
allowSetTerminals: false,
|
|
71
|
+
allowSetController: false,
|
|
72
|
+
allowAddAccountingContext: false,
|
|
73
|
+
allowAddPriceFeed: false,
|
|
74
|
+
ownerMustSendPayouts: false,
|
|
75
|
+
holdFees: false,
|
|
76
|
+
useTotalSurplusForCashOuts: false,
|
|
77
|
+
useDataHookForPay: true,
|
|
78
|
+
useDataHookForCashOut: true,
|
|
79
|
+
dataHook: address(0),
|
|
80
|
+
metadata: 0
|
|
81
|
+
}),
|
|
82
|
+
splitGroups: new JBSplitGroup[](0),
|
|
83
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
87
|
+
|
|
88
|
+
_protocolFeeProjectId =
|
|
89
|
+
jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
90
|
+
vm.prank(projectOwner);
|
|
91
|
+
address _nanaToken =
|
|
92
|
+
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
93
|
+
|
|
94
|
+
_defifaProjectId =
|
|
95
|
+
jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
|
|
96
|
+
vm.prank(projectOwner);
|
|
97
|
+
address _defifaToken = address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
98
|
+
|
|
99
|
+
hook = new DefifaHook(jbDirectory(), IERC20(_defifaToken), IERC20(_nanaToken));
|
|
100
|
+
governor = new DefifaGovernor(jbController(), address(this));
|
|
101
|
+
deployer = new DefifaDeployer(
|
|
102
|
+
address(hook),
|
|
103
|
+
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
104
|
+
governor,
|
|
105
|
+
jbController(),
|
|
106
|
+
new JBAddressRegistry(),
|
|
107
|
+
_defifaProjectId,
|
|
108
|
+
_protocolFeeProjectId
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
hook.transferOwnership(address(deployer));
|
|
112
|
+
governor.transferOwnership(address(deployer));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function test_attestationUnitsDuplicateAfterBeneficiaryTransfer() external {
|
|
116
|
+
address payer = address(bytes20(keccak256("payer")));
|
|
117
|
+
address beneficiary = address(bytes20(keccak256("beneficiary")));
|
|
118
|
+
address recipient = address(bytes20(keccak256("recipient")));
|
|
119
|
+
|
|
120
|
+
(uint256 projectId, DefifaHook nft) = _launchGame();
|
|
121
|
+
vm.warp(_mintPhaseStart);
|
|
122
|
+
|
|
123
|
+
vm.deal(payer, 1 ether);
|
|
124
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
125
|
+
tierIds[0] = 1;
|
|
126
|
+
bytes memory metadata = _buildPayMetadata(abi.encode(address(0), tierIds));
|
|
127
|
+
|
|
128
|
+
vm.prank(payer);
|
|
129
|
+
jbMultiTerminal().pay{value: 1 ether}({
|
|
130
|
+
projectId: projectId,
|
|
131
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
132
|
+
amount: 1 ether,
|
|
133
|
+
beneficiary: beneficiary,
|
|
134
|
+
minReturnedTokens: 0,
|
|
135
|
+
memo: "",
|
|
136
|
+
metadata: metadata
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
uint256 totalUnitsBefore = nft.getTierTotalAttestationUnitsOf(1);
|
|
140
|
+
uint256 beneficiaryUnitsBefore = nft.getTierAttestationUnitsOf(beneficiary, 1);
|
|
141
|
+
assertGt(totalUnitsBefore, 0, "tier should have nonzero attestation units");
|
|
142
|
+
assertEq(beneficiaryUnitsBefore, totalUnitsBefore, "beneficiary receives delegated units after mint");
|
|
143
|
+
|
|
144
|
+
uint256 tokenId = 1_000_000_001;
|
|
145
|
+
vm.prank(beneficiary);
|
|
146
|
+
nft.transferFrom(beneficiary, recipient, tokenId);
|
|
147
|
+
|
|
148
|
+
uint256 beneficiaryUnitsAfter = nft.getTierAttestationUnitsOf(beneficiary, 1);
|
|
149
|
+
uint256 recipientUnitsAfter = nft.getTierAttestationUnitsOf(recipient, 1);
|
|
150
|
+
uint256 totalUnitsAfter = nft.getTierTotalAttestationUnitsOf(1);
|
|
151
|
+
|
|
152
|
+
// After the fix: attestation units go to beneficiary on mint, then move to recipient on transfer.
|
|
153
|
+
// Total units stay constant, beneficiary loses units, recipient gains them.
|
|
154
|
+
assertEq(totalUnitsAfter, totalUnitsBefore, "total tier units stay constant");
|
|
155
|
+
assertEq(beneficiaryUnitsAfter, 0, "beneficiary loses attestation units after transferring NFT");
|
|
156
|
+
assertEq(recipientUnitsAfter, totalUnitsAfter, "recipient receives full attestation units from transfer");
|
|
157
|
+
// No double-counting: sum of individual units equals total.
|
|
158
|
+
assertEq(
|
|
159
|
+
beneficiaryUnitsAfter + recipientUnitsAfter,
|
|
160
|
+
totalUnitsAfter,
|
|
161
|
+
"no double-counting: sum of individual units equals total"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function _launchGame() internal returns (uint256 projectId, DefifaHook nft) {
|
|
166
|
+
DefifaTierParams[] memory tierParams = new DefifaTierParams[](2);
|
|
167
|
+
for (uint256 i = 0; i < 2; i++) {
|
|
168
|
+
tierParams[i] = DefifaTierParams({
|
|
169
|
+
reservedRate: 1001,
|
|
170
|
+
reservedTokenBeneficiary: address(0),
|
|
171
|
+
encodedIPFSUri: bytes32(0),
|
|
172
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
173
|
+
name: "DEFIFA"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
DefifaLaunchProjectData memory d = DefifaLaunchProjectData({
|
|
178
|
+
name: "DEFIFA",
|
|
179
|
+
projectUri: "",
|
|
180
|
+
contractUri: "",
|
|
181
|
+
baseUri: "",
|
|
182
|
+
tierPrice: 1 ether,
|
|
183
|
+
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
184
|
+
mintPeriodDuration: 1 days,
|
|
185
|
+
start: uint48(block.timestamp + 3 days),
|
|
186
|
+
refundPeriodDuration: 1 days,
|
|
187
|
+
store: new JB721TiersHookStore(),
|
|
188
|
+
splits: new JBSplit[](0),
|
|
189
|
+
attestationStartTime: 0,
|
|
190
|
+
attestationGracePeriod: 100_381,
|
|
191
|
+
defaultAttestationDelegate: address(0),
|
|
192
|
+
tiers: tierParams,
|
|
193
|
+
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
194
|
+
terminal: jbMultiTerminal(),
|
|
195
|
+
minParticipation: 0,
|
|
196
|
+
scorecardTimeout: 0
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
_mintPhaseStart = d.start - d.mintPeriodDuration - d.refundPeriodDuration;
|
|
200
|
+
|
|
201
|
+
projectId = deployer.launchGameWith(d);
|
|
202
|
+
|
|
203
|
+
JBRuleset memory fc = jbRulesets().currentOf(projectId);
|
|
204
|
+
if (fc.dataHook() == address(0)) {
|
|
205
|
+
(fc,) = jbRulesets().latestQueuedOf(projectId);
|
|
206
|
+
}
|
|
207
|
+
nft = DefifaHook(fc.dataHook());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function _buildPayMetadata(bytes memory metadata) internal view returns (bytes memory) {
|
|
211
|
+
bytes[] memory data = new bytes[](1);
|
|
212
|
+
data[0] = metadata;
|
|
213
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
214
|
+
ids[0] = metadataHelper().getId("pay", address(hook));
|
|
215
|
+
return metadataHelper().createMetadata(ids, data);
|
|
216
|
+
}
|
|
217
|
+
}
|
package/test/deployScript.t.sol
CHANGED