diff --git a/lib/interfaces b/lib/interfaces index d27e48f..5a4d0a1 160000 --- a/lib/interfaces +++ b/lib/interfaces @@ -1 +1 @@ -Subproject commit d27e48f93399e49d658b5b8cec3325e0a28f0122 +Subproject commit 5a4d0a155d74c451c0f980be01416ef6bbd45a40 diff --git a/test/zksync/KDAO.snapshot.t.sol b/test/zksync/KDAO.snapshot.t.sol index e9fcc68..b129d0b 100644 --- a/test/zksync/KDAO.snapshot.t.sol +++ b/test/zksync/KDAO.snapshot.t.sol @@ -12,7 +12,6 @@ import { VOTING } from "interfaces/kimlikdao/addresses.sol"; import {amountAddrFrom} from "interfaces/types/amountAddr.sol"; -import {computeCreateAddress as computeZkSyncCreateAddress} from "interfaces/zksync/IZkSync.sol"; import {KDAO} from "zksync/KDAO.sol"; import {KDAOLocked} from "zksync/KDAOLocked.sol"; @@ -20,7 +19,7 @@ contract KDAOSnapshotTest is Test { KDAO private kdao; KDAOLocked private kdaol; - function mintAll(uint256 amount) public { + function mintAll(uint256 amount) internal { vm.startPrank(VOTING); for (uint256 i = 1; i <= 20; ++i) { kdao.mint(amountAddrFrom(amount, vm.addr(i))); @@ -38,11 +37,6 @@ contract KDAOSnapshotTest is Test { mintAll(1e12); } - function testAddressConsistency() external pure { - assertEq(computeZkSyncCreateAddress(KDAO_LOCKED_DEPLOYER, 0), KDAO_LOCKED); - assertEq(computeZkSyncCreateAddress(KDAO_ZKSYNC_DEPLOYER, 0), KDAO_ZKSYNC); - } - function testSnapshot0() external { vm.prank(vm.addr(1)); kdao.transfer(vm.addr(2), 250_000e6); diff --git a/test/zksync/KDAO.t.sol b/test/zksync/KDAO.t.sol index bcd38cb..c007cbe 100644 --- a/test/zksync/KDAO.t.sol +++ b/test/zksync/KDAO.t.sol @@ -2,16 +2,20 @@ pragma solidity ^0.8.0; -import {Test} from "forge-std/Test.sol"; +import {Test, console} from "forge-std/Test.sol"; +import {DistroStage} from "interfaces/kimlikdao/IDistroStage.sol"; import { + DEV_FUND, KDAO_LOCKED, KDAO_LOCKED_DEPLOYER, KDAO_ZKSYNC, KDAO_ZKSYNC_DEPLOYER, + PROTOCOL_FUND_ZKSYNC, VOTING } from "interfaces/kimlikdao/addresses.sol"; import {amountAddrFrom} from "interfaces/types/amountAddr.sol"; import {uint48x2From} from "interfaces/types/uint48x2.sol"; +import {computeCreateAddress as computeZkSyncCreateAddress} from "interfaces/zksync/IZkSync.sol"; import {KDAO} from "zksync/KDAO.sol"; import {KDAOLocked} from "zksync/KDAOLocked.sol"; @@ -19,22 +23,27 @@ contract KDAOTest is Test { KDAO private kdao; KDAOLocked private kdaol; - function mintAll(uint256 amount) public { - vm.startPrank(VOTING); + function mintAll(uint256 amount, address minter) internal { + vm.startPrank(minter); for (uint256 i = 1; i <= 20; ++i) { kdao.mint(amountAddrFrom(amount, vm.addr(i))); } vm.stopPrank(); } - function setUp() public { + function setUp() external { vm.etch(KDAO_LOCKED, type(KDAOLocked).runtimeCode); kdaol = KDAOLocked(KDAO_LOCKED); vm.etch(KDAO_ZKSYNC, type(KDAO).runtimeCode); kdao = KDAO(KDAO_ZKSYNC); - mintAll(1e12); + mintAll(1e12, VOTING); + } + + function testAddressConsistency() external pure { + assertEq(computeZkSyncCreateAddress(KDAO_LOCKED_DEPLOYER, 0), KDAO_LOCKED); + assertEq(computeZkSyncCreateAddress(KDAO_ZKSYNC_DEPLOYER, 0), KDAO_ZKSYNC); } function testDomainSeparator() external view { @@ -54,7 +63,7 @@ contract KDAOTest is Test { ); } - function testAuthentication() public { + function testAuthentication() external { vm.expectRevert(); kdao.snapshot0(); vm.expectRevert(); @@ -82,14 +91,14 @@ contract KDAOTest is Test { vm.stopPrank(); } - function testTransfer() public { + function testTransfer() external { vm.prank(vm.addr(1)); kdao.transfer(vm.addr(2), 250_000e6); assertEq(kdao.balanceOf(vm.addr(1)), 0); assertEq(kdao.balanceOf(vm.addr(2)), 500_000e6); } - function testTransferFrom() public { + function testTransferFrom() external { vm.prank(vm.addr(1)); kdao.approve(vm.addr(3), 250_000e6); @@ -99,4 +108,116 @@ contract KDAOTest is Test { assertEq(kdao.balanceOf(vm.addr(1)), 0); assertEq(kdao.balanceOf(vm.addr(2)), 500_000e6); } + + function testProtocolAuthentication() external { + vm.expectRevert(); + kdao.mint(amountAddrFrom(1, vm.addr(1))); + + vm.expectRevert(); + kdao.incrementDistroStage(DistroStage.Presale2); + } + + function testSnapshotAuthentication() external { + vm.expectRevert(); + kdao.snapshot0(); + + vm.expectRevert(); + kdao.snapshot1(); + + vm.expectRevert(); + kdao.snapshot2(); + + vm.startPrank(VOTING); + kdao.snapshot0(); + kdao.snapshot1(); + kdao.snapshot2(); + vm.stopPrank(); + } + + function testShouldCompleteAllRounds() external { + // 1M each, 250k unlocked + assertEq(kdao.totalSupply(), 20e12); + assertEq(kdaol.totalSupply(), 15e12); + + vm.prank(vm.addr(1)); + kdao.transfer(vm.addr(2), 250_000e6); + + // vm.addr(1): 0, 750k + // vm.addr(2): 500k, 750k + assertEq(kdao.balanceOf(vm.addr(1)), 0); + assertEq(kdaol.balanceOf(vm.addr(1)), 750_000e6); + assertEq(kdao.balanceOf(vm.addr(2)), 500_000e6); + assertEq(kdaol.balanceOf(vm.addr(2)), 750_000e6); + + vm.prank(VOTING); + kdao.incrementDistroStage(DistroStage.Presale2); + mintAll(1e12, VOTING); + // vm.addr(1) 250k, 1_500k + // vm.addr(2) 750k, 1_500k + assertEq(kdao.totalSupply(), 40e12); + assertEq(kdaol.totalSupply(), 30e12); + + assertEq(kdao.balanceOf(vm.addr(1)), 250_000e6); + assertEq(kdaol.balanceOf(vm.addr(1)), 1_500_000e6); + assertEq(kdao.balanceOf(vm.addr(2)), 750_000e6); + assertEq(kdaol.balanceOf(vm.addr(2)), 1_500_000e6); + + vm.prank(VOTING); + kdao.incrementDistroStage(DistroStage.ProtocolSaleStart); + + assertEq(kdao.totalSupply(), 60e12); + assertEq(kdaol.totalSupply(), 30e12); + assertEq(kdao.balanceOf(PROTOCOL_FUND_ZKSYNC), 20e12); + + vm.prank(VOTING); + kdao.incrementDistroStage(DistroStage.ProtocolSaleEnd); + + kdaol.unlock(vm.addr(1)); + kdaol.unlock(vm.addr(2)); + + assertEq(kdao.balanceOf(vm.addr(1)), 1_000_000e6); + assertEq(kdao.balanceOf(vm.addr(2)), 1_500_000e6); + + kdaol.unlockAllEven(); + + assertEq(kdaol.balanceOf(vm.addr(1)), 750_000e6); + assertEq(kdaol.balanceOf(vm.addr(2)), 750_000e6); + + vm.prank(VOTING); + kdao.incrementDistroStage(DistroStage.ProtocolAMMStart); + + assertEq(kdao.totalSupply(), 80e12); + assertEq(kdaol.totalSupply(), 15e12); + + vm.prank(VOTING); + kdao.incrementDistroStage(DistroStage.Presale2Unlock); + + kdaol.unlockAllOdd(); + + assertEq(kdao.totalSupply(), 80e12); + assertEq(kdaol.totalSupply(), 0e12); + + vm.prank(VOTING); + kdao.incrementDistroStage(DistroStage.FinalMint); + mintAll(1e12, DEV_FUND); + + assertEq(kdaol.unlock(vm.addr(1)), false); + + assertEq(kdaol.balanceOf(vm.addr(1)), 750e9); + + vm.warp(1925097600); + vm.prank(VOTING); + kdao.incrementDistroStage(DistroStage.FinalUnlock); + kdaol.unlock(vm.addr(1)); + + assertEq(kdaol.balanceOf(vm.addr(1)), 0); + + kdaol.unlockAllEven(); + + assertEq(kdaol.balanceOf(vm.addr(12)), 0); + assertEq(kdaol.balanceOf(vm.addr(14)), 0); + assertEq(kdaol.balanceOf(vm.addr(17)), 0); + + assertEq(kdao.balanceOf(address(kdaol)), kdaol.totalSupply()); + } } diff --git a/zksync/KDAOLocked.sol b/zksync/KDAOLocked.sol index 7ed8650..633d2ba 100644 --- a/zksync/KDAOLocked.sol +++ b/zksync/KDAOLocked.sol @@ -6,7 +6,7 @@ import {IERC20} from "interfaces/erc/IERC20.sol"; import {DistroStage, IDistroStage} from "interfaces/kimlikdao/IDistroStage.sol"; import {KDAO_ZKSYNC} from "interfaces/kimlikdao/addresses.sol"; import {KDAO_ETHEREUM, PROTOCOL_FUND, VOTING} from "interfaces/kimlikdao/addresses.sol"; -import {uint128x2} from "interfaces/types/uint128x2.sol"; +import {uint48x2} from "interfaces/types/uint48x2.sol"; /** * @title KDAO-l: Locked KimlikDAO Token @@ -25,14 +25,6 @@ import {uint128x2} from "interfaces/types/uint128x2.sol"; * (I4) hi(balance[a]) > 0 => accounts1.includes(a) */ contract KDAOLocked is IERC20 { - uint256 public override totalSupply; - - mapping(address => uint128x2) private balances; - // Split Presale2 accounts out, so that even if we can't unlock them in - // one shot due to gas limit, we can still unlock others in one shot. - address[] private addrs0; - address[] private addrs1; - function name() external pure override returns (string memory) { return "Locked KDAO"; } @@ -45,10 +37,26 @@ contract KDAOLocked is IERC20 { return 6; } + /////////////////////////////////////////////////////////////////////////// + // + // IERC20 balance fields and methods + additional supply methods + // + /////////////////////////////////////////////////////////////////////////// + + uint256 public override totalSupply; + + mapping(address => uint48x2) private balances; + function balanceOf(address addr) external view override returns (uint256) { return balances[addr].sum(); } + /////////////////////////////////////////////////////////////////////////// + // + // IERC20 transfer methods + // + /////////////////////////////////////////////////////////////////////////// + function transfer(address to, uint256) external override returns (bool) { if (to == address(this)) return unlock(msg.sender); return false; @@ -58,6 +66,12 @@ contract KDAOLocked is IERC20 { return false; } + /////////////////////////////////////////////////////////////////////////// + // + // IERC20 allowance fields and methods + // + /////////////////////////////////////////////////////////////////////////// + function allowance(address, address) external pure override returns (uint256) { return 0; } @@ -72,6 +86,11 @@ contract KDAOLocked is IERC20 { // /////////////////////////////////////////////////////////////////////////// + // Split Presale2 accounts out, so that even if we can't unlock them in + // one shot due to gas limit, we can still unlock others in one shot. + address[] private addrs0; + address[] private addrs1; + function mint(address addr, uint256 amount, DistroStage stage) external { require(msg.sender == KDAO_ZKSYNC); if (uint256(stage) & 1 == 0) { @@ -79,7 +98,7 @@ contract KDAOLocked is IERC20 { balances[addr] = balances[addr].incLo(amount); } else { addrs1.push(addr); - balances[addr] == balances[addr].incHi(amount); + balances[addr] = balances[addr].incHi(amount); } unchecked { totalSupply += amount; @@ -90,7 +109,7 @@ contract KDAOLocked is IERC20 { function unlock(address addr) public returns (bool) { DistroStage stage = IDistroStage(KDAO_ZKSYNC).distroStage(); uint256 unlocked; - uint128x2 balance = balances[addr]; + uint48x2 balance = balances[addr]; if (stage >= DistroStage.ProtocolSaleEnd && stage != DistroStage.FinalMint) { unchecked { unlocked += balance.lo(); @@ -117,13 +136,16 @@ contract KDAOLocked is IERC20 { function unlockAllEven() external { DistroStage stage = IDistroStage(KDAO_ZKSYNC).distroStage(); - require(stage >= DistroStage.ProtocolSaleEnd && stage != DistroStage.FinalMint, "KDAO-l: Not matured"); + require( + stage >= DistroStage.ProtocolSaleEnd && stage != DistroStage.FinalMint, + "KDAO-l: Not matured" + ); unchecked { uint256 length = addrs0.length; uint256 totalUnlocked; for (uint256 i = 0; i < length; ++i) { address addr = addrs0[i]; - uint128x2 balance = balances[addr]; + uint48x2 balance = balances[addr]; uint256 unlocked = balance.lo(); if (unlocked > 0) { balances[addr] = balance.clearLo(); @@ -137,13 +159,16 @@ contract KDAOLocked is IERC20 { } function unlockAllOdd() external { - require(IDistroStage(KDAO_ZKSYNC).distroStage() >= DistroStage.Presale2Unlock, "KDAO-l: Not matured"); + require( + IDistroStage(KDAO_ZKSYNC).distroStage() >= DistroStage.Presale2Unlock, + "KDAO-l: Not matured" + ); unchecked { uint256 length = addrs1.length; uint256 totalUnlocked; for (uint256 i = 0; i < length; ++i) { address addr = addrs1[i]; - uint128x2 balance = balances[addr]; + uint48x2 balance = balances[addr]; uint256 unlocked = balance.hi(); if (unlocked > 0) { balances[addr] = balance.clearHi();