Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions contracts/dao/PreIPODistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ contract PreIPODistributor is

event SetPaused(uint64 indexed saleId, bool paused);

event ChangeUserTranche(uint64 indexed saleId, address indexed account, uint8 oldTranche, uint8 newTranche);

event DepositWhitelist(
uint64 indexed saleId,
address indexed account,
Expand Down Expand Up @@ -261,6 +263,26 @@ contract PreIPODistributor is
emit SetPaused(_saleId, _paused);
}

/// @dev MANAGER correction of an account's locked tranche. Allowed until the settlement root
/// is finalized; a pending (not-yet-finalized) root does not block it, but the pending
/// root must then be revoked and rebuilt off-chain to reflect the change. Deposit amounts
/// are untouched; the tranche never affects on-chain accounting.
function changeUserTranche(uint64 _saleId, address _account, uint8 _tranche)
external
onlyRole(MANAGER)
{
Sale storage sale = sales[_saleId];
require(sale.whitelistRoot != bytes32(0), "Invalid saleId");
require(settlements[_saleId].root == bytes32(0), "Settlement finalized");
require(_tranche == TRANCHE_UNLOCKED || _tranche == TRANCHE_LOCKED, "Invalid tranche");
uint8 oldTranche = userTranche[_saleId][_account];
require(oldTranche != 0, "Tranche not set");
require(_tranche != oldTranche, "Same tranche");

userTranche[_saleId][_account] = _tranche;
emit ChangeUserTranche(_saleId, _account, oldTranche, _tranche);
}

/// @dev Whitelist-round deposit. First deposit needs a valid proof; top-ups skip it.
/// The tranche is locked at the first deposit in this sale.
function depositWhitelist(uint64 _saleId, uint8 _tranche, bytes32[] calldata _proof, uint256 _amount)
Expand Down
74 changes: 74 additions & 0 deletions test/dao/PreIPODistributor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,80 @@ contract PreIPODistributorTest is Test {
assertEq(distributor.userTranche(saleId, alice), PKLSH);
}

// ---- changeUserTranche (manager correction) ----

function test_changeUserTranche_ok() public {
uint64 saleId = _saleWithDeposit(); // alice deposited XKLSH
assertEq(distributor.userTranche(saleId, alice), XKLSH);

vm.expectEmit(true, true, false, true, address(distributor));
emit PreIPODistributor.ChangeUserTranche(saleId, alice, XKLSH, PKLSH);
vm.prank(manager);
distributor.changeUserTranche(saleId, alice, PKLSH);

assertEq(distributor.userTranche(saleId, alice), PKLSH);
// deposit amounts are untouched
assertEq(distributor.deposits(saleId, alice), 200e18);
}

function test_changeUserTranche_acl() public {
uint64 saleId = _saleWithDeposit();
vm.prank(outsider);
vm.expectRevert();
distributor.changeUserTranche(saleId, alice, PKLSH);
}

function test_changeUserTranche_invalidSale_reverts() public {
vm.prank(manager);
vm.expectRevert("Invalid saleId");
distributor.changeUserTranche(0, alice, PKLSH);
}

function test_changeUserTranche_invalidTranche_reverts() public {
uint64 saleId = _saleWithDeposit();
vm.prank(manager);
vm.expectRevert("Invalid tranche");
distributor.changeUserTranche(saleId, alice, 3);
}

function test_changeUserTranche_noDeposit_reverts() public {
uint64 saleId = _saleWithDeposit(); // only alice deposited
vm.prank(manager);
vm.expectRevert("Tranche not set");
distributor.changeUserTranche(saleId, carol, PKLSH);
}

function test_changeUserTranche_sameTranche_reverts() public {
uint64 saleId = _saleWithDeposit(); // alice is XKLSH
vm.prank(manager);
vm.expectRevert("Same tranche");
distributor.changeUserTranche(saleId, alice, XKLSH);
}

function test_changeUserTranche_whileSettlementPending_ok() public {
uint64 saleId = _saleWithDeposit();
vm.prank(bot);
distributor.setSettlementRoot(saleId, keccak256("settle"), 50e18);

// a pending (not finalized) root does not block the correction
vm.prank(manager);
distributor.changeUserTranche(saleId, alice, PKLSH);
assertEq(distributor.userTranche(saleId, alice), PKLSH);
}

function test_changeUserTranche_afterFinalize_reverts() public {
uint64 saleId = _saleWithDeposit();
vm.prank(bot);
distributor.setSettlementRoot(saleId, keccak256("settle"), 50e18);
vm.warp(block.timestamp + 6 hours);
vm.prank(bot);
distributor.finalizeSettlement(saleId);

vm.prank(manager);
vm.expectRevert("Settlement finalized");
distributor.changeUserTranche(saleId, alice, PKLSH);
}

// ---- settlement: set / finalize ----

// create a sale, deposit in WL round, return saleId (totalDeposits = 200e18)
Expand Down
Loading