diff --git a/src/OriginKeyToken.sol b/src/OriginKeyToken.sol index dc32a3f..a418401 100644 --- a/src/OriginKeyToken.sol +++ b/src/OriginKeyToken.sol @@ -144,8 +144,7 @@ contract OriginKeyToken is ReentrancyGuard { // ─── Vault registrar ────────────────────────────────────────────────────── address public immutable vaultRegistrar; - mapping(address => bytes32) public vaultRegistry; - mapping(address => bool) public isVault; + mapping(address => bytes32) public vaultRegistry; // bytes32(0) means no vault mapping(address => bool) public vaultHasBeenSwept; mapping(address => uint256) public vaultOrdinal; mapping(address => bool) public vaultHasOrdinal; @@ -222,7 +221,7 @@ contract OriginKeyToken is ReentrancyGuard { // ─── Vault sweep check ─────────────────────────────────────────────────── function _checkVaultSweep(address vault, uint256 amount) internal { - if (isVault[vault] && !vaultHasBeenSwept[vault]) { + if (vaultRegistry[vault] != bytes32(0) && !vaultHasBeenSwept[vault]) { vaultHasBeenSwept[vault] = true; emit VaultSwept(vault, vaultRegistry[vault], amount, block.timestamp); } @@ -230,14 +229,13 @@ contract OriginKeyToken is ReentrancyGuard { // ─── Buy ────────────────────────────────────────────────────────────────── // PITcoin exact — fee returned to first buyer, distributed to holders after - function buy(uint256 cbbtcAmount, uint256 minTokens) external nonReentrant { + function buy(uint256 cbbtcAmount) external nonReentrant { require(cbbtcAmount >= MIN_SATS, "Minimum 100 sats"); require(cbbtcAmount <= MAX_BUY, "Maximum 1,000,000 sats per buy"); CBBTC.safeTransferFrom(msg.sender, address(this), cbbtcAmount); uint256 fee = (cbbtcAmount * BUY_FEE) / 100; uint256 tokens = cbbtcAmount - fee; - require(tokens >= minTokens, "Slippage: too few tokens"); if (totalSupply > 0) { _distributeFee(fee); @@ -275,7 +273,7 @@ contract OriginKeyToken is ReentrancyGuard { // DO NOT change signedSub to signedAdd — that breaks dividend distribution // for any wallet that sells and rebuys. PITcoin uses signedSub. Always. // - function sell(uint256 tokens, uint256 minCbbtc) external nonReentrant { + function sell(uint256 tokens) external nonReentrant { require(tokens >= MIN_SELL, "Minimum 100 sats to sell"); require(balanceOf[msg.sender] >= tokens, "Insufficient balance"); require(totalSupply > tokens, "Cannot sell entire supply"); @@ -284,7 +282,6 @@ contract OriginKeyToken is ReentrancyGuard { uint256 fee = (tokens * SELL_FEE) / 100; uint256 taxed = tokens - fee; - require(taxed >= minCbbtc, "Slippage: too little cbBTC"); // 1. Burn tokens balanceOf[msg.sender] -= tokens; @@ -372,7 +369,7 @@ contract OriginKeyToken is ReentrancyGuard { require(vault != address(0), "Vault: zero address"); require(assetId != bytes32(0), "Vault: empty asset ID"); require(cbbtcAmount >= MIN_SATS, "Vault: minimum 100 sats"); - require(!isVault[vault], "Vault: already registered"); + require(vaultRegistry[vault] == bytes32(0), "Vault: already registered"); CBBTC.safeTransferFrom(msg.sender, address(this), cbbtcAmount); @@ -398,7 +395,6 @@ contract OriginKeyToken is ReentrancyGuard { // Register vault vaultRegistry[vault] = assetId; - isVault[vault] = true; vaultHasOrdinal[vault] = (ordinalNumber > 0); if (ordinalNumber > 0) { @@ -460,7 +456,7 @@ contract OriginKeyToken is ReentrancyGuard { bytes32 assetId ) { return ( - isVault[vault], + vaultRegistry[vault] != bytes32(0), vaultHasBeenSwept[vault], balanceOf[vault], vaultRegistry[vault] diff --git a/test/OKT.audit.t.sol b/test/OKT.audit.t.sol index e528084..aa03bec 100644 --- a/test/OKT.audit.t.sol +++ b/test/OKT.audit.t.sol @@ -29,7 +29,7 @@ contract ReentrancyAttacker { function attack(uint256 amount) external { cbbtc.approve(address(okt), type(uint256).max); - okt.buy(amount, 0); + okt.buy(amount); } // Try to reenter on cbBTC transfer callback @@ -71,7 +71,7 @@ contract OKTAuditTest is Test { // ═══════════════════════════════════════════════════════════════════════════ function test_reentrancyOnWithdraw() public { - vm.prank(alice); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); ReentrancyAttacker attacker = new ReentrancyAttacker(okt, cbbtc); cbbtc.mint(address(attacker), 100_000); @@ -79,7 +79,7 @@ contract OKTAuditTest is Test { attacker.attack(10_000); // Generate dividends for attacker - vm.prank(bob); okt.buy(100_000, 0); + vm.prank(bob); okt.buy(100_000); // Attacker tries to reenter — ReentrancyGuard should block uint256 contractBefore = cbbtc.balanceOf(address(okt)); @@ -95,20 +95,20 @@ contract OKTAuditTest is Test { // Sandwich attack simulation — buy before, sell after a large trade function test_sandwichAttackUnprofitable() public { - vm.prank(alice); okt.buy(1_000_000, 0); // seed + vm.prank(alice); okt.buy(1_000_000); // seed uint256 attackerBefore = cbbtc.balanceOf(bob); // Attacker front-runs with buy - vm.prank(bob); okt.buy(100_000, 0); + vm.prank(bob); okt.buy(100_000); // Victim makes large buy - vm.prank(carol); okt.buy(1_000_000, 0); + vm.prank(carol); okt.buy(1_000_000); // Attacker back-runs with sell uint256 bobBal = okt.balanceOf(bob); if (bobBal > 1 && okt.totalSupply() > bobBal) { - vm.prank(bob); okt.sell(bobBal - 1, 0); + vm.prank(bob); okt.sell(bobBal - 1); } uint256 bobDivs = okt.dividendsOf(bob); if (bobDivs > 0) { @@ -123,17 +123,17 @@ contract OKTAuditTest is Test { // Flash loan simulation — massive buy/sell in same context function test_flashLoanAttackUnprofitable() public { - vm.prank(alice); okt.buy(1_000_000, 0); // seed + vm.prank(alice); okt.buy(1_000_000); // seed uint256 attackerBefore = cbbtc.balanceOf(bob); // Simulate flash loan — buy massive amount - vm.prank(bob); okt.buy(1_000_000, 0); + vm.prank(bob); okt.buy(1_000_000); // Immediately sell uint256 bobBal = okt.balanceOf(bob); if (bobBal > 1 && okt.totalSupply() > bobBal) { - vm.prank(bob); okt.sell(bobBal - 1, 0); + vm.prank(bob); okt.sell(bobBal - 1); } // Collect any dividends @@ -153,15 +153,15 @@ contract OKTAuditTest is Test { // Attacker buys massive amount to dominate supply // Whale buys max 10 times for (uint i = 0; i < 10; i++) { - vm.prank(bob); okt.buy(1_000_000, 0); + vm.prank(bob); okt.buy(1_000_000); } // Other users should still be able to buy - vm.prank(alice); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); assertGt(okt.balanceOf(alice), 0, "Alice should still be able to buy"); // Larger buy to generate meaningful dividends - vm.prank(carol); okt.buy(1_000_000, 0); + vm.prank(carol); okt.buy(1_000_000); uint256 aliceDivs = okt.dividendsOf(alice); // Alice holds ~0.2% of supply so gets ~0.2% of 70,000 fee = ~140 sats assertGt(aliceDivs, 0, "Alice should earn dividends even with whale"); @@ -173,17 +173,17 @@ contract OKTAuditTest is Test { function test_maxBuyDoesNotOverflow() public { // Buy with max amount — should work cleanly - vm.prank(alice); okt.buy(1_000_000, 0); + vm.prank(alice); okt.buy(1_000_000); assertGt(okt.balanceOf(alice), 0, "Max buy should work"); assertGt(okt.totalSupply(), 0, "Supply should increase"); } function test_profitPerTokenDoesNotOverflow() public { // Small supply, large fee — worst case for overflow - vm.prank(alice); okt.buy(100, 0); // minimum buy, first buyer gets 100 + vm.prank(alice); okt.buy(100); // minimum buy, first buyer gets 100 // Large buy generates large fee distributed to small supply - vm.prank(bob); okt.buy(1_000_000, 0); + vm.prank(bob); okt.buy(1_000_000); // profitPerToken should be very large but not overflow uint256 ppt = okt.profitPerToken(); @@ -204,19 +204,19 @@ contract OKTAuditTest is Test { uint256 totalOut = 0; // Track all cbBTC entering contract - vm.prank(alice); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); totalIn += 100_000; - vm.prank(bob); okt.buy(200_000, 0); + vm.prank(bob); okt.buy(200_000); totalIn += 200_000; - vm.prank(carol); okt.buy(50_000, 0); + vm.prank(carol); okt.buy(50_000); totalIn += 50_000; // Track all cbBTC leaving contract uint256 bobBefore = cbbtc.balanceOf(bob); uint256 bobBal = okt.balanceOf(bob); - vm.prank(bob); okt.sell(bobBal - 1, 0); + vm.prank(bob); okt.sell(bobBal - 1); uint256 bobDivs = okt.dividendsOf(bob); if (bobDivs > 0) { vm.prank(bob); okt.withdraw(); @@ -246,16 +246,16 @@ contract OKTAuditTest is Test { // ═══════════════════════════════════════════════════════════════════════════ function test_gasConsistentAfterManyTransactions() public { - vm.prank(alice); okt.buy(1_000_000, 0); + vm.prank(alice); okt.buy(1_000_000); // Do 100 transactions to grow state for (uint i = 0; i < 100; i++) { - vm.prank(bob); okt.buy(1_000, 0); + vm.prank(bob); okt.buy(1_000); } // Measure gas for a buy after 100 transactions uint256 gasBefore = gasleft(); - vm.prank(carol); okt.buy(1_000, 0); + vm.prank(carol); okt.buy(1_000); uint256 gasUsed = gasBefore - gasleft(); // Gas should be reasonable — under 200k @@ -263,15 +263,15 @@ contract OKTAuditTest is Test { } function test_gasConsistentForSellAfterManyTransactions() public { - vm.prank(alice); okt.buy(1_000_000, 0); + vm.prank(alice); okt.buy(1_000_000); for (uint i = 0; i < 100; i++) { - vm.prank(bob); okt.buy(1_000, 0); + vm.prank(bob); okt.buy(1_000); } uint256 bobBal = okt.balanceOf(bob); uint256 gasBefore = gasleft(); - vm.prank(bob); okt.sell(bobBal - 1, 0); + vm.prank(bob); okt.sell(bobBal - 1); uint256 gasUsed = gasBefore - gasleft(); assertLt(gasUsed, 200_000, "Sell gas cost grew unreasonably"); @@ -306,26 +306,26 @@ contract OKTAuditTest is Test { // ═══════════════════════════════════════════════════════════════════════════ function test_buyExactMinimum() public { - vm.prank(alice); okt.buy(100, 0); + vm.prank(alice); okt.buy(100); assertEq(okt.balanceOf(alice), 100); // first buyer gets fee back } function test_buyAboveMaxReverts() public { vm.prank(alice); vm.expectRevert("Maximum 1,000,000 sats per buy"); - okt.buy(1_000_001, 0); + okt.buy(1_000_001); } function test_buyExactMaxWorks() public { - vm.prank(alice); okt.buy(100, 0); // seed first buyer - vm.prank(bob); okt.buy(1_000_000, 0); + vm.prank(alice); okt.buy(100); // seed first buyer + vm.prank(bob); okt.buy(1_000_000); assertGt(okt.balanceOf(bob), 0, "Max buy should work"); } function test_buyBelowMinimumReverts() public { vm.prank(alice); vm.expectRevert("Minimum 100 sats"); - okt.buy(99, 0); + okt.buy(99); } function test_inscribeBelowMinimumReverts() public { @@ -336,21 +336,21 @@ contract OKTAuditTest is Test { // sellOneToken removed — replaced by test_sellBelowMinimumReverts and test_sellMinimumChargesFee function test_sellBelowMinimumReverts() public { - vm.prank(alice); okt.buy(10_000, 0); - vm.prank(bob); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); + vm.prank(bob); okt.buy(10_000); vm.prank(bob); vm.expectRevert("Minimum 100 sats to sell"); - okt.sell(99, 0); + okt.sell(99); } function test_sellMinimumChargesFee() public { - vm.prank(alice); okt.buy(10_000, 0); - vm.prank(bob); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); + vm.prank(bob); okt.buy(10_000); uint256 bobBefore = cbbtc.balanceOf(bob); vm.prank(bob); - okt.sell(100, 0); + okt.sell(100); uint256 bobAfter = cbbtc.balanceOf(bob); assertEq(bobAfter - bobBefore, 93, "Minimum sell should charge 7 sats"); } @@ -371,7 +371,7 @@ contract OKTAuditTest is Test { } function test_zeroTransferReverts() public { - vm.prank(alice); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); vm.prank(alice); vm.expectRevert("Zero tokens"); okt.transfer(bob, 0); @@ -402,7 +402,7 @@ contract OKTAuditTest is Test { okt.inscribe(vault2, bytes32("SMALL"), 10_000, 0, ""); // Generate dividends - vm.prank(alice); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); uint256 vault1Divs = okt.dividendsOf(vault1); uint256 vault2Divs = okt.dividendsOf(vault2); @@ -420,8 +420,8 @@ contract OKTAuditTest is Test { // ═══════════════════════════════════════════════════════════════════════════ function test_transferDoesNotCreateDividends() public { - vm.prank(alice); okt.buy(100_000, 0); - vm.prank(bob); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); + vm.prank(bob); okt.buy(100_000); uint256 contractBefore = cbbtc.balanceOf(address(okt)); @@ -441,8 +441,8 @@ contract OKTAuditTest is Test { } function test_transferPreservesDividends() public { - vm.prank(alice); okt.buy(100_000, 0); - vm.prank(bob); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); + vm.prank(bob); okt.buy(100_000); uint256 aliceDivsBefore = okt.dividendsOf(alice); diff --git a/test/OKT.invariant.t.sol b/test/OKT.invariant.t.sol index 5b8dc31..a17ef50 100644 --- a/test/OKT.invariant.t.sol +++ b/test/OKT.invariant.t.sol @@ -48,7 +48,7 @@ contract OKTHandler is Test { if (cbbtc.balanceOf(actor) < amount) return; vm.prank(actor); - try okt.buy(amount, 0) {} catch {} + try okt.buy(amount) {} catch {} } // ─── Sell ───────────────────────────────────────────────────────────────── @@ -63,7 +63,7 @@ contract OKTHandler is Test { if (okt.totalSupply() <= amount) return; vm.prank(actor); - try okt.sell(amount, 0) {} catch {} + try okt.sell(amount) {} catch {} } // ─── Withdraw ───────────────────────────────────────────────────────────── @@ -130,7 +130,7 @@ contract OKTInvariantTest is Test { // Seed the contract with a first buy so totalSupply > 0 address first = handler.actors(0); vm.prank(first); - okt.buy(10_000, 0); + okt.buy(10_000); // Tell Foundry to only call handler functions targetContract(address(handler)); diff --git a/test/OKT.security.t.sol b/test/OKT.security.t.sol index 08c7243..b75ee22 100644 --- a/test/OKT.security.t.sol +++ b/test/OKT.security.t.sol @@ -48,16 +48,16 @@ contract OKTSecurityTest is Test { function test_dividendDrift_100Cycles() public { // Seed the contract - vm.prank(alice); okt.buy(1_000_000, 0); + vm.prank(alice); okt.buy(1_000_000); uint256 contractBalBefore = cbbtc.balanceOf(address(okt)); // Bob does 100 buy/sell/withdraw cycles for (uint i = 0; i < 100; i++) { - vm.prank(bob); okt.buy(10_000, 0); + vm.prank(bob); okt.buy(10_000); uint256 bobBal = okt.balanceOf(bob); if (bobBal > 1) { - vm.prank(bob); okt.sell(bobBal - 1, 0); + vm.prank(bob); okt.sell(bobBal - 1); } uint256 bobDivs = okt.dividendsOf(bob); if (bobDivs > 0) { @@ -73,18 +73,18 @@ contract OKTSecurityTest is Test { function test_dividendDrift_multipleActors() public { // Three actors trading simultaneously - vm.prank(alice); okt.buy(500_000, 0); - vm.prank(bob); okt.buy(500_000, 0); - vm.prank(carol); okt.buy(500_000, 0); + vm.prank(alice); okt.buy(500_000); + vm.prank(bob); okt.buy(500_000); + vm.prank(carol); okt.buy(500_000); for (uint i = 0; i < 50; i++) { // Each actor buys, sells, withdraws in rotation - vm.prank(alice); okt.buy(10_000, 0); - vm.prank(bob); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); + vm.prank(bob); okt.buy(10_000); uint256 aliceBal = okt.balanceOf(alice); if (aliceBal > 1000) { - vm.prank(alice); okt.sell(1000, 0); + vm.prank(alice); okt.sell(1000); } uint256 bobDivs = okt.dividendsOf(bob); @@ -92,7 +92,7 @@ contract OKTSecurityTest is Test { vm.prank(bob); okt.withdraw(); } - vm.prank(carol); okt.buy(5_000, 0); + vm.prank(carol); okt.buy(5_000); } // Final solvency check @@ -109,10 +109,10 @@ contract OKTSecurityTest is Test { function test_precisionLoss_minimumBuy() public { // First buyer - vm.prank(alice); okt.buy(100, 0); // minimum buy + vm.prank(alice); okt.buy(100); // minimum buy // Second buyer minimum - vm.prank(bob); okt.buy(100, 0); + vm.prank(bob); okt.buy(100); // Contract should hold exactly 200 sats assertEq(cbbtc.balanceOf(address(okt)), 200); @@ -124,13 +124,13 @@ contract OKTSecurityTest is Test { function test_precisionLoss_manySmallBuys() public { // First buyer - vm.prank(alice); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); uint256 contractBefore = cbbtc.balanceOf(address(okt)); // 200 minimum buys from different senders for (uint i = 0; i < 200; i++) { - vm.prank(bob); okt.buy(100, 0); + vm.prank(bob); okt.buy(100); } uint256 contractAfter = cbbtc.balanceOf(address(okt)); @@ -141,14 +141,14 @@ contract OKTSecurityTest is Test { } function test_precisionLoss_manySmallSells() public { - vm.prank(alice); okt.buy(100_000, 0); - vm.prank(bob); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); + vm.prank(bob); okt.buy(100_000); // Bob sells 1 token at a time, 100 times for (uint i = 0; i < 100; i++) { uint256 bobBal = okt.balanceOf(bob); if (bobBal > 1 && okt.totalSupply() > 1) { - vm.prank(bob); okt.sell(100, 0); + vm.prank(bob); okt.sell(100); } } @@ -159,8 +159,8 @@ contract OKTSecurityTest is Test { function test_precisionLoss_largeSpread() public { // One whale, one small buyer — tests precision with very different balances - vm.prank(alice); okt.buy(1_000_000, 0); // max buy whale - vm.prank(bob); okt.buy(100, 0); // minimum buy + vm.prank(alice); okt.buy(1_000_000); // max buy whale + vm.prank(bob); okt.buy(100); // minimum buy uint256 aliceDivs = okt.dividendsOf(alice); uint256 bobDivs = okt.dividendsOf(bob); @@ -179,8 +179,8 @@ contract OKTSecurityTest is Test { // ═══════════════════════════════════════════════════════════════════════════ function test_reinvestDustLoop_blocked() public { - vm.prank(alice); okt.buy(10_000, 0); - vm.prank(bob); okt.buy(200, 0); // tiny buy generates small dividend + vm.prank(alice); okt.buy(10_000); + vm.prank(bob); okt.buy(200); // tiny buy generates small dividend uint256 supplyBefore = okt.totalSupply(); @@ -238,7 +238,7 @@ contract OKTSecurityTest is Test { // Vault sells — should trigger VaultSwept vm.prank(vault1); - okt.sell(1000, 0); + okt.sell(1000); // Check vault is marked as swept (bool registered, bool swept,,) = okt.vaultStatus(vault1); @@ -250,7 +250,7 @@ contract OKTSecurityTest is Test { okt.inscribe(vault1, bytes32("TEST-001"), 50_000, 0, ""); // Need another holder to transfer to - vm.prank(alice); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); // Vault transfers — should trigger sweep vm.prank(vault1); @@ -265,7 +265,7 @@ contract OKTSecurityTest is Test { okt.inscribe(vault1, bytes32("TEST-001"), 50_000, 0, ""); // Generate some dividends for the vault - vm.prank(alice); okt.buy(100_000, 0); + vm.prank(alice); okt.buy(100_000); uint256 vaultDivs = okt.dividendsOf(vault1); if (vaultDivs > 0) { @@ -283,14 +283,14 @@ contract OKTSecurityTest is Test { // ═══════════════════════════════════════════════════════════════════════════ function test_doubleSpend_sellThenWithdraw() public { - vm.prank(alice); okt.buy(1_000_000, 0); - vm.prank(bob); okt.buy(1_000_000, 0); + vm.prank(alice); okt.buy(1_000_000); + vm.prank(bob); okt.buy(1_000_000); uint256 bobCbbtcBefore = cbbtc.balanceOf(bob); // Bob sells everything except 1 uint256 bobTokens = okt.balanceOf(bob); - vm.prank(bob); okt.sell(bobTokens - 1, 0); + vm.prank(bob); okt.sell(bobTokens - 1); // Bob tries to withdraw any remaining dividends uint256 bobDivs = okt.dividendsOf(bob); @@ -307,16 +307,16 @@ contract OKTSecurityTest is Test { } function test_doubleSpend_rapidBuySellCycle() public { - vm.prank(alice); okt.buy(1_000_000, 0); // seed + vm.prank(alice); okt.buy(1_000_000); // seed uint256 attackerBefore = cbbtc.balanceOf(attacker); // Attacker tries rapid buy/sell/withdraw 20 times for (uint i = 0; i < 20; i++) { - vm.prank(attacker); okt.buy(10_000, 0); + vm.prank(attacker); okt.buy(10_000); uint256 bal = okt.balanceOf(attacker); if (bal > 1 && okt.totalSupply() > bal) { - vm.prank(attacker); okt.sell(bal - 1, 0); + vm.prank(attacker); okt.sell(bal - 1); } uint256 divs = okt.dividendsOf(attacker); if (divs > 0) { @@ -337,43 +337,43 @@ contract OKTSecurityTest is Test { function test_zeroBuyReverts() public { vm.prank(alice); vm.expectRevert("Minimum 100 sats"); - okt.buy(0, 0); + okt.buy(0); } function test_zeroSellReverts() public { - vm.prank(alice); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); vm.prank(alice); vm.expectRevert("Minimum 100 sats to sell"); - okt.sell(0, 0); + okt.sell(0); } function test_sellMoreThanBalanceReverts() public { - vm.prank(alice); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); vm.prank(alice); vm.expectRevert("Insufficient balance"); - okt.sell(999_999, 0); + okt.sell(999_999); } function test_transferToZeroReverts() public { - vm.prank(alice); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); vm.prank(alice); vm.expectRevert("Zero address"); okt.transfer(address(0), 100); } function test_withdrawWithNoDivsReverts() public { - vm.prank(alice); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); vm.prank(alice); vm.expectRevert("No dividends to withdraw"); okt.withdraw(); } function test_sellEntireSupplyReverts() public { - vm.prank(alice); okt.buy(10_000, 0); + vm.prank(alice); okt.buy(10_000); uint256 bal = okt.balanceOf(alice); vm.prank(alice); vm.expectRevert("Cannot sell entire supply"); - okt.sell(bal, 0); + okt.sell(bal); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/test/OKT.t.sol b/test/OKT.t.sol index dba785d..5d942fb 100644 --- a/test/OKT.t.sol +++ b/test/OKT.t.sol @@ -46,27 +46,27 @@ contract OKTTest is Test { function test_firstBuyGetsFullAmount() public { vm.prank(alice); - okt.buy(SATS, 0); + okt.buy(SATS); assertEq(okt.balanceOf(alice), SATS); // first buyer gets fee back } function test_secondBuyPays7PercentFee() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); assertEq(okt.balanceOf(bob), SATS * 93 / 100); } // ─── Dividend tests ─────────────────────────────────────────────────────── function test_dividendsAccumulateAfterBuy() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); assertGt(okt.dividendsOf(alice), 0); } function test_withdrawGivesCorrectAmount() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); uint256 divs = okt.dividendsOf(alice); uint256 balBefore = cbbtc.balanceOf(alice); vm.prank(alice); okt.withdraw(); @@ -76,30 +76,30 @@ contract OKTTest is Test { // ─── Sell tests ─────────────────────────────────────────────────────────── function test_sellSendsCbbtcDirectly() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); uint256 balBefore = cbbtc.balanceOf(bob); uint256 bobTokens = okt.balanceOf(bob); - vm.prank(bob); okt.sell(bobTokens - 1, 0); + vm.prank(bob); okt.sell(bobTokens - 1); uint256 balAfter = cbbtc.balanceOf(bob); // Bob should receive 93% of tokens sold directly in cbBTC assertGt(balAfter, balBefore); } function test_sellAndRebuyStillEarnsDividends() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); - vm.prank(bob); okt.sell(1000, 0); // sell fixed amount - vm.prank(bob); okt.buy(SATS, 0); - vm.prank(carol); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); + vm.prank(bob); okt.sell(1000); // sell fixed amount + vm.prank(bob); okt.buy(SATS); + vm.prank(carol); okt.buy(SATS); assertGt(okt.dividendsOf(bob), 0); } function test_sellEverythingDividendsRemain() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); uint256 divsBefore = okt.dividendsOf(alice); - vm.prank(alice); okt.sell(1000, 0); + vm.prank(alice); okt.sell(1000); uint256 divsAfter = okt.dividendsOf(alice); assertGe(divsAfter, divsBefore); } @@ -107,8 +107,8 @@ contract OKTTest is Test { // ─── Reinvest test ──────────────────────────────────────────────────────── function test_reinvestMintsTokens() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); uint256 balBefore = okt.balanceOf(alice); vm.prank(alice); okt.reinvest(); assertGt(okt.balanceOf(alice), balBefore); @@ -116,14 +116,14 @@ contract OKTTest is Test { // ─── Double-spend protection test ─────────────────────────────────────── function test_noDoubleSpendOnSell() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); uint256 bobCbbtcBefore = cbbtc.balanceOf(bob); uint256 bobTokens = okt.balanceOf(bob); // Bob sells - vm.prank(bob); okt.sell(1000, 0); + vm.prank(bob); okt.sell(1000); uint256 bobCbbtcAfterSell = cbbtc.balanceOf(bob); uint256 bobDivsAfterSell = okt.dividendsOf(bob); @@ -139,8 +139,8 @@ contract OKTTest is Test { // ─── Reinvest dust loop prevention ────────────────────────────────────── function test_reinvestMinimum100Sats() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); // Alice has dividends from Bob's buy but likely < 100 sats uint256 divs = okt.dividendsOf(alice); if (divs < 100) { @@ -151,8 +151,8 @@ contract OKTTest is Test { } function test_reinvestDustLoopBlocked() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(200, 0); // small buy generates tiny dividend + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(200); // small buy generates tiny dividend uint256 divs = okt.dividendsOf(alice); if (divs < 100) { // Cannot reinvest dust — loop is blocked @@ -166,9 +166,9 @@ contract OKTTest is Test { function test_minimalSell() public { // Alice buys first - gets 10000 (first buyer) - vm.prank(alice); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); // Bob buys - gets 9300 - vm.prank(bob); okt.buy(SATS, 0); + vm.prank(bob); okt.buy(SATS); // Check balances before sell uint256 bobBalance = okt.balanceOf(bob); @@ -181,7 +181,7 @@ contract OKTTest is Test { // Sell minimum 100 tokens vm.prank(bob); - okt.sell(100, 0); + okt.sell(100); emit log_named_uint("Contract cbBTC after sell", cbbtc.balanceOf(address(okt))); emit log_named_uint("Bob dividends after sell", okt.dividendsOf(bob)); @@ -191,9 +191,9 @@ contract OKTTest is Test { // cbBTC in contract must always cover all claimable dividends function test_solvency_after_buys() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); - vm.prank(carol); okt.buy(SATS, 0); + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); + vm.prank(carol); okt.buy(SATS); uint256 contractBalance = cbbtc.balanceOf(address(okt)); uint256 totalOwed = okt.dividendsOf(alice) @@ -204,10 +204,10 @@ contract OKTTest is Test { } function test_solvency_after_sell() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); - vm.prank(carol); okt.buy(SATS, 0); - vm.prank(bob); okt.sell(1000, 0); // sell fixed amount + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); + vm.prank(carol); okt.buy(SATS); + vm.prank(bob); okt.sell(1000); // sell fixed amount uint256 contractBalance = cbbtc.balanceOf(address(okt)); uint256 aliceDivs = okt.dividendsOf(alice); @@ -226,10 +226,10 @@ contract OKTTest is Test { } function test_solvency_after_withdraw() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); - vm.prank(carol); okt.buy(SATS, 0); - vm.prank(bob); okt.sell(1000, 0); // sell fixed amount + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); + vm.prank(carol); okt.buy(SATS); + vm.prank(bob); okt.sell(1000); // sell fixed amount vm.prank(alice); okt.withdraw(); uint256 contractBalance = cbbtc.balanceOf(address(okt)); @@ -241,10 +241,10 @@ contract OKTTest is Test { } function test_solvency_after_reinvest() public { - vm.prank(alice); okt.buy(SATS, 0); - vm.prank(bob); okt.buy(SATS, 0); - vm.prank(carol); okt.buy(SATS, 0); - vm.prank(bob); okt.sell(1000, 0); // sell fixed amount + vm.prank(alice); okt.buy(SATS); + vm.prank(bob); okt.buy(SATS); + vm.prank(carol); okt.buy(SATS); + vm.prank(bob); okt.sell(1000); // sell fixed amount vm.prank(alice); okt.reinvest(); uint256 contractBalance = cbbtc.balanceOf(address(okt));