@bananapus/suckers-v6 0.0.20 → 0.0.21
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 +36 -5
- package/ARCHITECTURE.md +42 -103
- package/AUDIT_INSTRUCTIONS.md +116 -386
- package/CHANGELOG.md +72 -0
- package/README.md +87 -415
- package/RISKS.md +22 -5
- package/SKILLS.md +29 -250
- package/STYLE_GUIDE.md +58 -21
- package/USER_JOURNEYS.md +47 -311
- package/package.json +2 -3
- package/references/operations.md +25 -0
- package/references/runtime.md +28 -0
- package/script/Deploy.s.sol +7 -7
- package/src/JBCeloSucker.sol +4 -2
- package/src/JBSucker.sol +4 -4
- package/src/JBSuckerRegistry.sol +5 -1
- package/src/interfaces/IJBSuckerRegistry.sol +2 -0
- package/src/libraries/ARBAddresses.sol +1 -1
- package/src/libraries/ARBChains.sol +1 -1
- package/test/audit/codex-ToRemoteFeeIrrecoverable.t.sol +238 -0
- package/CHANGE_LOG.md +0 -484
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
+
import "forge-std/Test.sol";
|
|
6
|
+
|
|
7
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
8
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
9
|
+
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
10
|
+
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
11
|
+
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
12
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
13
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
14
|
+
import {LibClone} from "solady/src/utils/LibClone.sol";
|
|
15
|
+
|
|
16
|
+
import "../../src/JBSucker.sol";
|
|
17
|
+
import "../../src/interfaces/IJBSuckerRegistry.sol";
|
|
18
|
+
import "../../src/structs/JBClaim.sol";
|
|
19
|
+
import "../../src/structs/JBInboxTreeRoot.sol";
|
|
20
|
+
import "../../src/structs/JBLeaf.sol";
|
|
21
|
+
import "../../src/structs/JBMessageRoot.sol";
|
|
22
|
+
import "../../src/structs/JBRemoteToken.sol";
|
|
23
|
+
import "../../src/utils/MerkleLib.sol";
|
|
24
|
+
|
|
25
|
+
contract CodexFeeIrrecoverableHarness is JBSucker {
|
|
26
|
+
using MerkleLib for MerkleLib.Tree;
|
|
27
|
+
using BitMaps for BitMaps.BitMap;
|
|
28
|
+
|
|
29
|
+
bool internal _skipNextProofCheck;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
IJBDirectory directory,
|
|
33
|
+
IJBPermissions permissions,
|
|
34
|
+
IJBTokens tokens,
|
|
35
|
+
IJBSuckerRegistry registry
|
|
36
|
+
)
|
|
37
|
+
JBSucker(directory, permissions, tokens, 1, registry, address(0))
|
|
38
|
+
{}
|
|
39
|
+
|
|
40
|
+
function peerChainId() external view override returns (uint256) {
|
|
41
|
+
return block.chainid;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _isRemotePeer(address sender) internal view override returns (bool) {
|
|
45
|
+
return sender == _toAddress(peer());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function _sendRootOverAMB(
|
|
49
|
+
uint256,
|
|
50
|
+
uint256,
|
|
51
|
+
address,
|
|
52
|
+
uint256,
|
|
53
|
+
JBRemoteToken memory,
|
|
54
|
+
JBMessageRoot memory
|
|
55
|
+
)
|
|
56
|
+
internal
|
|
57
|
+
override
|
|
58
|
+
{}
|
|
59
|
+
|
|
60
|
+
function _validateBranchRoot(
|
|
61
|
+
bytes32 expectedRoot,
|
|
62
|
+
uint256 projectTokenCount,
|
|
63
|
+
uint256 terminalTokenAmount,
|
|
64
|
+
bytes32 beneficiary,
|
|
65
|
+
uint256 index,
|
|
66
|
+
bytes32[_TREE_DEPTH] calldata leaves
|
|
67
|
+
)
|
|
68
|
+
internal
|
|
69
|
+
override
|
|
70
|
+
{
|
|
71
|
+
if (_skipNextProofCheck) {
|
|
72
|
+
_skipNextProofCheck = false;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
super._validateBranchRoot(expectedRoot, projectTokenCount, terminalTokenAmount, beneficiary, index, leaves);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function test_setRemoteToken(address token, JBRemoteToken memory remoteToken) external {
|
|
80
|
+
_remoteTokenFor[token] = remoteToken;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function test_insertIntoTree(
|
|
84
|
+
uint256 projectTokenCount,
|
|
85
|
+
address token,
|
|
86
|
+
uint256 terminalTokenAmount,
|
|
87
|
+
bytes32 beneficiary
|
|
88
|
+
)
|
|
89
|
+
external
|
|
90
|
+
{
|
|
91
|
+
_insertIntoTree(projectTokenCount, token, terminalTokenAmount, beneficiary);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function test_setInboxRoot(address token, uint64 nonce, bytes32 root) external {
|
|
95
|
+
_inboxOf[token] = JBInboxTreeRoot({nonce: nonce, root: root});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function test_skipNextProofCheck() external {
|
|
99
|
+
_skipNextProofCheck = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
contract CodexTerminalStub {
|
|
104
|
+
uint256 public totalReceived;
|
|
105
|
+
|
|
106
|
+
function addToBalanceOf(uint256, address, uint256 amount, bool, string calldata, bytes calldata) external payable {
|
|
107
|
+
totalReceived += amount;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
contract CodexToRemoteFeeIrrecoverableTest is Test {
|
|
112
|
+
address internal constant DIRECTORY = address(0x1000);
|
|
113
|
+
address internal constant PERMISSIONS = address(0x2000);
|
|
114
|
+
address internal constant TOKENS = address(0x3000);
|
|
115
|
+
address internal constant REGISTRY = address(0x4000);
|
|
116
|
+
address internal constant PROJECT = address(0x5000);
|
|
117
|
+
address internal constant CONTROLLER = address(0x6000);
|
|
118
|
+
address internal constant TERMINAL = address(0x7000);
|
|
119
|
+
|
|
120
|
+
uint256 internal constant PROJECT_ID = 2;
|
|
121
|
+
uint256 internal constant FEE = 1;
|
|
122
|
+
|
|
123
|
+
CodexFeeIrrecoverableHarness internal sucker;
|
|
124
|
+
CodexTerminalStub internal terminal;
|
|
125
|
+
|
|
126
|
+
function setUp() public {
|
|
127
|
+
terminal = new CodexTerminalStub();
|
|
128
|
+
|
|
129
|
+
CodexFeeIrrecoverableHarness singleton = new CodexFeeIrrecoverableHarness({
|
|
130
|
+
directory: IJBDirectory(DIRECTORY),
|
|
131
|
+
permissions: IJBPermissions(PERMISSIONS),
|
|
132
|
+
tokens: IJBTokens(TOKENS),
|
|
133
|
+
registry: IJBSuckerRegistry(REGISTRY)
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
sucker = CodexFeeIrrecoverableHarness(
|
|
137
|
+
payable(address(LibClone.cloneDeterministic(address(singleton), bytes32("codex-fee-stuck"))))
|
|
138
|
+
);
|
|
139
|
+
sucker.initialize(PROJECT_ID);
|
|
140
|
+
|
|
141
|
+
vm.mockCall(DIRECTORY, abi.encodeCall(IJBDirectory.PROJECTS, ()), abi.encode(PROJECT));
|
|
142
|
+
vm.mockCall(PROJECT, abi.encodeCall(IERC721.ownerOf, (PROJECT_ID)), abi.encode(address(this)));
|
|
143
|
+
vm.mockCall(DIRECTORY, abi.encodeCall(IJBDirectory.controllerOf, (PROJECT_ID)), abi.encode(CONTROLLER));
|
|
144
|
+
vm.mockCall(
|
|
145
|
+
DIRECTORY,
|
|
146
|
+
abi.encodeCall(IJBDirectory.primaryTerminalOf, (PROJECT_ID, JBConstants.NATIVE_TOKEN)),
|
|
147
|
+
abi.encode(IJBTerminal(address(terminal)))
|
|
148
|
+
);
|
|
149
|
+
vm.mockCall(
|
|
150
|
+
CONTROLLER,
|
|
151
|
+
abi.encodeCall(IJBController.mintTokensOf, (PROJECT_ID, 1, address(0xBEEF), "", false)),
|
|
152
|
+
abi.encode(uint256(1))
|
|
153
|
+
);
|
|
154
|
+
vm.mockCall(REGISTRY, abi.encodeCall(IJBSuckerRegistry.toRemoteFee, ()), abi.encode(FEE));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function test_feeEthRemainsStuckAfterLaterNativeClaim() external {
|
|
158
|
+
sucker.test_setRemoteToken(
|
|
159
|
+
JBConstants.NATIVE_TOKEN,
|
|
160
|
+
JBRemoteToken({
|
|
161
|
+
addr: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))),
|
|
162
|
+
enabled: true,
|
|
163
|
+
emergencyHatch: false,
|
|
164
|
+
minGas: 200_000
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
sucker.test_insertIntoTree({
|
|
169
|
+
projectTokenCount: 0,
|
|
170
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
171
|
+
terminalTokenAmount: 0,
|
|
172
|
+
beneficiary: bytes32(uint256(uint160(address(0xABCD))))
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
vm.mockCall(
|
|
176
|
+
DIRECTORY,
|
|
177
|
+
abi.encodeCall(IJBDirectory.primaryTerminalOf, (1, JBConstants.NATIVE_TOKEN)),
|
|
178
|
+
abi.encode(IJBTerminal(address(0)))
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
sucker.toRemote{value: FEE}(JBConstants.NATIVE_TOKEN);
|
|
182
|
+
assertEq(address(sucker).balance, FEE, "fee should remain in the sucker after failed fee payment");
|
|
183
|
+
|
|
184
|
+
vm.deal(address(sucker), FEE + 1);
|
|
185
|
+
sucker.test_setInboxRoot(JBConstants.NATIVE_TOKEN, 1, bytes32(uint256(1)));
|
|
186
|
+
sucker.test_skipNextProofCheck();
|
|
187
|
+
|
|
188
|
+
sucker.claim(
|
|
189
|
+
JBClaim({
|
|
190
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
191
|
+
leaf: JBLeaf({
|
|
192
|
+
index: 0,
|
|
193
|
+
beneficiary: bytes32(uint256(uint160(address(0xBEEF)))),
|
|
194
|
+
projectTokenCount: 1,
|
|
195
|
+
terminalTokenAmount: 1
|
|
196
|
+
}),
|
|
197
|
+
proof: [
|
|
198
|
+
bytes32(0),
|
|
199
|
+
bytes32(0),
|
|
200
|
+
bytes32(0),
|
|
201
|
+
bytes32(0),
|
|
202
|
+
bytes32(0),
|
|
203
|
+
bytes32(0),
|
|
204
|
+
bytes32(0),
|
|
205
|
+
bytes32(0),
|
|
206
|
+
bytes32(0),
|
|
207
|
+
bytes32(0),
|
|
208
|
+
bytes32(0),
|
|
209
|
+
bytes32(0),
|
|
210
|
+
bytes32(0),
|
|
211
|
+
bytes32(0),
|
|
212
|
+
bytes32(0),
|
|
213
|
+
bytes32(0),
|
|
214
|
+
bytes32(0),
|
|
215
|
+
bytes32(0),
|
|
216
|
+
bytes32(0),
|
|
217
|
+
bytes32(0),
|
|
218
|
+
bytes32(0),
|
|
219
|
+
bytes32(0),
|
|
220
|
+
bytes32(0),
|
|
221
|
+
bytes32(0),
|
|
222
|
+
bytes32(0),
|
|
223
|
+
bytes32(0),
|
|
224
|
+
bytes32(0),
|
|
225
|
+
bytes32(0),
|
|
226
|
+
bytes32(0),
|
|
227
|
+
bytes32(0),
|
|
228
|
+
bytes32(0),
|
|
229
|
+
bytes32(0)
|
|
230
|
+
]
|
|
231
|
+
})
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
assertEq(address(sucker).balance, FEE, "later native claims do not sweep the retained fee ETH");
|
|
235
|
+
assertEq(address(terminal).balance, 1, "claim should forward only the leaf amount");
|
|
236
|
+
assertEq(sucker.amountToAddToBalanceOf(JBConstants.NATIVE_TOKEN), FEE, "fee ETH stays addable but unreachable");
|
|
237
|
+
}
|
|
238
|
+
}
|