diff --git a/src/sales/SettlementSale.sol b/src/sales/SettlementSale.sol index 257efa2..1c2e109 100644 --- a/src/sales/SettlementSale.sol +++ b/src/sales/SettlementSale.sol @@ -108,8 +108,10 @@ import {TokenAmount, WalletTokenAmount} from "sales/interfaces/types.sol"; /// # Multi-Token Support /// /// This contract accepts multiple payment tokens (e.g. USDC and USDT) and tracks commitments, allocations, and refunds separately for each token. -/// All amounts in bids and allocations represent the total value across all tokens, and the contract uses amounts interchangeably -/// under the assumption that all tokens have the same value (e.g. all are USD stablecoins with 6 decimals). +/// All amounts in bids and allocations represent the total value across all tokens, and the contract uses amounts interchangeably. +/// CRITICAL ASSUMPTION: All payment tokens MUST maintain 1:1 value parity throughout the sale lifecycle (e.g. USD stablecoins). +/// If a token depegs or loses parity, the sale SHOULD be paused immediately using the `pause()` function for further assessment. +/// /// When processing bids, the contract accepts a single payment token per transaction, tracking the breakdown by token internally. /// During refunds and withdrawals, each token is transferred separately based on the accepted amounts recorded per-token amounts during settlement. /// @@ -155,27 +157,27 @@ contract SettlementSale is bytes32 public constant REFUNDER_ROLE = keccak256("REFUNDER_ROLE"); // Initialization errors - error InvalidPaymentTokenDecimals(IERC20Metadata token); - error DuplicatePaymentToken(IERC20 token); + error InvalidPaymentTokenDecimals(address token, uint256 got, uint256 want); + error DuplicatePaymentToken(address token); error NoPaymentTokens(); // Purchase permit validation errors error InvalidSaleUUID(bytes16 got, bytes16 want); - error PurchasePermitExpired(); + error PurchasePermitExpired(uint256 expiresAt, uint256 currentTime); error BidOutsideAllowedWindow(uint64 opensAt, uint64 closesAt, uint256 currentTime); error InvalidSender(address got, address want); error UnauthorizedSigner(address signer); // Commitment submission errors - error BidBelowMinAmount(uint256 newBidAmount, uint256 minAmount); - error BidExceedsMaxAmount(uint256 newBidAmount, uint256 maxAmount); - error WalletTiedToAnotherEntity(bytes16 got, bytes16 existing, address addr); - error MaxWalletsPerEntityExceeded(bytes16 entityID, uint256 current, uint256 max); + error BidBelowMinAmount(uint256 amount, uint256 min); + error BidExceedsMaxAmount(uint256 amount, uint256 max); + error WalletTiedToAnotherEntity(bytes16 got, bytes16 want, address wallet); + error MaxWalletsPerEntityExceeded(bytes16 entityID, uint256 count, uint256 max); error ZeroAmount(); - error BidAmountCannotBeLowered(uint256 newAmount, uint256 previousAmount); - error BidPriceCannotBeLowered(uint256 newPrice, uint256 previousPrice); - error BidPriceExceedsMaxPrice(uint256 bidPrice, uint256 maxPrice); - error BidPriceBelowMinPrice(uint256 bidPrice, uint256 minPrice); + error BidAmountCannotBeLowered(uint256 got, uint256 want); + error BidPriceCannotBeLowered(uint256 got, uint256 want); + error BidPriceExceedsMaxPrice(uint256 price, uint256 max); + error BidPriceBelowMinPrice(uint256 price, uint256 min); error BidMustHaveLockup(); error BidLockupCannotBeUndone(); error InvalidPaymentToken(address token); @@ -183,10 +185,10 @@ contract SettlementSale is // Settlement errors error AllocationAlreadySet(bytes16 entityID, uint256 acceptedAmount); error AllocationExceedsCommitment( - bytes16 entityID, address wallet, IERC20 token, uint256 allocation, uint256 commitment + bytes16 entityID, address wallet, address token, uint256 allocation, uint256 commitment ); error WalletNotAssociatedWithEntity(address wallet, bytes16 entityID); - error UnexpectedTotalAcceptedAmount(uint256 expected, uint256 actual); + error UnexpectedTotalAcceptedAmount(uint256 got, uint256 want); // Refund errors error AlreadyRefunded(bytes16 entityID); @@ -194,27 +196,34 @@ contract SettlementSale is error ClaimRefundDisabled(); // Withdrawal errors - error WithdrawalExceedsAvailable(IERC20 token, uint256 requested, uint256 available); + error WithdrawalExceedsAvailable(address token, uint256 requested, uint256 available); // Generic errors - error InvalidStage(Stage); + error InvalidStage(Stage got, Stage[] want); error ZeroAddress(); error ZeroEntityID(); error ZeroMaxWalletsPerEntity(); error SalePaused(); error EntityNotInitialized(bytes16 entityID); - error WalletNotInitialized(address); + error WalletNotInitialized(address wallet); event StageChanged(Stage indexed previousStage, Stage indexed newStage); - event EntityInitialized(bytes16 indexed entityID, address indexed addr); - event WalletInitialized(bytes16 indexed entityID, address indexed addr); - event BidPlaced(bytes16 indexed entityID, address indexed addr, Bid bid); - event BidCancelled(bytes16 indexed entityID, address indexed addr, uint256 amount); - event AllocationSet(bytes16 indexed entityID, address indexed wallet, IERC20 indexed token, uint256 acceptedAmount); + event EntityInitialized(bytes16 indexed entityID, address indexed wallet); + event WalletInitialized(bytes16 indexed entityID, address indexed wallet); + event BidPlaced(bytes16 indexed entityID, address indexed wallet, Bid bid); + event BidCancelled(bytes16 indexed entityID, address indexed wallet, uint256 amount); + event AllocationSet( + bytes16 indexed entityID, address indexed wallet, address indexed token, uint256 acceptedAmount + ); event EntityRefunded(bytes16 indexed entityID, uint256 amount); - event WalletRefunded(bytes16 indexed entityID, address indexed wallet, IERC20 indexed token, uint256 amount); + event WalletRefunded(bytes16 indexed entityID, address indexed wallet, address indexed token, uint256 amount); event RefundedEntitySkipped(bytes16 indexed entityID); - event ProceedsWithdrawn(address indexed receiver, IERC20 indexed token, uint256 amount); + event ProceedsWithdrawn(address indexed receiver, address indexed token, uint256 amount); + event ProceedsReceiverChanged(address indexed previousReceiver, address indexed newReceiver); + event ClaimRefundEnabledChanged(bool enabled); + event MaxWalletsPerEntityChanged(uint8 previousMax, uint8 newMax); + event PausedStateChanged(bool paused); + event TokensRecovered(address indexed token, uint256 amount, address indexed to); /// @notice The state of a wallet in the sale. /// @dev This tracks the wallet's committed and accepted amounts for each payment token. @@ -426,11 +435,13 @@ contract SettlementSale is // additional sanity check to ensure that all payment tokens have the same number of decimals, // so we can use amounts interchangeably (assuming they all have the same value, e.g. all are USD stablecoins) if (init.paymentTokens[i].decimals() != init.expectedPaymentTokenDecimals) { - revert InvalidPaymentTokenDecimals(init.paymentTokens[i]); + revert InvalidPaymentTokenDecimals( + address(init.paymentTokens[i]), init.paymentTokens[i].decimals(), init.expectedPaymentTokenDecimals + ); } if (_isValidPaymentToken[init.paymentTokens[i]]) { - revert DuplicatePaymentToken(init.paymentTokens[i]); + revert DuplicatePaymentToken(address(init.paymentTokens[i])); } _paymentTokens.push(init.paymentTokens[i]); @@ -468,14 +479,12 @@ contract SettlementSale is /// @notice Moves the sale to the `Commitment` stage, allowing participants to submit bids. /// @dev Can be called from `PreOpen` (first open) or `Closed` (reopen after closing). function openCommitment() external onlyRole(SALE_MANAGER_ROLE) onlyStages(Stage.PreOpen, Stage.Closed) { - emit StageChanged(stage, Stage.Commitment); - stage = Stage.Commitment; + _setStage(Stage.Commitment); } /// @notice Moves the sale to the `Closed` stage, preventing any new bids from being submitted. function closeCommitment() external onlyRole(SALE_MANAGER_ROLE) onlyStage(Stage.Commitment) { - emit StageChanged(Stage.Commitment, Stage.Closed); - stage = Stage.Closed; + _setStage(Stage.Closed); } /// @notice Tracks entities that placed bids in the sale. @@ -688,7 +697,7 @@ contract SettlementSale is } if (permit.expiresAt <= block.timestamp) { - revert PurchasePermitExpired(); + revert PurchasePermitExpired(permit.expiresAt, block.timestamp); } if (permit.wallet != msg.sender) { @@ -703,8 +712,7 @@ contract SettlementSale is /// @notice Moves the sale to the `Cancellation` stage, allowing participants to cancel their bids and receive refunds. function openCancellation() external onlyRole(SALE_MANAGER_ROLE) onlyStage(Stage.Closed) { - emit StageChanged(Stage.Closed, Stage.Cancellation); - stage = Stage.Cancellation; + _setStage(Stage.Cancellation); } /// @notice Cancels a bid during the `Cancellation` stage, allowing participants to cancel their bids and receive refunds. @@ -729,8 +737,7 @@ contract SettlementSale is /// @notice Moves the sale to the `Settlement` stage, allowing the settler to set allocations. /// @dev Can be called during the `Closed` stage (skipping the cancellation stage) or the `Cancellation` stage. function openSettlement() external onlyRole(SALE_MANAGER_ROLE) onlyStages(Stage.Closed, Stage.Cancellation) { - emit StageChanged(stage, Stage.Settlement); - stage = Stage.Settlement; + _setStage(Stage.Settlement); } /// @notice Allows the settler to set allocations for each entity that participated in the sale. @@ -776,7 +783,7 @@ contract SettlementSale is revert AllocationExceedsCommitment( allocation.saleSpecificEntityID, allocation.wallet, - token, + address(token), allocation.acceptedAmount, walletState.committedAmountByToken[token] ); @@ -801,7 +808,9 @@ contract SettlementSale is // set wallet state walletState.acceptedAmountByToken[token] = allocation.acceptedAmount; - emit AllocationSet(allocation.saleSpecificEntityID, allocation.wallet, token, allocation.acceptedAmount); + emit AllocationSet( + allocation.saleSpecificEntityID, allocation.wallet, address(token), allocation.acceptedAmount + ); } /// @notice Moves the sale to the `Done` stage, allowing participants to claim refunds and the admin to withdraw the proceeds. @@ -812,11 +821,10 @@ contract SettlementSale is onlyStage(Stage.Settlement) { if (totalAcceptedAmount() != expectedTotalAcceptedAmount) { - revert UnexpectedTotalAcceptedAmount(expectedTotalAcceptedAmount, totalAcceptedAmount()); + revert UnexpectedTotalAcceptedAmount(totalAcceptedAmount(), expectedTotalAcceptedAmount); } - emit StageChanged(Stage.Settlement, Stage.Done); - stage = Stage.Done; + _setStage(Stage.Done); } /// @notice Refunds entities their unallocated payment tokens. @@ -887,7 +895,7 @@ contract SettlementSale is // increment global counters entityTotalRefundAmount += refundAmount; _totalRefundedAmountByToken[token] += refundAmount; - emit WalletRefunded(entityID, wallets[i], token, refundAmount); + emit WalletRefunded(entityID, wallets[i], address(token), refundAmount); // Note: We transfer tokens within the same loop that updates state, to avoid having to recompute // or store the amounts to be refunded. @@ -931,11 +939,11 @@ contract SettlementSale is uint256 available = _totalAcceptedAmountByToken[token] - _withdrawnAmountByToken[token]; if (amount > available) { - revert WithdrawalExceedsAvailable(token, amount, available); + revert WithdrawalExceedsAvailable(address(token), amount, available); } _withdrawnAmountByToken[token] += amount; - emit ProceedsWithdrawn(proceedsReceiver, token, amount); + emit ProceedsWithdrawn(proceedsReceiver, address(token), amount); token.safeTransfer(proceedsReceiver, amount); } @@ -946,12 +954,15 @@ contract SettlementSale is revert ZeroAddress(); } + address previousReceiver = proceedsReceiver; proceedsReceiver = newProceedsReceiver; + emit ProceedsReceiverChanged(previousReceiver, newProceedsReceiver); } /// @notice Sets whether wallets can claim their own refunds during the `Done` stage. function setClaimRefundEnabled(bool enabled) external onlyRole(SALE_MANAGER_ROLE) { claimRefundEnabled = enabled; + emit ClaimRefundEnabledChanged(enabled); } /// @notice Sets the maximum number of wallets that can be associated with a single entity. @@ -960,27 +971,37 @@ contract SettlementSale is if (max == 0) { revert ZeroMaxWalletsPerEntity(); } + uint8 previousMax = maxWalletsPerEntity; maxWalletsPerEntity = max; + emit MaxWalletsPerEntityChanged(previousMax, max); } /// @notice Pauses the sale. /// @dev This is intended to be used in emergency situations. function pause() external onlyRole(PAUSER_ROLE) { paused = true; + emit PausedStateChanged(true); } /// @notice Sets whether the sale is paused. /// @dev This is intended to unpause the sale after a pause. function setPaused(bool isPaused) external onlyRole(SALE_MANAGER_ROLE) { paused = isPaused; + emit PausedStateChanged(isPaused); + } + + /// @notice Internal function to set the stage of the sale. + /// @dev Emits a StageChanged event and updates the stage. + function _setStage(Stage newStage) internal { + emit StageChanged(stage, newStage); + stage = newStage; } /// @notice Sets the stage of the sale. /// @dev This is only intended to be used in exceptional circumstances. /// Use with caution and consult with the Sonar team before using this function. function unsafeSetStage(Stage newStage) external onlyRole(DEFAULT_ADMIN_ROLE) { - emit StageChanged(stage, newStage); - stage = newStage; + _setStage(newStage); } /// @notice Returns the number of entities that have participated in the sale. @@ -1191,17 +1212,12 @@ contract SettlementSale is /// @notice Recovers any ERC20 tokens that are sent to the contract. /// @dev This can be used to recover any tokens that are sent to the contract by mistake. function recoverTokens(IERC20 token, uint256 amount, address to) external onlyRole(TOKEN_RECOVERER_ROLE) { + emit TokensRecovered(address(token), amount, to); token.safeTransfer(to, amount); } /// @notice Checks if the contract supports an interface. - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(AccessControlEnumerable) - returns (bool) - { + function supportsInterface(bytes4 interfaceId) public view override(AccessControlEnumerable) returns (bool) { return interfaceId == type(ICommitmentDataReader).interfaceId || interfaceId == type(ITotalCommitmentsReader).interfaceId || interfaceId == type(IEntityAllocationDataReader).interfaceId @@ -1218,7 +1234,9 @@ contract SettlementSale is function _onlyStage(Stage want) private view { Stage s = stage; if (s != want) { - revert InvalidStage(s); + Stage[] memory wanted = new Stage[](1); + wanted[0] = want; + revert InvalidStage(s, wanted); } } @@ -1231,7 +1249,10 @@ contract SettlementSale is function _onlyStages(Stage want1, Stage want2) private view { Stage s = stage; if (s != want1 && s != want2) { - revert InvalidStage(s); + Stage[] memory wanted = new Stage[](2); + wanted[0] = want1; + wanted[1] = want2; + revert InvalidStage(s, wanted); } } diff --git a/test/BidSubmission.t.sol b/test/BidSubmission.t.sol index fca3555..04ea06e 100644 --- a/test/BidSubmission.t.sol +++ b/test/BidSubmission.t.sol @@ -268,7 +268,9 @@ contract SettlementSalePurchasePermitValidationTest is SettlementSaleBidTestBase price: 10, amount: 1000e6, purchasePermit: permit, - err: abi.encodeWithSelector(SettlementSale.PurchasePermitExpired.selector) + err: abi.encodeWithSelector( + SettlementSale.PurchasePermitExpired.selector, permit.expiresAt, block.timestamp + ) }); } @@ -490,7 +492,7 @@ contract SettlementSalePurchasePermitValidationTest is SettlementSaleBidTestBase amount: 1000e6, token: usdc, purchasePermit: permit, - err: abi.encodeWithSelector(SettlementSale.PurchasePermitExpired.selector) + err: abi.encodeWithSelector(SettlementSale.PurchasePermitExpired.selector, expiresAt, block.timestamp) }); } } @@ -554,7 +556,7 @@ contract SettlementSaleBidTest is SettlementSaleBidTestBase { price: 10, amount: 1000e6, token: usdc, - err: abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Closed) + err: encodeInvalidStage(SettlementSale.Stage.Closed, SettlementSale.Stage.Commitment) }); } @@ -567,7 +569,7 @@ contract SettlementSaleBidTest is SettlementSaleBidTestBase { price: 10, amount: 1000e6, token: usdc, - err: abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.PreOpen) + err: encodeInvalidStage(SettlementSale.Stage.PreOpen, SettlementSale.Stage.Commitment) }); } diff --git a/test/Cancellation.t.sol b/test/Cancellation.t.sol index d81d7d4..63ca856 100644 --- a/test/Cancellation.t.sol +++ b/test/Cancellation.t.sol @@ -87,9 +87,7 @@ contract SettlementSaleCancellationTest is SettlementSaleBaseTest { openCancellation(); openSettlement(); - cancelBidFail( - alice, abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Settlement) - ); + cancelBidFail(alice, encodeInvalidStage(SettlementSale.Stage.Settlement, SettlementSale.Stage.Cancellation)); } function testCancelBid_DuringWrongStage_RevertsOrSucceeds(uint8 s) public { @@ -104,7 +102,7 @@ contract SettlementSaleCancellationTest is SettlementSaleBaseTest { if (stage == SettlementSale.Stage.Cancellation) { cancelBidSuccess(alice); } else { - cancelBidFail(alice, abi.encodeWithSelector(SettlementSale.InvalidStage.selector, stage)); + cancelBidFail(alice, encodeInvalidStage(stage, SettlementSale.Stage.Cancellation)); } } @@ -184,10 +182,10 @@ contract SettlementSaleCancellationTest is SettlementSaleBaseTest { emit SettlementSale.BidCancelled(aliceID, aliceWallet2, 5000e6); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(aliceID, alice, usdc, 2000e6); + emit SettlementSale.WalletRefunded(aliceID, alice, address(usdc), 2000e6); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(aliceID, aliceWallet2, usdt, 3000e6); + emit SettlementSale.WalletRefunded(aliceID, aliceWallet2, address(usdt), 3000e6); vm.expectEmit(true, true, true, true, address(sale)); emit SettlementSale.EntityRefunded(aliceID, 5000e6); diff --git a/test/General.t.sol b/test/General.t.sol index 5fbb78a..b1dd407 100644 --- a/test/General.t.sol +++ b/test/General.t.sol @@ -171,9 +171,7 @@ contract SettlementSaleConstructorTest is BaseTest { }); vm.expectRevert( - abi.encodeWithSelector( - SettlementSale.InvalidPaymentTokenDecimals.selector, IERC20Metadata(address(invalidToken)) - ) + abi.encodeWithSelector(SettlementSale.InvalidPaymentTokenDecimals.selector, address(invalidToken), 18, 6) ); new TestableSettlementSale(init); } @@ -604,27 +602,41 @@ contract SettlementSaleStageTest is SettlementSaleBaseTest { openCommitment(); assertEq(uint8(sale.stage()), uint8(SettlementSale.Stage.Commitment)); - // Try to open commitment phase while in Commitment stage - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Commitment)); + // Try to open commitment while in Commitment stage + vm.expectRevert( + encodeInvalidStage( + SettlementSale.Stage.Commitment, SettlementSale.Stage.PreOpen, SettlementSale.Stage.Closed + ) + ); vm.prank(manager); sale.openCommitment(); // Try to open commitment phase while in Cancellation stage closeCommitment(); openCancellation(); - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Cancellation)); + vm.expectRevert( + encodeInvalidStage( + SettlementSale.Stage.Cancellation, SettlementSale.Stage.PreOpen, SettlementSale.Stage.Closed + ) + ); vm.prank(manager); sale.openCommitment(); // Try to open commitment phase while in Settlement stage openSettlement(); - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Settlement)); + vm.expectRevert( + encodeInvalidStage( + SettlementSale.Stage.Settlement, SettlementSale.Stage.PreOpen, SettlementSale.Stage.Closed + ) + ); vm.prank(manager); sale.openCommitment(); // Try to open commitment phase while in Done stage finalizeSettlement(); - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Done)); + vm.expectRevert( + encodeInvalidStage(SettlementSale.Stage.Done, SettlementSale.Stage.PreOpen, SettlementSale.Stage.Closed) + ); vm.prank(manager); sale.openCommitment(); } @@ -641,33 +653,42 @@ contract SettlementSaleStageTest is SettlementSaleBaseTest { function testCloseCommitment_WhenNotCommitment_Reverts() public { // Try to close while in PreOpen - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.PreOpen)); + vm.expectRevert(encodeInvalidStage(SettlementSale.Stage.PreOpen, SettlementSale.Stage.Commitment)); vm.prank(manager); sale.closeCommitment(); } function testOpenCancellation_WhenNotClosed_Reverts() public { // Try to open cancellation while in PreOpen - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.PreOpen)); + vm.expectRevert(encodeInvalidStage(SettlementSale.Stage.PreOpen, SettlementSale.Stage.Closed)); vm.prank(manager); sale.openCancellation(); // Try to open cancellation while in Commitment openCommitment(); - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Commitment)); + vm.expectRevert(encodeInvalidStage(SettlementSale.Stage.Commitment, SettlementSale.Stage.Closed)); + vm.prank(manager); sale.openCancellation(); } function testOpenSettlement_WhenNotClosedOrCancellation_Reverts() public { // Try while in PreOpen - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.PreOpen)); + vm.expectRevert( + encodeInvalidStage( + SettlementSale.Stage.PreOpen, SettlementSale.Stage.Closed, SettlementSale.Stage.Cancellation + ) + ); vm.prank(manager); sale.openSettlement(); // Try while in Commitment openCommitment(); - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Commitment)); + vm.expectRevert( + encodeInvalidStage( + SettlementSale.Stage.Commitment, SettlementSale.Stage.Closed, SettlementSale.Stage.Cancellation + ) + ); vm.prank(manager); sale.openSettlement(); } diff --git a/test/Refund.t.sol b/test/Refund.t.sol index 4d10130..be3e389 100644 --- a/test/Refund.t.sol +++ b/test/Refund.t.sol @@ -74,7 +74,7 @@ contract SettlementSaleRefundsTest is SettlementSaleBaseTest { entityIDs[1] = bobID; // repeated vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(charlieID, charlie, usdt, 10000e6); + emit SettlementSale.WalletRefunded(charlieID, charlie, address(usdt), 10000e6); vm.expectEmit(true, true, true, true, address(sale)); emit SettlementSale.EntityRefunded(charlieID, 10000e6); @@ -98,7 +98,7 @@ contract SettlementSaleRefundsTest is SettlementSaleBaseTest { bytes16[] memory entityIDs = new bytes16[](1); entityIDs[0] = aliceID; - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Settlement)); + vm.expectRevert(encodeInvalidStage(SettlementSale.Stage.Settlement, SettlementSale.Stage.Done)); vm.prank(refunder); sale.processRefunds(entityIDs, false); } @@ -327,10 +327,10 @@ contract SettlementSaleRefundsTest is SettlementSaleBaseTest { entityIDs[0] = aliceID; vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(aliceID, alice, usdc, 1000e6); + emit SettlementSale.WalletRefunded(aliceID, alice, address(usdc), 1000e6); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(aliceID, aliceWallet2, usdt, 1000e6); + emit SettlementSale.WalletRefunded(aliceID, aliceWallet2, address(usdt), 1000e6); vm.expectEmit(true, true, true, true, address(sale)); emit SettlementSale.EntityRefunded(aliceID, 2000e6); diff --git a/test/Settlement.t.sol b/test/Settlement.t.sol index db284e1..a405b6a 100644 --- a/test/Settlement.t.sol +++ b/test/Settlement.t.sol @@ -33,7 +33,7 @@ contract SettlementSaleSettlementTest is SettlementSaleBaseTest { }); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.AllocationSet(entityID, wallet, token, amount); + emit SettlementSale.AllocationSet(entityID, wallet, address(token), amount); vm.prank(settler); sale.setAllocations({allocations: allocations, allowOverwrite: allowOverwrite}); @@ -204,7 +204,7 @@ contract SettlementSaleSettlementTest is SettlementSaleBaseTest { token: usdc, amount: 3000e6, allowOverwrite: false, - err: abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.PreOpen) + err: encodeInvalidStage(SettlementSale.Stage.PreOpen, SettlementSale.Stage.Settlement) }); openCommitment(); @@ -214,7 +214,7 @@ contract SettlementSaleSettlementTest is SettlementSaleBaseTest { token: usdc, amount: 3000e6, allowOverwrite: false, - err: abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Commitment) + err: encodeInvalidStage(SettlementSale.Stage.Commitment, SettlementSale.Stage.Settlement) }); closeCommitment(); @@ -230,7 +230,7 @@ contract SettlementSaleSettlementTest is SettlementSaleBaseTest { token: usdc, amount: 3000e6, allowOverwrite: false, - err: abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Done) + err: encodeInvalidStage(SettlementSale.Stage.Done, SettlementSale.Stage.Settlement) }); } @@ -244,7 +244,7 @@ contract SettlementSaleSettlementTest is SettlementSaleBaseTest { setAllocationSuccess(alice, usdc, 1000e6, false); - vm.expectRevert(abi.encodeWithSelector(SettlementSale.UnexpectedTotalAcceptedAmount.selector, 2000e6, 1000e6)); + vm.expectRevert(abi.encodeWithSelector(SettlementSale.UnexpectedTotalAcceptedAmount.selector, 1000e6, 2000e6)); vm.prank(admin); sale.finalizeSettlement(2000e6); diff --git a/test/SettlementSaleBaseTest.sol b/test/SettlementSaleBaseTest.sol index 04cf45c..6b34f68 100644 --- a/test/SettlementSaleBaseTest.sol +++ b/test/SettlementSaleBaseTest.sol @@ -659,4 +659,26 @@ contract SettlementSaleBaseTest is BaseTest { balances[1] = TokenAmount({token: address(usdt), amount: usdt.balanceOf(owner)}); return balances; } + + /// @notice Helper to encode InvalidStage error with a single expected stage. + function encodeInvalidStage( + SettlementSale.Stage got, + SettlementSale.Stage want + ) internal pure returns (bytes memory) { + SettlementSale.Stage[] memory wanted = new SettlementSale.Stage[](1); + wanted[0] = want; + return abi.encodeWithSelector(SettlementSale.InvalidStage.selector, got, wanted); + } + + /// @notice Helper to encode InvalidStage error with two expected stages. + function encodeInvalidStage( + SettlementSale.Stage got, + SettlementSale.Stage want1, + SettlementSale.Stage want2 + ) internal pure returns (bytes memory) { + SettlementSale.Stage[] memory wanted = new SettlementSale.Stage[](2); + wanted[0] = want1; + wanted[1] = want2; + return abi.encodeWithSelector(SettlementSale.InvalidStage.selector, got, wanted); + } } diff --git a/test/Withdraw.t.sol b/test/Withdraw.t.sol index 2c38f9a..6df206a 100644 --- a/test/Withdraw.t.sol +++ b/test/Withdraw.t.sol @@ -36,13 +36,13 @@ contract SettlementSaleWithdrawTest is SettlementSaleBaseTest { finalizeSettlement(); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(aliceID, alice, usdc, 3000e6); + emit SettlementSale.WalletRefunded(aliceID, alice, address(usdc), 3000e6); vm.expectEmit(true, true, true, true, address(sale)); emit SettlementSale.EntityRefunded(aliceID, 3000e6); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(charlieID, charlie, usdt, 10000e6); + emit SettlementSale.WalletRefunded(charlieID, charlie, address(usdt), 10000e6); vm.expectEmit(true, true, true, true, address(sale)); emit SettlementSale.EntityRefunded(charlieID, 10000e6); @@ -58,7 +58,7 @@ contract SettlementSaleWithdrawTest is SettlementSaleBaseTest { sale.withdraw(); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.WalletRefunded(bobID, bob, usdt, 4000e6); + emit SettlementSale.WalletRefunded(bobID, bob, address(usdt), 4000e6); vm.expectEmit(true, true, true, true, address(sale)); emit SettlementSale.EntityRefunded(bobID, 4000e6); @@ -106,7 +106,7 @@ contract SettlementSaleWithdrawTest is SettlementSaleBaseTest { } function testWithdraw_WrongStage_Reverts() public { - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Settlement)); + vm.expectRevert(encodeInvalidStage(SettlementSale.Stage.Settlement, SettlementSale.Stage.Done)); vm.prank(admin); sale.withdraw(); } @@ -115,7 +115,7 @@ contract SettlementSaleWithdrawTest is SettlementSaleBaseTest { finalizeSettlement(); vm.expectEmit(true, true, true, true, address(sale)); - emit SettlementSale.ProceedsWithdrawn(receiver, usdc, 3000e6); + emit SettlementSale.ProceedsWithdrawn(receiver, address(usdc), 3000e6); vm.prank(admin); sale.withdrawPartial(usdc, 3000e6); @@ -155,7 +155,7 @@ contract SettlementSaleWithdrawTest is SettlementSaleBaseTest { vm.prank(admin); vm.expectRevert( - abi.encodeWithSelector(SettlementSale.WithdrawalExceedsAvailable.selector, usdc, 10000e6, 7000e6) + abi.encodeWithSelector(SettlementSale.WithdrawalExceedsAvailable.selector, address(usdc), 10000e6, 7000e6) ); sale.withdrawPartial(usdc, 10000e6); } @@ -171,7 +171,7 @@ contract SettlementSaleWithdrawTest is SettlementSaleBaseTest { } function testwithdrawPartial_WrongStage_Reverts() public { - vm.expectRevert(abi.encodeWithSelector(SettlementSale.InvalidStage.selector, SettlementSale.Stage.Settlement)); + vm.expectRevert(encodeInvalidStage(SettlementSale.Stage.Settlement, SettlementSale.Stage.Done)); vm.prank(admin); sale.withdrawPartial(usdc, 1000e6); }