From 7f43822f1adf216937b4aa4a2ff701e712e0a815 Mon Sep 17 00:00:00 2001 From: Xiaozhou Li Date: Fri, 8 May 2026 16:22:43 -0700 Subject: [PATCH] update require errors --- src/CelerLedger.sol | 19 +- src/CelerLedgerMock.sol | 2 +- src/CelerWallet.sol | 17 +- src/PayRegistry.sol | 18 +- src/PayResolver.sol | 47 +-- src/RouterRegistry.sol | 9 +- src/VirtContractResolver.sol | 9 +- src/helper/BooleanCondMock.sol | 2 +- src/helper/ERC20ExampleToken.sol | 2 +- src/helper/NativeWrapMock.sol | 2 +- src/helper/NumericCondMock.sol | 2 +- src/helper/WalletTestHelper.sol | 2 +- src/interfaces/IBooleanCond.sol | 2 +- src/interfaces/ICelerLedger.sol | 2 +- src/interfaces/ICelerWallet.sol | 2 +- src/interfaces/INativeWrap.sol | 2 +- src/interfaces/INumericCond.sol | 2 +- src/interfaces/IPayRegistry.sol | 2 +- src/interfaces/IPayResolver.sol | 2 +- src/interfaces/IRouterRegistry.sol | 2 +- src/interfaces/IVirtContractResolver.sol | 2 +- src/lib/AgentPayErrors.sol | 302 ++++++++++++++++++ src/lib/data/Pb.sol | 2 +- src/lib/data/PbChain.sol | 2 +- src/lib/data/PbEntity.sol | 2 +- src/lib/ledgerlib/LedgerChannel.sol | 5 +- src/lib/ledgerlib/LedgerMigrate.sol | 21 +- src/lib/ledgerlib/LedgerOperation.sol | 156 +++++---- src/lib/ledgerlib/LedgerStruct.sol | 2 +- test/CelerLedger.ERC20.t.sol | 7 +- test/CelerLedger.ETH.t.sol | 63 ++-- test/CelerLedger.Migrate.t.sol | 5 +- test/CelerWallet.t.sol | 19 +- test/PayRegistry.t.sol | 11 +- test/PayResolver.t.sol | 35 +- test/RouterRegistry.t.sol | 7 +- test/VirtContractResolver.t.sol | 5 +- test/gas_logs/CelerLedger-ERC20.txt | 8 +- test/gas_logs/CelerLedger-ETH.txt | 30 +- test/gas_logs/PayResolver.txt | 4 +- .../fine_granularity/DepositEthInBatch.txt | 12 +- .../IntendSettle-OneState.txt | 14 +- 42 files changed, 623 insertions(+), 238 deletions(-) create mode 100644 src/lib/AgentPayErrors.sol diff --git a/src/CelerLedger.sol b/src/CelerLedger.sol index e7b2aa0..e0e95fa 100644 --- a/src/CelerLedger.sol +++ b/src/CelerLedger.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./lib/ledgerlib/LedgerStruct.sol"; import "./lib/ledgerlib/LedgerOperation.sol"; import "./lib/ledgerlib/LedgerMigrate.sol"; import "./lib/ledgerlib/LedgerChannel.sol"; +import "./lib/AgentPayErrors.sol"; import "./interfaces/ICelerWallet.sol"; import "./interfaces/INativeWrap.sol"; import "./interfaces/IPayRegistry.sol"; @@ -43,8 +44,8 @@ contract CelerLedger is ICelerLedger, Ownable { * later become its operator (during channel opening or via wallet creation). */ constructor(address _nativeWrap, address _payRegistry, address _celerWallet) Ownable(msg.sender) { - require(_nativeWrap != address(0), "nativeWrap address required"); - require(_nativeWrap.code.length > 0, "nativeWrap code required"); + require(_nativeWrap != address(0), AgentPayErrors.ZeroAddress()); + require(_nativeWrap.code.length > 0, AgentPayErrors.NativeWrapNotContract()); ledger.nativeWrap = INativeWrap(_nativeWrap); ledger.payRegistry = IPayRegistry(_payRegistry); ledger.celerWallet = ICelerWallet(_celerWallet); @@ -59,7 +60,7 @@ contract CelerLedger is ICelerLedger, Ownable { * native-drain path). */ receive() external payable { - require(msg.sender == address(ledger.nativeWrap), "Only nativeWrap"); + require(msg.sender == address(ledger.nativeWrap), AgentPayErrors.CallerNotNativeWrap()); } /** @@ -68,7 +69,7 @@ contract CelerLedger is ICelerLedger, Ownable { * @param _limits balance limits of the tokens */ function setBalanceLimits(address[] calldata _tokenAddrs, uint256[] calldata _limits) external onlyOwner { - require(_tokenAddrs.length == _limits.length, "Lengths do not match"); + require(_tokenAddrs.length == _limits.length, AgentPayErrors.LengthMismatch(_tokenAddrs.length, _limits.length)); for (uint256 i = 0; i < _tokenAddrs.length; i++) { ledger.balanceLimits[_tokenAddrs[i]] = _limits[i]; } @@ -126,8 +127,12 @@ contract CelerLedger is ICelerLedger, Ownable { uint256[] calldata _transferFromAmounts ) external { require( - _channelIds.length == _receivers.length && _receivers.length == _transferFromAmounts.length, - "Lengths do not match" + _channelIds.length == _receivers.length, + AgentPayErrors.LengthMismatch(_channelIds.length, _receivers.length) + ); + require( + _receivers.length == _transferFromAmounts.length, + AgentPayErrors.LengthMismatch(_receivers.length, _transferFromAmounts.length) ); for (uint256 i = 0; i < _channelIds.length; i++) { ledger.deposit(_channelIds[i], _receivers[i], _transferFromAmounts[i]); diff --git a/src/CelerLedgerMock.sol b/src/CelerLedgerMock.sol index f25d6fc..655aebf 100644 --- a/src/CelerLedgerMock.sol +++ b/src/CelerLedgerMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./lib/ledgerlib/LedgerStruct.sol"; import "./lib/ledgerlib/LedgerOperation.sol"; diff --git a/src/CelerWallet.sol b/src/CelerWallet.sol index 4df9064..372804a 100644 --- a/src/CelerWallet.sol +++ b/src/CelerWallet.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./interfaces/ICelerWallet.sol"; +import "./lib/AgentPayErrors.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; @@ -45,7 +46,7 @@ contract CelerWallet is ICelerWallet, Pausable, Ownable { * @param _walletId id of the wallet to be operated */ modifier onlyOperator(bytes32 _walletId) { - require(msg.sender == wallets[_walletId].operator, "msg.sender is not operator"); + require(msg.sender == wallets[_walletId].operator, AgentPayErrors.NotOperator()); _; } @@ -55,7 +56,7 @@ contract CelerWallet is ICelerWallet, Pausable, Ownable { * @param _addr address to be checked */ modifier onlyWalletOwner(bytes32 _walletId, address _addr) { - require(_isWalletOwner(_walletId, _addr), "Given address is not wallet owner"); + require(_isWalletOwner(_walletId, _addr), AgentPayErrors.NotWalletOwner()); _; } @@ -73,12 +74,12 @@ contract CelerWallet is ICelerWallet, Pausable, Ownable { whenNotPaused returns (bytes32) { - require(_operator != address(0), "New operator is address(0)"); + require(_operator != address(0), AgentPayErrors.ZeroAddress()); bytes32 walletId = keccak256(abi.encodePacked(block.chainid, address(this), msg.sender, _nonce)); Wallet storage w = wallets[walletId]; // wallet must be uninitialized - require(w.operator == address(0), "Occupied wallet id"); + require(w.operator == address(0), AgentPayErrors.WalletIdOccupied()); w.owners = _owners; w.operator = _operator; walletNum++; @@ -182,7 +183,7 @@ contract CelerWallet is ICelerWallet, Pausable, Ownable { * @param _newOperator the new operator proposal */ function proposeNewOperator(bytes32 _walletId, address _newOperator) public onlyWalletOwner(_walletId, msg.sender) { - require(_newOperator != address(0), "New operator is address(0)"); + require(_newOperator != address(0), AgentPayErrors.ZeroAddress()); Wallet storage w = wallets[_walletId]; if (_newOperator != w.proposedNewOperator) { @@ -273,7 +274,7 @@ contract CelerWallet is ICelerWallet, Pausable, Ownable { function _withdrawToken(address _tokenAddress, address _receiver, uint256 _amount) internal { if (_tokenAddress == address(0)) { (bool success,) = payable(_receiver).call{value: _amount}(""); - require(success, "Native transfer failed"); + require(success, AgentPayErrors.NativeTransferFailed()); } else { IERC20(_tokenAddress).safeTransfer(_receiver, _amount); } @@ -313,7 +314,7 @@ contract CelerWallet is ICelerWallet, Pausable, Ownable { * @param _newOperator the new operator */ function _changeOperator(bytes32 _walletId, address _newOperator) internal { - require(_newOperator != address(0), "New operator is address(0)"); + require(_newOperator != address(0), AgentPayErrors.ZeroAddress()); Wallet storage w = wallets[_walletId]; address oldOperator = w.operator; diff --git a/src/PayRegistry.sol b/src/PayRegistry.sol index 31e11a5..d0bb16c 100644 --- a/src/PayRegistry.sol +++ b/src/PayRegistry.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./interfaces/IPayRegistry.sol"; +import "./lib/AgentPayErrors.sol"; /** * @title PayRegistry @@ -56,7 +57,7 @@ contract PayRegistry is IPayRegistry { /// @inheritdoc IPayRegistry function setPayAmounts(bytes32[] calldata _payHashes, uint256[] calldata _amts) external { - require(_payHashes.length == _amts.length, "Lengths do not match"); + require(_payHashes.length == _amts.length, AgentPayErrors.LengthMismatch(_payHashes.length, _amts.length)); bytes32 payId; address msgSender = msg.sender; @@ -71,7 +72,9 @@ contract PayRegistry is IPayRegistry { /// @inheritdoc IPayRegistry function setPayDeadlines(bytes32[] calldata _payHashes, uint256[] calldata _deadlines) external { - require(_payHashes.length == _deadlines.length, "Lengths do not match"); + require( + _payHashes.length == _deadlines.length, AgentPayErrors.LengthMismatch(_payHashes.length, _deadlines.length) + ); bytes32 payId; address msgSender = msg.sender; @@ -88,7 +91,10 @@ contract PayRegistry is IPayRegistry { function setPayInfos(bytes32[] calldata _payHashes, uint256[] calldata _amts, uint256[] calldata _deadlines) external { - require(_payHashes.length == _amts.length && _payHashes.length == _deadlines.length, "Lengths do not match"); + require(_payHashes.length == _amts.length, AgentPayErrors.LengthMismatch(_payHashes.length, _amts.length)); + require( + _payHashes.length == _deadlines.length, AgentPayErrors.LengthMismatch(_payHashes.length, _deadlines.length) + ); bytes32 payId; address msgSender = msg.sender; @@ -112,10 +118,10 @@ contract PayRegistry is IPayRegistry { for (uint256 i = 0; i < _payIds.length; i++) { if (payInfoMap[_payIds[i]].resolveDeadline == 0) { // unresolved pays are gated by the caller-supplied upper-bound deadline - require(block.timestamp > _maxResolveDeadline, "Payment is not finalized"); + require(block.timestamp > _maxResolveDeadline, AgentPayErrors.PaymentNotFinalized()); } else { // resolved pays are gated by their per-pay resolve deadline - require(block.timestamp > payInfoMap[_payIds[i]].resolveDeadline, "Payment is not finalized"); + require(block.timestamp > payInfoMap[_payIds[i]].resolveDeadline, AgentPayErrors.PaymentNotFinalized()); } amounts[i] = payInfoMap[_payIds[i]].amount; } diff --git a/src/PayResolver.sol b/src/PayResolver.sol index 2a7f325..4fed8a5 100644 --- a/src/PayResolver.sol +++ b/src/PayResolver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./lib/data/PbChain.sol"; import "./lib/data/PbEntity.sol"; @@ -8,6 +8,7 @@ import "./interfaces/IPayResolver.sol"; import "./interfaces/IBooleanCond.sol"; import "./interfaces/INumericCond.sol"; import "./interfaces/IVirtContractResolver.sol"; +import "./lib/AgentPayErrors.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; @@ -73,12 +74,17 @@ contract PayResolver is IPayResolver { PbEntity.CondPayResult memory payResult = PbEntity.decCondPayResult(vouchedPayResult.condPayResult); PbEntity.ConditionalPay memory pay = PbEntity.decConditionalPay(payResult.condPay); - require(payResult.amount <= pay.transferFunc.maxTransfer.receiver.amt, "Exceed max transfer amount"); + require( + payResult.amount <= pay.transferFunc.maxTransfer.receiver.amt, + AgentPayErrors.MaxTransferExceeded(payResult.amount, pay.transferFunc.maxTransfer.receiver.amt) + ); // check signatures bytes32 hash = keccak256(vouchedPayResult.condPayResult).toEthSignedMessageHash(); address recoveredSrc = hash.recover(vouchedPayResult.sigOfSrc); address recoveredDest = hash.recover(vouchedPayResult.sigOfDest); - require(recoveredSrc == address(pay.src) && recoveredDest == address(pay.dest), "Check sigs failed"); + require( + recoveredSrc == address(pay.src) && recoveredDest == address(pay.dest), AgentPayErrors.InvalidCoSignatures() + ); bytes32 payHash = keccak256(payResult.condPay); _resolvePayment(pay, payHash, payResult.amount); @@ -92,21 +98,23 @@ contract PayResolver is IPayResolver { */ function _resolvePayment(PbEntity.ConditionalPay memory _pay, bytes32 _payHash, uint256 _amount) internal { // bind the signed pay to its intended (chain, resolver) target - require(_pay.chainId == block.chainid, "Wrong chain id for pay"); - require(_pay.payResolver == address(this), "Wrong resolver for pay"); + require(_pay.chainId == block.chainid, AgentPayErrors.ChainIdMismatch(block.chainid, _pay.chainId)); + require(_pay.payResolver == address(this), AgentPayErrors.ResolverAddressMismatch()); uint256 nowTs = block.timestamp; - require(nowTs <= _pay.resolveDeadline, "Passed pay resolve deadline in condPay msg"); + require(nowTs <= _pay.resolveDeadline, AgentPayErrors.DeadlinePassed()); bytes32 payId = _calculatePayId(_payHash, address(this)); (uint256 currentAmt, uint256 currentDeadline) = payRegistry.getPayInfo(payId); - // should never resolve a pay before or not reaching onchain resolve deadline - require(currentDeadline == 0 || nowTs <= currentDeadline, "Passed onchain resolve pay deadline"); + // If a prior on-chain resolution exists (`currentDeadline > 0`), updates + // are only accepted while the registry's stored deadline has not yet + // passed. First-time resolution (`currentDeadline == 0`) always passes. + require(currentDeadline == 0 || nowTs <= currentDeadline, AgentPayErrors.ResolveUpdateWindowClosed()); if (currentDeadline > 0) { // currentDeadline > 0 implies that this pay has been updated // payment amount must be monotone increasing - require(_amount > currentAmt, "New amount is not larger"); + require(_amount > currentAmt, AgentPayErrors.AmountNotGreater()); if (_amount == _pay.transferFunc.maxTransfer.receiver.amt) { // set resolve deadline = current timestamp if amount = max @@ -124,7 +132,7 @@ contract PayResolver is IPayResolver { } else { newDeadline = Math.min(nowTs + _pay.resolveTimeout, _pay.resolveDeadline); // 0 is reserved for unresolved status of a payment - require(newDeadline > 0, "New resolve deadline is 0"); + require(newDeadline > 0, AgentPayErrors.ZeroDeadline()); } payRegistry.setPayInfo(_payHash, _amount, newDeadline); @@ -148,7 +156,7 @@ contract PayResolver is IPayResolver { for (uint256 i = 0; i < _pay.conditions.length; i++) { PbEntity.Condition memory cond = _pay.conditions[i]; if (cond.conditionType == PbEntity.ConditionType.HASH_LOCK) { - require(keccak256(_preimages[j]) == cond.hashLock, "Wrong preimage"); + require(keccak256(_preimages[j]) == cond.hashLock, AgentPayErrors.PreimageMismatch()); j++; } else if ( cond.conditionType == PbEntity.ConditionType.DEPLOYED_CONTRACT @@ -156,7 +164,7 @@ contract PayResolver is IPayResolver { ) { address addr = _getCondAddress(cond); IBooleanCond dependent = IBooleanCond(addr); - require(dependent.isFinalized(cond.argsQueryFinalization), "Condition is not finalized"); + require(dependent.isFinalized(cond.argsQueryFinalization), AgentPayErrors.ConditionNotFinalized()); if (!dependent.getOutcome(cond.argsQueryOutcome)) { hasFalseContractCond = true; @@ -191,7 +199,7 @@ contract PayResolver is IPayResolver { for (uint256 i = 0; i < _pay.conditions.length; i++) { PbEntity.Condition memory cond = _pay.conditions[i]; if (cond.conditionType == PbEntity.ConditionType.HASH_LOCK) { - require(keccak256(_preimages[j]) == cond.hashLock, "Wrong preimage"); + require(keccak256(_preimages[j]) == cond.hashLock, AgentPayErrors.PreimageMismatch()); j++; } else if ( cond.conditionType == PbEntity.ConditionType.DEPLOYED_CONTRACT @@ -199,7 +207,7 @@ contract PayResolver is IPayResolver { ) { address addr = _getCondAddress(cond); IBooleanCond dependent = IBooleanCond(addr); - require(dependent.isFinalized(cond.argsQueryFinalization), "Condition is not finalized"); + require(dependent.isFinalized(cond.argsQueryFinalization), AgentPayErrors.ConditionNotFinalized()); hasContractCond = true; if (dependent.getOutcome(cond.argsQueryOutcome)) { @@ -236,7 +244,7 @@ contract PayResolver is IPayResolver { for (uint256 i = 0; i < _pay.conditions.length; i++) { PbEntity.Condition memory cond = _pay.conditions[i]; if (cond.conditionType == PbEntity.ConditionType.HASH_LOCK) { - require(keccak256(_preimages[j]) == cond.hashLock, "Wrong preimage"); + require(keccak256(_preimages[j]) == cond.hashLock, AgentPayErrors.PreimageMismatch()); j++; } else if ( cond.conditionType == PbEntity.ConditionType.DEPLOYED_CONTRACT @@ -244,7 +252,7 @@ contract PayResolver is IPayResolver { ) { address addr = _getCondAddress(cond); INumericCond dependent = INumericCond(addr); - require(dependent.isFinalized(cond.argsQueryFinalization), "Condition is not finalized"); + require(dependent.isFinalized(cond.argsQueryFinalization), AgentPayErrors.ConditionNotFinalized()); if (_funcType == PbEntity.TransferFunctionType.NUMERIC_ADD) { amount = amount + dependent.getOutcome(cond.argsQueryOutcome); @@ -267,7 +275,10 @@ contract PayResolver is IPayResolver { } if (hasContractCond) { - require(amount <= _pay.transferFunc.maxTransfer.receiver.amt, "Exceed max transfer amount"); + require( + amount <= _pay.transferFunc.maxTransfer.receiver.amt, + AgentPayErrors.MaxTransferExceeded(amount, _pay.transferFunc.maxTransfer.receiver.amt) + ); return amount; } else { return _pay.transferFunc.maxTransfer.receiver.amt; @@ -288,7 +299,7 @@ contract PayResolver is IPayResolver { } else if (_cond.conditionType == PbEntity.ConditionType.VIRTUAL_CONTRACT) { return virtResolver.resolve(_cond.virtualContractAddress); } else { - revert("Invalid condition type"); + revert AgentPayErrors.InvalidConditionType(); } } diff --git a/src/RouterRegistry.sol b/src/RouterRegistry.sol index 5d51f2c..d2d6581 100644 --- a/src/RouterRegistry.sol +++ b/src/RouterRegistry.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./interfaces/IRouterRegistry.sol"; +import "./lib/AgentPayErrors.sol"; /** * @title RouterRegistry @@ -19,7 +20,7 @@ contract RouterRegistry is IRouterRegistry { * @notice An external router could register to join the Celer Network */ function registerRouter() external { - require(routerInfo[msg.sender] == 0, "Router address already exists"); + require(routerInfo[msg.sender] == 0, AgentPayErrors.RouterAlreadyRegistered()); routerInfo[msg.sender] = block.timestamp; @@ -30,7 +31,7 @@ contract RouterRegistry is IRouterRegistry { * @notice An in-network router could deregister to leave the network */ function deregisterRouter() external { - require(routerInfo[msg.sender] != 0, "Router address does not exist"); + require(routerInfo[msg.sender] != 0, AgentPayErrors.RouterNotRegistered()); delete routerInfo[msg.sender]; @@ -41,7 +42,7 @@ contract RouterRegistry is IRouterRegistry { * @notice Refresh the existed router's stored timestamp */ function refreshRouter() external { - require(routerInfo[msg.sender] != 0, "Router address does not exist"); + require(routerInfo[msg.sender] != 0, AgentPayErrors.RouterNotRegistered()); routerInfo[msg.sender] = block.timestamp; diff --git a/src/VirtContractResolver.sol b/src/VirtContractResolver.sol index be02009..d8825a4 100644 --- a/src/VirtContractResolver.sol +++ b/src/VirtContractResolver.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./interfaces/IVirtContractResolver.sol"; +import "./lib/AgentPayErrors.sol"; /** * @title VirtContractResolver @@ -18,12 +19,12 @@ contract VirtContractResolver is IVirtContractResolver { function deploy(bytes calldata _code, uint256 _nonce) external returns (bool) { bytes32 virtAddr = keccak256(abi.encodePacked(_code, _nonce)); bytes memory c = _code; - require(virtToRealMap[virtAddr] == address(0), "Current real address is not 0"); + require(virtToRealMap[virtAddr] == address(0), AgentPayErrors.VirtAddressOccupied()); address deployedAddress; assembly { deployedAddress := create(0, add(c, 32), mload(c)) } - require(deployedAddress != address(0), "Create contract failed."); + require(deployedAddress != address(0), AgentPayErrors.CreateContractFailed()); virtToRealMap[virtAddr] = deployedAddress; emit Deploy(virtAddr); @@ -32,7 +33,7 @@ contract VirtContractResolver is IVirtContractResolver { /// @inheritdoc IVirtContractResolver function resolve(bytes32 _virtAddr) external view returns (address) { - require(virtToRealMap[_virtAddr] != address(0), "Nonexistent virtual address"); + require(virtToRealMap[_virtAddr] != address(0), AgentPayErrors.VirtAddressUnresolved()); return virtToRealMap[_virtAddr]; } } diff --git a/src/helper/BooleanCondMock.sol b/src/helper/BooleanCondMock.sol index ead97ad..3260ae3 100644 --- a/src/helper/BooleanCondMock.sol +++ b/src/helper/BooleanCondMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "../interfaces/IBooleanCond.sol"; diff --git a/src/helper/ERC20ExampleToken.sol b/src/helper/ERC20ExampleToken.sol index b4b9d39..8171468 100644 --- a/src/helper/ERC20ExampleToken.sol +++ b/src/helper/ERC20ExampleToken.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // Based on https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/examples/SimpleToken.sol -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/src/helper/NativeWrapMock.sol b/src/helper/NativeWrapMock.sol index 582bd14..db1ef65 100644 --- a/src/helper/NativeWrapMock.sol +++ b/src/helper/NativeWrapMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "../interfaces/INativeWrap.sol"; diff --git a/src/helper/NumericCondMock.sol b/src/helper/NumericCondMock.sol index 33f66f2..768b1da 100644 --- a/src/helper/NumericCondMock.sol +++ b/src/helper/NumericCondMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "../interfaces/INumericCond.sol"; diff --git a/src/helper/WalletTestHelper.sol b/src/helper/WalletTestHelper.sol index 8900d57..cb3660d 100644 --- a/src/helper/WalletTestHelper.sol +++ b/src/helper/WalletTestHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "../interfaces/ICelerWallet.sol"; diff --git a/src/interfaces/IBooleanCond.sol b/src/interfaces/IBooleanCond.sol index afaff00..62cb708 100644 --- a/src/interfaces/IBooleanCond.sol +++ b/src/interfaces/IBooleanCond.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title BooleanCond interface diff --git a/src/interfaces/ICelerLedger.sol b/src/interfaces/ICelerLedger.sol index 0f5eab1..76c1b9b 100644 --- a/src/interfaces/ICelerLedger.sol +++ b/src/interfaces/ICelerLedger.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "../lib/data/PbEntity.sol"; import "../lib/ledgerlib/LedgerStruct.sol"; diff --git a/src/interfaces/ICelerWallet.sol b/src/interfaces/ICelerWallet.sol index d49dc57..ec5a75c 100644 --- a/src/interfaces/ICelerWallet.sol +++ b/src/interfaces/ICelerWallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title CelerWallet interface diff --git a/src/interfaces/INativeWrap.sol b/src/interfaces/INativeWrap.sol index 1010c8b..426f66d 100644 --- a/src/interfaces/INativeWrap.sol +++ b/src/interfaces/INativeWrap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/src/interfaces/INumericCond.sol b/src/interfaces/INumericCond.sol index 2533e13..2db8f1a 100644 --- a/src/interfaces/INumericCond.sol +++ b/src/interfaces/INumericCond.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title NumericCond interface diff --git a/src/interfaces/IPayRegistry.sol b/src/interfaces/IPayRegistry.sol index 18951e3..d16cf82 100644 --- a/src/interfaces/IPayRegistry.sol +++ b/src/interfaces/IPayRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title PayRegistry interface diff --git a/src/interfaces/IPayResolver.sol b/src/interfaces/IPayResolver.sol index a5b5bc7..5115000 100644 --- a/src/interfaces/IPayResolver.sol +++ b/src/interfaces/IPayResolver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title PayResolver interface diff --git a/src/interfaces/IRouterRegistry.sol b/src/interfaces/IRouterRegistry.sol index 81b0bfb..087bc80 100644 --- a/src/interfaces/IRouterRegistry.sol +++ b/src/interfaces/IRouterRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title RouterRegistry interface diff --git a/src/interfaces/IVirtContractResolver.sol b/src/interfaces/IVirtContractResolver.sol index 9e29b9f..e271dd0 100644 --- a/src/interfaces/IVirtContractResolver.sol +++ b/src/interfaces/IVirtContractResolver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title VirtContractResolver interface diff --git a/src/lib/AgentPayErrors.sol b/src/lib/AgentPayErrors.sol new file mode 100644 index 0000000..afd2923 --- /dev/null +++ b/src/lib/AgentPayErrors.sol @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +/** + * @title AgentPayErrors + * @notice Shared custom-error vocabulary for every production contract in + * AgentPay. All errors live under one namespace (`AgentPayErrors.X`) so the + * symbol's origin is unambiguous on every call site. + * + * Conventions: + * - Errors with parameters carry runtime values that meaningfully aid + * debugging — `expected vs actual` mismatches, `attempted vs limit` + * bounds, replay-protection mismatches. + * - Errors without parameters are named so the name *is* the diagnosis; + * the call shape itself disambiguates which check fired. + * - Errors used by multiple contracts (e.g. `LengthMismatch`, + * `ChainIdMismatch`, `InvalidCoSignatures`) are defined once and reused. + * - Naming style: `XMismatch` for value mismatches, `InvalidX` for + * structural invalidity, `XExceeded` for limit overruns. Avoid `Wrong*` + * and `*Failed` — they tell you the outcome but not the diagnosis. + */ +library AgentPayErrors { + // ------------------------------------------------------------------------- + // Parameterized — args carry diagnostic value + // ------------------------------------------------------------------------- + + /// @notice Two arrays whose lengths must match did not. `a` and `b` are + /// the two observed lengths. + error LengthMismatch(uint256 a, uint256 b); + + /// @notice Cross-chain replay protection — the message's bound chain id + /// did not match `block.chainid`. + error ChainIdMismatch(uint256 expected, uint256 actual); + + /// @notice `msg.value` did not match the amount the call shape requires. + /// Hit on the native-funding path of `openChannel` and `deposit`. + error MsgValueMismatch(uint256 expected, uint256 actual); + + /// @notice An attempted deposit / open would push the channel over the + /// configured per-token cap. + error BalanceLimitExceeded(uint256 attempted, uint256 limit); + + /// @notice `confirmWithdraw`'s post-update withdraw-limit gate. The + /// pending intent's amount exceeds what the receiver can claim given + /// the current accounting. + error WithdrawLimitExceeded(uint256 amount, uint256 limit); + + /// @notice Pay-resolution amount exceeds the `ConditionalPay`'s + /// declared `transferFunc.maxTransfer`. + error MaxTransferExceeded(uint256 attempted, uint256 max); + + /// @notice Co-signed simplex / cooperative-withdraw / cooperative-settle + /// seqNum was not strictly greater than (or, for cooperativeWithdraw, + /// exactly +1 above) the on-chain seqNum. + error SeqNumOutOfOrder(uint256 onchain, uint256 proposed); + + // ------------------------------------------------------------------------- + // Shared unparameterized — name *is* the diagnosis + // ------------------------------------------------------------------------- + + /// @notice A user-supplied address parameter was `address(0)`. + /// Covers operator / wallet-owner / nativeWrap-address checks. + error ZeroAddress(); + + /// @notice A time-bound deadline (open / withdraw / settle / migration / + /// pay-resolve) has expired. The call shape disambiguates which + /// deadline was checked. + /// @dev The pay-resolution *update* window has its own + /// {ResolveUpdateWindowClosed} since the two failure modes lead + /// operators to different remediation paths. + error DeadlinePassed(); + + /// @notice Co-signed message's recovered signers did not both match the + /// required signing pair. Used by channel-state co-sigs (peer0 + peer1) + /// in `LedgerOperation` / `LedgerMigrate`, and by vouched pay results + /// (`pay.src` + `pay.dest`) in `PayResolver`. + error InvalidCoSignatures(); + + /// @notice Single-sig message's recovered signer did not match the + /// required peer. + error InvalidSignature(); + + // ------------------------------------------------------------------------- + // CelerLedger + // ------------------------------------------------------------------------- + + /// @notice Constructor's `_nativeWrap` argument has no deployed + /// bytecode. Catches EOA / unrelated-address misconfiguration at + /// deploy time. + error NativeWrapNotContract(); + + /// @notice Restricted `receive()` rejected a direct native send. Only + /// the `nativeWrap` contract's `withdraw(...)` callback may credit the + /// ledger with native; everyone else reverts to keep accidental dust + /// from getting stranded. + error CallerNotNativeWrap(); + + // ------------------------------------------------------------------------- + // CelerWallet + // ------------------------------------------------------------------------- + + /// @notice `msg.sender` is not the wallet's operator. + error NotOperator(); + + /// @notice The given address is not an owner of the wallet. + error NotWalletOwner(); + + /// @notice A wallet with the derived id already exists. + error WalletIdOccupied(); + + /// @notice Native transfer via `payable.call{value:}` returned false. + error NativeTransferFailed(); + + // ------------------------------------------------------------------------- + // LedgerOperation: openChannel + // ------------------------------------------------------------------------- + + /// @notice `initDistribution.distribution.length != 2`. AgentPay only + /// supports two-peer channels. + error WrongPeerCount(); + + /// @notice Initializer's `ledgerAddress` did not match `address(this)`. + /// Same-chain wrong-ledger replay protection. + error LedgerAddressMismatch(); + + /// @notice The two peers' addresses are not strictly ascending. + /// Required so per-peer ordering is canonical without per-call sort. + error PeersNotAscending(); + + /// @notice ERC-20 path requires `msg.value == 0`. Distinct from + /// `MsgValueMismatch` (the native-funding amount mismatch). + error MsgValueMustBeZero(); + + /// @notice Initializer's `tokenType` is neither NATIVE nor ERC20. + error InvalidTokenType(); + + /// @notice ERC-20 path: token contract has no deployed bytecode. + error TokenNotContract(); + + /// @notice Token type / address pairing is inconsistent — NATIVE channel + /// with non-zero token address, or ERC-20 channel with `address(0)`. + error InvalidTokenAddress(); + + /// @notice Wallet creation derived `bytes32(0)` for the channel id — + /// the zero value is reserved as a non-channel sentinel. Defensive + /// check; only reachable on a hash-collision of the wallet-id derivation. + error ZeroChannelId(); + + /// @notice The derived channel id collides with an existing channel. + /// Defensive check that pairs with `ZeroChannelId`. + error ChannelIdOccupied(); + + // ------------------------------------------------------------------------- + // LedgerOperation: deposit / withdraw + // ------------------------------------------------------------------------- + + /// @notice Channel must be in the Operable status for this call. + /// Replaces the ambiguous `"Channel status error"` string. + error ChannelNotOperable(); + + /// @notice `msg.sender` is not one of the channel's peers. + error NotPeer(); + + /// @notice An unresolved unilateral withdraw intent already exists for + /// this channel. Veto or wait for it before opening a new one. + error WithdrawIntentExists(); + + /// @notice There is no unilateral withdraw intent to confirm or veto. + error NoWithdrawIntent(); + + /// @notice The dispute window has not yet elapsed; cannot confirm yet. + error DisputeNotElapsed(); + + /// @notice Recipient channel's token type / address does not match the + /// source channel's. Withdraw-into-channel must keep token consistent. + error RecipientChannelTokenMismatch(); + + // ------------------------------------------------------------------------- + // LedgerOperation: snapshot / settle + // ------------------------------------------------------------------------- + + /// @notice Multi-channel batch in `snapshotStates` / `intendSettle` was + /// not in ascending channel-id order. + error NonAscendingChannelIds(); + + /// @notice `intendSettle` peer-path requires the channel to be Operable + /// or Settling. + error ChannelNotOperableOrSettling(); + + /// @notice `intendSettle` non-peer path requires the channel to already + /// be Settling — a non-peer cannot start the settlement window. + error ChannelNotSettling(); + + /// @notice `intendSettle` cannot run once the dispute window has + /// closed; only `confirmSettle` is legal in that state. + error IntendSettleWindowClosed(); + + /// @notice Null-state `intendSettle` requires no prior settlement + /// attempt — but `settleFinalizedTime` is non-zero. + error SettlementAlreadyActive(); + + /// @notice `cooperativeSettle` settle accounts did not match the + /// channel's two peers (in canonical ascending order). + error SettlePeersMismatch(); + + /// @notice `cooperativeSettle` settle amounts do not sum to the + /// channel's total balance. + error SettleBalanceSumMismatch(); + + /// @notice `confirmSettle` cannot run before + /// `block.timestamp >= settleFinalizedTime`. + error ConfirmSettleTooEarly(); + + /// @notice A pay's resolution result is not yet final. Fires from + /// `PayRegistry.getPayAmounts` (the per-pay or caller-supplied + /// resolve deadline has not elapsed) and from `confirmSettle` (a + /// multi-segment pay list remains uncleared past its + /// `payClearDeadline`). + error PaymentNotFinalized(); + + /// @notice In `clearPays`, the head of the supplied pay-id list does + /// not hash to the on-chain `nextPayIdListHash`. + error PayListHashMismatch(); + + // ------------------------------------------------------------------------- + // LedgerMigrate + // ------------------------------------------------------------------------- + + /// @notice Migration request's `fromLedgerAddress` is not this ledger. + error FromLedgerAddressMismatch(); + + /// @notice Migration request's `toLedgerAddress` is not `msg.sender` + /// (the caller acting as the new ledger). + error ToLedgerAddressMismatch(); + + /// @notice Channel already exists on the destination ledger. + error ChannelAlreadyMigrated(); + + /// @notice Operatorship was not transferred to the new ledger by the + /// old ledger before `migrateChannelFrom`. + error OperatorshipNotTransferred(); + + // ------------------------------------------------------------------------- + // PayResolver + // ------------------------------------------------------------------------- + + /// @notice `pay.payResolver != address(this)`. Same-chain + /// wrong-resolver replay protection. + error ResolverAddressMismatch(); + + /// @notice A pay was previously resolved and the registry's stored + /// `resolveDeadline` has now passed — no further amount updates are + /// allowed (only the *first* resolution is unconditional; subsequent + /// updates are gated by this window). Distinct from {DeadlinePassed} + /// which fires when the pay's *own* signed `resolveDeadline` has + /// passed (the pay can never be resolved at all). Pairs with + /// {AmountNotGreater}, the other update-path gate. + error ResolveUpdateWindowClosed(); + + /// @notice New on-chain resolved amount must strictly exceed the + /// current resolved amount (monotonic resolution). + error AmountNotGreater(); + + /// @notice New on-chain resolve deadline cannot be zero. + error ZeroDeadline(); + + /// @notice Hash-lock condition's preimage hash did not match the + /// declared hash. + error PreimageMismatch(); + + /// @notice Dependent contract reports the condition is not yet + /// finalized. + error ConditionNotFinalized(); + + /// @notice Condition's declared type is outside the supported enum + /// range. + error InvalidConditionType(); + + // ------------------------------------------------------------------------- + // VirtContractResolver + // ------------------------------------------------------------------------- + + /// @notice The virtual address has already been resolved to a real + /// deployed address. + error VirtAddressOccupied(); + + /// @notice CREATE returned `address(0)` (deployment failed). + error CreateContractFailed(); + + /// @notice The virtual address has not been resolved yet. + error VirtAddressUnresolved(); + + // ------------------------------------------------------------------------- + // RouterRegistry + // ------------------------------------------------------------------------- + + /// @notice Router has already self-registered. + error RouterAlreadyRegistered(); + + /// @notice Router has not registered (or has been removed). + error RouterNotRegistered(); +} diff --git a/src/lib/data/Pb.sol b/src/lib/data/Pb.sol index 8317129..7481773 100644 --- a/src/lib/data/Pb.sol +++ b/src/lib/data/Pb.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // Code generated by protoc-gen-sol. DO NOT EDIT. -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; /** * @title Pb diff --git a/src/lib/data/PbChain.sol b/src/lib/data/PbChain.sol index c1c5890..468f061 100644 --- a/src/lib/data/PbChain.sol +++ b/src/lib/data/PbChain.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Code generated by protoc-gen-sol. DO NOT EDIT. // source: chain.proto -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./Pb.sol"; diff --git a/src/lib/data/PbEntity.sol b/src/lib/data/PbEntity.sol index bc43036..78405ad 100644 --- a/src/lib/data/PbEntity.sol +++ b/src/lib/data/PbEntity.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Code generated by protoc-gen-sol. DO NOT EDIT. // source: entity.proto -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./Pb.sol"; diff --git a/src/lib/ledgerlib/LedgerChannel.sol b/src/lib/ledgerlib/LedgerChannel.sol index 0d01961..c4e7268 100644 --- a/src/lib/ledgerlib/LedgerChannel.sol +++ b/src/lib/ledgerlib/LedgerChannel.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./LedgerStruct.sol"; +import "../AgentPayErrors.sol"; import "../../interfaces/ICelerLedger.sol"; import "../data/PbEntity.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -357,7 +358,7 @@ library LedgerChannel { } else if (_peer == _c.peerProfiles[1].peerAddr) { return 1; } else { - revert("Nonexist peer"); + revert AgentPayErrors.NotPeer(); } } diff --git a/src/lib/ledgerlib/LedgerMigrate.sol b/src/lib/ledgerlib/LedgerMigrate.sol index 5a9bf1c..5fb19aa 100644 --- a/src/lib/ledgerlib/LedgerMigrate.sol +++ b/src/lib/ledgerlib/LedgerMigrate.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./LedgerOperation.sol"; import "./LedgerChannel.sol"; import "./LedgerStruct.sol"; import "../../interfaces/ICelerLedger.sol"; +import "../AgentPayErrors.sol"; import "../data/PbChain.sol"; import "../data/PbEntity.sol"; @@ -38,13 +39,15 @@ library LedgerMigrate { LedgerStruct.Channel storage c = _self.channelMap[channelId]; address toLedgerAddr = migrationInfo.toLedgerAddress; - require(c.status == LedgerStruct.ChannelStatus.Operable || c.status == LedgerStruct.ChannelStatus.Settling); + require( + c.status == LedgerStruct.ChannelStatus.Operable || c.status == LedgerStruct.ChannelStatus.Settling, + AgentPayErrors.ChannelNotOperableOrSettling() + ); bytes32 h = keccak256(migrationRequest.channelMigrationInfo); - // use Channel Library instead - require(c._checkCoSignatures(h, migrationRequest.sigs), "Check co-sigs failed"); - require(migrationInfo.fromLedgerAddress == address(this), "From ledger address is not this"); - require(toLedgerAddr == msg.sender, "To ledger address is not msg.sender"); - require(block.timestamp <= migrationInfo.migrationDeadline, "Passed migration deadline"); + require(c._checkCoSignatures(h, migrationRequest.sigs), AgentPayErrors.InvalidCoSignatures()); + require(migrationInfo.fromLedgerAddress == address(this), AgentPayErrors.FromLedgerAddressMismatch()); + require(toLedgerAddr == msg.sender, AgentPayErrors.ToLedgerAddressMismatch()); + require(block.timestamp <= migrationInfo.migrationDeadline, AgentPayErrors.DeadlinePassed()); _self._updateChannelStatus(c, LedgerStruct.ChannelStatus.Migrated); c.migratedTo = toLedgerAddr; @@ -71,8 +74,8 @@ library LedgerMigrate { address payable fromLedgerAddrPayable = payable(_fromLedgerAddr); bytes32 channelId = ICelerLedger(fromLedgerAddrPayable).migrateChannelTo(_migrationRequest); LedgerStruct.Channel storage c = _self.channelMap[channelId]; - require(c.status == LedgerStruct.ChannelStatus.Uninitialized, "Immigrated channel already exists"); - require(_self.celerWallet.getOperator(channelId) == address(this), "Operatorship not transferred"); + require(c.status == LedgerStruct.ChannelStatus.Uninitialized, AgentPayErrors.ChannelAlreadyMigrated()); + require(_self.celerWallet.getOperator(channelId) == address(this), AgentPayErrors.OperatorshipNotTransferred()); _self._updateChannelStatus(c, LedgerStruct.ChannelStatus.Operable); // Do not migrate WithdrawIntent, in other words, migration will implicitly veto diff --git a/src/lib/ledgerlib/LedgerOperation.sol b/src/lib/ledgerlib/LedgerOperation.sol index e3254b5..9e96b76 100644 --- a/src/lib/ledgerlib/LedgerOperation.sol +++ b/src/lib/ledgerlib/LedgerOperation.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "./LedgerStruct.sol"; import "./LedgerChannel.sol"; +import "../AgentPayErrors.sol"; import "../../interfaces/ICelerWallet.sol"; import "../data/PbChain.sol"; import "../data/PbEntity.sol"; @@ -31,11 +32,14 @@ library LedgerOperation { PbChain.OpenChannelRequest memory openRequest = PbChain.decOpenChannelRequest(_openRequest); PbEntity.PaymentChannelInitializer memory channelInitializer = PbEntity.decPaymentChannelInitializer(openRequest.channelInitializer); - require(channelInitializer.initDistribution.distribution.length == 2, "Wrong length"); - require(block.timestamp <= channelInitializer.openDeadline, "Open deadline passed"); + require(channelInitializer.initDistribution.distribution.length == 2, AgentPayErrors.WrongPeerCount()); + require(block.timestamp <= channelInitializer.openDeadline, AgentPayErrors.DeadlinePassed()); // bind the co-signed initializer to its intended (chain, ledger) target - require(channelInitializer.chainId == block.chainid, "Wrong chain id for open"); - require(channelInitializer.ledgerAddress == address(this), "Wrong ledger for open"); + require( + channelInitializer.chainId == block.chainid, + AgentPayErrors.ChainIdMismatch(block.chainid, channelInitializer.chainId) + ); + require(channelInitializer.ledgerAddress == address(this), AgentPayErrors.LedgerAddressMismatch()); PbEntity.TokenInfo memory token = channelInitializer.initDistribution.token; uint256[2] memory amounts = [ @@ -47,7 +51,7 @@ library LedgerOperation { channelInitializer.initDistribution.distribution[1].account ]; // enforce ascending order of peers' addresses to simplify contract code - require(peerAddrs[0] < peerAddrs[1], "Peer addrs are not ascending"); + require(peerAddrs[0] < peerAddrs[1], AgentPayErrors.PeersNotAscending()); bytes32 h = keccak256(openRequest.channelInitializer); (bytes32 channelId, LedgerStruct.Channel storage c) = _createWallet(_self, _self.celerWallet, peerAddrs, h); @@ -60,20 +64,23 @@ library LedgerOperation { c.peerProfiles[1].peerAddr = peerAddrs[1]; c.peerProfiles[1].deposit = amounts[1]; - require(c._checkCoSignatures(h, openRequest.sigs), "Check co-sigs failed"); + require(c._checkCoSignatures(h, openRequest.sigs), AgentPayErrors.InvalidCoSignatures()); emit OpenChannel(channelId, uint256(token.tokenType), token.tokenAddress, peerAddrs, amounts); uint256 amtSum = amounts[0] + amounts[1]; // if total deposit is 0 if (amtSum == 0) { - require(msg.value == 0, "msg.value is not 0"); + require(msg.value == 0, AgentPayErrors.MsgValueMustBeZero()); return; } // if total deposit is larger than 0 if (_self.balanceLimitsEnabled) { - require(amtSum <= _self.balanceLimits[token.tokenAddress], "Balance exceeds limit"); + require( + amtSum <= _self.balanceLimits[token.tokenAddress], + AgentPayErrors.BalanceLimitExceeded(amtSum, _self.balanceLimits[token.tokenAddress]) + ); } _fundChannelOpen(_self, channelId, peerAddrs, amounts, amtSum, token, channelInitializer.msgValueReceiver); @@ -101,7 +108,10 @@ library LedgerOperation { uint256 _msgValueReceiver ) internal { if (_token.tokenType == PbEntity.TokenType.NATIVE) { - require(msg.value == _amounts[_msgValueReceiver], "msg.value mismatch"); + require( + msg.value == _amounts[_msgValueReceiver], + AgentPayErrors.MsgValueMismatch(_amounts[_msgValueReceiver], msg.value) + ); uint256 pid = 1 - _msgValueReceiver; if (_amounts[pid] > 0) { IERC20(address(_self.nativeWrap)).safeTransferFrom(_peerAddrs[pid], address(this), _amounts[pid]); @@ -111,7 +121,7 @@ library LedgerOperation { // a single combined depositNative covers both peers' contributions. _self.celerWallet.depositNative{value: _amtSum}(_channelId); } else if (_token.tokenType == PbEntity.TokenType.ERC20) { - require(msg.value == 0, "msg.value is not 0"); + require(msg.value == 0, AgentPayErrors.MsgValueMustBeZero()); IERC20 erc20Token = IERC20(_token.tokenAddress); for (uint256 i = 0; i < 2; i++) { @@ -156,7 +166,7 @@ library LedgerOperation { _self.celerWallet.depositNative{value: _transferFromAmount}(_channelId); } } else if (c.token.tokenType == PbEntity.TokenType.ERC20) { - require(msgValue == 0, "msg.value is not 0"); + require(msgValue == 0, AgentPayErrors.MsgValueMustBeZero()); IERC20 erc20Token = IERC20(c.token.tokenAddress); erc20Token.safeTransferFrom(msg.sender, address(this), _transferFromAmount); @@ -189,14 +199,16 @@ library LedgerOperation { bytes32 currentChannelId = simplexState.channelId; LedgerStruct.Channel storage c = _self.channelMap[currentChannelId]; - require(c.status == LedgerStruct.ChannelStatus.Operable, "Channel status error"); + require(c.status == LedgerStruct.ChannelStatus.Operable, AgentPayErrors.ChannelNotOperable()); bytes32 stateHash = keccak256(signedSimplexStateArray.signedSimplexStates[i].simplexState); bytes[] memory sigs = signedSimplexStateArray.signedSimplexStates[i].sigs; - require(c._checkCoSignatures(stateHash, sigs), "Check co-sigs failed"); + require(c._checkCoSignatures(stateHash, sigs), AgentPayErrors.InvalidCoSignatures()); uint256 peerFromId = c._getPeerId(simplexState.peerFrom); LedgerStruct.PeerState storage state = c.peerProfiles[peerFromId].state; - require(simplexState.seqNum > state.seqNum, "seqNum error"); + require( + simplexState.seqNum > state.seqNum, AgentPayErrors.SeqNumOutOfOrder(state.seqNum, simplexState.seqNum) + ); // no need to update nextPayIdListHash and payClearDeadline for snapshot purpose state.seqNum = simplexState.seqNum; @@ -209,7 +221,7 @@ library LedgerOperation { simplexState = PbEntity.decSimplexPaymentChannel(signedSimplexStateArray.signedSimplexStates[i + 1].simplexState); // enforce channelIds of simplex states are ascending - require(currentChannelId <= simplexState.channelId, "Non-ascending channelIds"); + require(currentChannelId <= simplexState.channelId, AgentPayErrors.NonAscendingChannelIds()); if (currentChannelId < simplexState.channelId) { emit SnapshotStates(currentChannelId, c._getStateSeqNums()); } @@ -237,11 +249,11 @@ library LedgerOperation { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; LedgerStruct.WithdrawIntent storage withdrawIntent = c.withdrawIntent; address receiver = msg.sender; - require(c.status == LedgerStruct.ChannelStatus.Operable, "Channel status error"); + require(c.status == LedgerStruct.ChannelStatus.Operable, AgentPayErrors.ChannelNotOperable()); // withdrawIntent.receiver is address(0) if and only if there is no pending WithdrawIntent, // because withdrawIntent.receiver may only be set as msg.sender which can't be address(0). - require(withdrawIntent.receiver == address(0), "Pending withdraw intent exists"); - require(c._isPeer(receiver)); + require(withdrawIntent.receiver == address(0), AgentPayErrors.WithdrawIntentExists()); + require(c._isPeer(receiver), AgentPayErrors.NotPeer()); withdrawIntent.receiver = receiver; withdrawIntent.amount = _amount; @@ -259,9 +271,9 @@ library LedgerOperation { */ function confirmWithdraw(LedgerStruct.Ledger storage _self, bytes32 _channelId) external { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; - require(c.status == LedgerStruct.ChannelStatus.Operable, "Channel status error"); - require(c.withdrawIntent.receiver != address(0), "No pending withdraw intent"); - require(block.timestamp >= c.withdrawIntent.requestTime + c.disputeTimeout, "Dispute not timeout"); + require(c.status == LedgerStruct.ChannelStatus.Operable, AgentPayErrors.ChannelNotOperable()); + require(c.withdrawIntent.receiver != address(0), AgentPayErrors.NoWithdrawIntent()); + require(block.timestamp >= c.withdrawIntent.requestTime + c.disputeTimeout, AgentPayErrors.DisputeNotElapsed()); address receiver = c.withdrawIntent.receiver; uint256 amount = c.withdrawIntent.amount; @@ -274,7 +286,7 @@ library LedgerOperation { LedgerStruct.PeerProfile[2] storage peerProfiles = c.peerProfiles; uint256 withdrawLimit = peerProfiles[rid].deposit + peerProfiles[pid].state.transferOut - peerProfiles[rid].withdrawal - peerProfiles[rid].state.transferOut - peerProfiles[rid].state.pendingPayOut; - require(amount <= withdrawLimit, "Exceed withdraw limit"); + require(amount <= withdrawLimit, AgentPayErrors.WithdrawLimitExceeded(amount, withdrawLimit)); c._addWithdrawal(receiver, amount); @@ -293,9 +305,9 @@ library LedgerOperation { */ function vetoWithdraw(LedgerStruct.Ledger storage _self, bytes32 _channelId) external { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; - require(c.status == LedgerStruct.ChannelStatus.Operable, "Channel status error"); - require(c.withdrawIntent.receiver != address(0), "No pending withdraw intent"); - require(c._isPeer(msg.sender), "msg.sender is not peer"); + require(c.status == LedgerStruct.ChannelStatus.Operable, AgentPayErrors.ChannelNotOperable()); + require(c.withdrawIntent.receiver != address(0), AgentPayErrors.NoWithdrawIntent()); + require(c._isPeer(msg.sender), AgentPayErrors.NotPeer()); delete c.withdrawIntent; @@ -318,12 +330,15 @@ library LedgerOperation { bytes32 recipientChannelId = withdrawInfo.recipientChannelId; LedgerStruct.Channel storage c = _self.channelMap[channelId]; - require(c.status == LedgerStruct.ChannelStatus.Operable, "Channel status error"); + require(c.status == LedgerStruct.ChannelStatus.Operable, AgentPayErrors.ChannelNotOperable()); bytes32 h = keccak256(cooperativeWithdrawRequest.withdrawInfo); - require(c._checkCoSignatures(h, cooperativeWithdrawRequest.sigs), "Check co-sigs failed"); + require(c._checkCoSignatures(h, cooperativeWithdrawRequest.sigs), AgentPayErrors.InvalidCoSignatures()); // require an increment of exactly 1 for seqNum of each cooperative withdraw request - require(withdrawInfo.seqNum - c.cooperativeWithdrawSeqNum == 1, "seqNum error"); - require(block.timestamp <= withdrawInfo.withdrawDeadline, "Withdraw deadline passed"); + require( + withdrawInfo.seqNum - c.cooperativeWithdrawSeqNum == 1, + AgentPayErrors.SeqNumOutOfOrder(c.cooperativeWithdrawSeqNum, withdrawInfo.seqNum) + ); + require(block.timestamp <= withdrawInfo.withdrawDeadline, AgentPayErrors.DeadlinePassed()); address receiver = withdrawInfo.withdraw.account; c.cooperativeWithdrawSeqNum = withdrawInfo.seqNum; @@ -363,14 +378,15 @@ library LedgerOperation { if (c._isPeer(msg.sender)) { require( c.status == LedgerStruct.ChannelStatus.Operable || c.status == LedgerStruct.ChannelStatus.Settling, - "Peer channel status error" + AgentPayErrors.ChannelNotOperableOrSettling() ); } else { // A nonpeer cannot be the first one to call intendSettle - require(c.status == LedgerStruct.ChannelStatus.Settling, "Nonpeer channel status error"); + require(c.status == LedgerStruct.ChannelStatus.Settling, AgentPayErrors.ChannelNotSettling()); } require( - c.settleFinalizedTime == 0 || block.timestamp < c.settleFinalizedTime, "Settle has already finalized" + c.settleFinalizedTime == 0 || block.timestamp < c.settleFinalizedTime, + AgentPayErrors.IntendSettleWindowClosed() ); bytes32 stateHash = keccak256(signedSimplexStateArray.signedSimplexStates[i].simplexState); @@ -378,15 +394,21 @@ library LedgerOperation { if (simplexState.seqNum > 0) { // non-null state - require(c._checkCoSignatures(stateHash, sigs), "Check co-sigs failed"); + require(c._checkCoSignatures(stateHash, sigs), AgentPayErrors.InvalidCoSignatures()); uint256 peerFromId = c._getPeerId(simplexState.peerFrom); LedgerStruct.PeerState storage state = c.peerProfiles[peerFromId].state; // ensure each state can be intendSettle at most once if (c.status == LedgerStruct.ChannelStatus.Operable) { // "==" is the case of cooperative on-chain checkpoint - require(simplexState.seqNum >= state.seqNum, "seqNum error"); + require( + simplexState.seqNum >= state.seqNum, + AgentPayErrors.SeqNumOutOfOrder(state.seqNum, simplexState.seqNum) + ); } else if (c.status == LedgerStruct.ChannelStatus.Settling) { - require(simplexState.seqNum > state.seqNum, "seqNum error"); + require( + simplexState.seqNum > state.seqNum, + AgentPayErrors.SeqNumOutOfOrder(state.seqNum, simplexState.seqNum) + ); } else { assert(false); } @@ -407,8 +429,10 @@ library LedgerOperation { } else if (simplexState.seqNum == 0) { // null state // this implies both stored seqNums are 0 - require(c.settleFinalizedTime == 0, "intendSettle before"); - require(sigs.length == 1 && c._checkSingleSignature(stateHash, sigs[0]), "Check sig failed"); + require(c.settleFinalizedTime == 0, AgentPayErrors.SettlementAlreadyActive()); + require( + sigs.length == 1 && c._checkSingleSignature(stateHash, sigs[0]), AgentPayErrors.InvalidSignature() + ); } else { assert(false); } @@ -419,7 +443,7 @@ library LedgerOperation { simplexState = PbEntity.decSimplexPaymentChannel(signedSimplexStateArray.signedSimplexStates[i + 1].simplexState); // enforce channelIds of simplex states are ascending - require(currentChannelId <= simplexState.channelId, "Non-ascending channelIds"); + require(currentChannelId <= simplexState.channelId, AgentPayErrors.NonAscendingChannelIds()); if (currentChannelId < simplexState.channelId) { _updateOverallStatesByIntendState(_self, currentChannelId); } @@ -443,12 +467,12 @@ library LedgerOperation { bytes calldata _payIdList ) external { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; - require(c.status == LedgerStruct.ChannelStatus.Settling, "Channel status error"); + require(c.status == LedgerStruct.ChannelStatus.Settling, AgentPayErrors.ChannelNotSettling()); uint256 peerFromId = c._getPeerId(_peerFrom); bytes32 listHash = keccak256(_payIdList); LedgerStruct.PeerState storage state = c.peerProfiles[peerFromId].state; - require(state.nextPayIdListHash == listHash, "List hash mismatch"); + require(state.nextPayIdListHash == listHash, AgentPayErrors.PayListHashMismatch()); PbEntity.PayIdList memory payIdList = PbEntity.decPayIdList(_payIdList); state.nextPayIdListHash = payIdList.nextListHash; @@ -465,9 +489,9 @@ library LedgerOperation { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; LedgerStruct.PeerProfile[2] storage peerProfiles = c.peerProfiles; uint256 nowTs = block.timestamp; - require(c.status == LedgerStruct.ChannelStatus.Settling, "Channel status error"); + require(c.status == LedgerStruct.ChannelStatus.Settling, AgentPayErrors.ChannelNotSettling()); // require no new intendSettle can be called - require(nowTs >= c.settleFinalizedTime, "Settle is not finalized"); + require(nowTs >= c.settleFinalizedTime, AgentPayErrors.ConfirmSettleTooEarly()); // require channel status of current intendSettle has been finalized, // namely all payments have already been either cleared or expired. @@ -481,7 +505,7 @@ library LedgerOperation { (peerProfiles[0].state.nextPayIdListHash == bytes32(0) || nowTs > peerProfiles[0].state.payClearDeadline) && (peerProfiles[1].state.nextPayIdListHash == bytes32(0) || nowTs > peerProfiles[1].state.payClearDeadline), - "Payments are not finalized" + AgentPayErrors.PaymentNotFinalized() ); (bool validBalance, uint256[2] memory settleBalance) = c._validateSettleBalance(); @@ -514,26 +538,31 @@ library LedgerOperation { LedgerStruct.Channel storage c = _self.channelMap[channelId]; require( c.status == LedgerStruct.ChannelStatus.Operable || c.status == LedgerStruct.ChannelStatus.Settling, - "Channel status error" + AgentPayErrors.ChannelNotOperableOrSettling() ); bytes32 h = keccak256(settleRequest.settleInfo); - require(c._checkCoSignatures(h, settleRequest.sigs), "Check co-sigs failed"); + require(c._checkCoSignatures(h, settleRequest.sigs), AgentPayErrors.InvalidCoSignatures()); address[2] memory peerAddrs = [c.peerProfiles[0].peerAddr, c.peerProfiles[1].peerAddr]; - require( - settleInfo.seqNum > c.peerProfiles[0].state.seqNum && settleInfo.seqNum > c.peerProfiles[1].state.seqNum, - "seqNum error" - ); - require(settleInfo.settleDeadline >= block.timestamp, "Settle deadline passed"); + // settleInfo.seqNum must strictly exceed both simplex states' on-chain seqNums. + // For the parameterized error we surface the larger of the two on-chain seqNums + // since that's the binding minimum the proposed seqNum must clear. + { + uint256 maxSeq = c.peerProfiles[0].state.seqNum > c.peerProfiles[1].state.seqNum + ? c.peerProfiles[0].state.seqNum + : c.peerProfiles[1].state.seqNum; + require(settleInfo.seqNum > maxSeq, AgentPayErrors.SeqNumOutOfOrder(maxSeq, settleInfo.seqNum)); + } + require(settleInfo.settleDeadline >= block.timestamp, AgentPayErrors.DeadlinePassed()); // require distribution is consistent with the order of peerAddrs in channel require( settleInfo.settleBalance[0].account == peerAddrs[0] && settleInfo.settleBalance[1].account == peerAddrs[1], - "Settle accounts mismatch" + AgentPayErrors.SettlePeersMismatch() ); uint256[2] memory settleBalance = [settleInfo.settleBalance[0].amt, settleInfo.settleBalance[1].amt]; - require(settleBalance[0] + settleBalance[1] == c.getTotalBalance(), "Balance sum mismatch"); + require(settleBalance[0] + settleBalance[1] == c.getTotalBalance(), AgentPayErrors.SettleBalanceSumMismatch()); _updateChannelStatus(_self, c, LedgerStruct.ChannelStatus.Closed); @@ -576,10 +605,10 @@ library LedgerOperation { // use walletId as channelId bytes32 channelId = _w.create(owners, address(this), _nonce); // 0 is reserved for non-channel indication - require(channelId != bytes32(0), "channelId gets 0"); + require(channelId != bytes32(0), AgentPayErrors.ZeroChannelId()); LedgerStruct.Channel storage c = _self.channelMap[channelId]; // No harm in having this check in case of keccak256 being broken - require(c.status == LedgerStruct.ChannelStatus.Uninitialized, "Occupied channelId"); + require(c.status == LedgerStruct.ChannelStatus.Uninitialized, AgentPayErrors.ChannelIdOccupied()); return (channelId, c); } @@ -595,12 +624,17 @@ library LedgerOperation { internal { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; - require(c.status == LedgerStruct.ChannelStatus.Operable, "Channel status error"); + require(c.status == LedgerStruct.ChannelStatus.Operable, AgentPayErrors.ChannelNotOperable()); // this implicitly require _receiver be a peer uint256 rid = c._getPeerId(_receiver); if (_self.balanceLimitsEnabled) { - require(_amount + c.getTotalBalance() <= _self.balanceLimits[c.token.tokenAddress], "Balance exceeds limit"); + require( + _amount + c.getTotalBalance() <= _self.balanceLimits[c.token.tokenAddress], + AgentPayErrors.BalanceLimitExceeded( + _amount + c.getTotalBalance(), _self.balanceLimits[c.token.tokenAddress] + ) + ); } c.peerProfiles[rid].deposit = c.peerProfiles[rid].deposit + _amount; @@ -656,7 +690,7 @@ library LedgerOperation { require( c.token.tokenType == recipientChannel.token.tokenType && c.token.tokenAddress == recipientChannel.token.tokenAddress, - "Token mismatch of recipient channel" + AgentPayErrors.RecipientChannelTokenMismatch() ); _addDeposit(_self, _recipientChannelId, _receiver, _amount); @@ -765,10 +799,10 @@ library LedgerOperation { */ function _validateTokenInfo(PbEntity.TokenInfo memory _token) internal view returns (PbEntity.TokenInfo memory) { if (_token.tokenType == PbEntity.TokenType.NATIVE) { - require(_token.tokenAddress == address(0)); + require(_token.tokenAddress == address(0), AgentPayErrors.InvalidTokenAddress()); } else if (_token.tokenType == PbEntity.TokenType.ERC20) { - require(_token.tokenAddress != address(0)); - require(_token.tokenAddress.code.length > 0); + require(_token.tokenAddress != address(0), AgentPayErrors.InvalidTokenAddress()); + require(_token.tokenAddress.code.length > 0, AgentPayErrors.TokenNotContract()); } else { assert(false); } diff --git a/src/lib/ledgerlib/LedgerStruct.sol b/src/lib/ledgerlib/LedgerStruct.sol index 8a00b0c..0347521 100644 --- a/src/lib/ledgerlib/LedgerStruct.sol +++ b/src/lib/ledgerlib/LedgerStruct.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.26; import "../../interfaces/ICelerWallet.sol"; import "../../interfaces/INativeWrap.sol"; diff --git a/test/CelerLedger.ERC20.t.sol b/test/CelerLedger.ERC20.t.sol index 732412a..1f8c9ca 100644 --- a/test/CelerLedger.ERC20.t.sol +++ b/test/CelerLedger.ERC20.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {LedgerTestBase} from "./utils/LedgerTestBase.t.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {LedgerStruct} from "../src/lib/ledgerlib/LedgerStruct.sol"; /** @@ -39,7 +40,7 @@ contract CelerLedgerErc20Test is LedgerTestBase { function test_openErc20Channel_withFunds_revertsBeforeBalanceLimit() public { uint256 deadline = openDeadlineCursor++; (bytes memory request,,) = _buildOpenErc20(address(erc20), [uint256(100), 200], deadline); - vm.expectRevert(bytes("Balance exceeds limit")); + vm.expectPartialRevert(AgentPayErrors.BalanceLimitExceeded.selector); celerLedger.openChannel(request); } @@ -84,7 +85,7 @@ contract CelerLedgerErc20Test is LedgerTestBase { _setErc20BalanceLimit(50); bytes32 channelId = _openZeroErc20Channel(); - vm.expectRevert(bytes("Balance exceeds limit")); + vm.expectPartialRevert(AgentPayErrors.BalanceLimitExceeded.selector); vm.prank(peer0); celerLedger.deposit(channelId, peer0, 100); } @@ -95,7 +96,7 @@ contract CelerLedgerErc20Test is LedgerTestBase { // ERC20 channel deposit must not have msg.value. vm.deal(peer0, 1 ether); - vm.expectRevert(bytes("msg.value is not 0")); + vm.expectRevert(AgentPayErrors.MsgValueMustBeZero.selector); vm.prank(peer0); celerLedger.deposit{value: 1}(channelId, peer0, 25); } diff --git a/test/CelerLedger.ETH.t.sol b/test/CelerLedger.ETH.t.sol index 047507e..b02f21d 100644 --- a/test/CelerLedger.ETH.t.sol +++ b/test/CelerLedger.ETH.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {LedgerTestBase} from "./utils/LedgerTestBase.t.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {LedgerStruct} from "../src/lib/ledgerlib/LedgerStruct.sol"; import {CelerLedger} from "../src/CelerLedger.sol"; import {Fixtures} from "./utils/Fixtures.sol"; @@ -54,7 +55,7 @@ contract CelerLedgerEthTest is LedgerTestBase { function test_openChannel_afterDeadline_reverts() public { (bytes memory request,,) = _buildOpenEth([uint256(0), 0], 0, block.timestamp - 1); - vm.expectRevert(bytes("Open deadline passed")); + vm.expectRevert(AgentPayErrors.DeadlinePassed.selector); celerLedger.openChannel(request); } @@ -64,7 +65,7 @@ contract CelerLedgerEthTest is LedgerTestBase { (bytes memory request,,) = _buildOpenEth([uint256(0), 0], 0, deadline); celerLedger.openChannel(request); - vm.expectRevert(bytes("Occupied wallet id")); + vm.expectRevert(AgentPayErrors.WalletIdOccupied.selector); celerLedger.openChannel(request); } @@ -72,7 +73,9 @@ contract CelerLedgerEthTest is LedgerTestBase { // Default: balance limits enabled but limit for ETH is unset (== 0). (bytes memory request,,) = _buildOpenEth([uint256(100), 200], 0, openDeadlineCursor++); - vm.expectRevert(bytes("Balance exceeds limit")); + // Full-payload assertion — locks down (attempted, limit). attempted = 300 + // (sum of both peers' deposits), limit = 0 (unconfigured). + vm.expectRevert(abi.encodeWithSelector(AgentPayErrors.BalanceLimitExceeded.selector, uint256(300), uint256(0))); vm.prank(peer0); celerLedger.openChannel{value: 100}(request); } @@ -111,7 +114,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes[] memory sigs = SignUtil.coSign(peer0Pk, peer1Pk, initializer); bytes memory request = Fixtures.encOpenChannelRequest(initializer, sigs); - vm.expectRevert(bytes("Wrong chain id for open")); + vm.expectPartialRevert(AgentPayErrors.ChainIdMismatch.selector); celerLedger.openChannel(request); } @@ -136,7 +139,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes[] memory sigs = SignUtil.coSign(peer0Pk, peer1Pk, initializer); bytes memory request = Fixtures.encOpenChannelRequest(initializer, sigs); - vm.expectRevert(bytes("Wrong ledger for open")); + vm.expectRevert(AgentPayErrors.LedgerAddressMismatch.selector); celerLedger.openChannel(request); } @@ -162,7 +165,7 @@ contract CelerLedgerEthTest is LedgerTestBase { // Switch chain context — the same signed payload is now mismatched. vm.chainId(block.chainid + 1); - vm.expectRevert(bytes("Wrong chain id for open")); + vm.expectPartialRevert(AgentPayErrors.ChainIdMismatch.selector); celerLedger.openChannel(request); } @@ -187,7 +190,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes[] memory sigs = SignUtil.coSign(peer0Pk, peer1Pk, initializer); bytes memory request = Fixtures.encOpenChannelRequest(initializer, sigs); - vm.expectRevert(bytes("Wrong ledger for open")); + vm.expectRevert(AgentPayErrors.LedgerAddressMismatch.selector); siblingLedger.openChannel(request); } @@ -297,7 +300,7 @@ contract CelerLedgerEthTest is LedgerTestBase { _setEthBalanceLimit(100); bytes32 channelId = _openZeroEthChannel(); - vm.expectRevert(bytes("Balance exceeds limit")); + vm.expectPartialRevert(AgentPayErrors.BalanceLimitExceeded.selector); vm.prank(peer0); celerLedger.deposit{value: 200}(channelId, peer0, 0); } @@ -307,7 +310,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes32 channelId = _openZeroEthChannel(); vm.deal(stranger, 1 ether); - vm.expectRevert(bytes("Nonexist peer")); + vm.expectRevert(AgentPayErrors.NotPeer.selector); vm.prank(stranger); celerLedger.deposit{value: 25}(channelId, stranger, 0); } @@ -344,7 +347,7 @@ contract CelerLedgerEthTest is LedgerTestBase { receivers[0] = peer0; uint256[] memory amounts = new uint256[](2); - vm.expectRevert(bytes("Lengths do not match")); + vm.expectPartialRevert(AgentPayErrors.LengthMismatch.selector); celerLedger.depositInBatch(ids, receivers, amounts); } @@ -389,7 +392,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes memory request = _buildCoopWithdraw(channelId, 1, peer0, 100, block.timestamp - 1, bytes32(0)); - vm.expectRevert(bytes("Withdraw deadline passed")); + vm.expectRevert(AgentPayErrors.DeadlinePassed.selector); celerLedger.cooperativeWithdraw(request); } @@ -403,7 +406,9 @@ contract CelerLedgerEthTest is LedgerTestBase { // Try seqNum=1 again (should be 2 next) — reverts. bytes memory r2 = _buildCoopWithdraw(channelId, 1, peer0, 50, block.timestamp + 1000, bytes32(0)); - vm.expectRevert(bytes("seqNum error")); + // Full-payload assertion — locks down (onchain, proposed). After the + // first withdraw the on-chain seqNum is 1; the test resubmits 1. + vm.expectRevert(abi.encodeWithSelector(AgentPayErrors.SeqNumOutOfOrder.selector, uint256(1), uint256(1))); celerLedger.cooperativeWithdraw(r2); } @@ -425,7 +430,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes32 ethChannel = _openFundedEthChannel([uint256(200), 0]); bytes memory request = _buildCoopWithdraw(ethChannel, 1, peer0, 50, block.timestamp + 1000, erc20Channel); - vm.expectRevert(bytes("Token mismatch of recipient channel")); + vm.expectRevert(AgentPayErrors.RecipientChannelTokenMismatch.selector); celerLedger.cooperativeWithdraw(request); } @@ -450,7 +455,7 @@ contract CelerLedgerEthTest is LedgerTestBase { sigs[1] = SignUtil.sign(peer1Pk, body); bytes memory request = Fixtures.encCooperativeWithdrawRequest(body, sigs); - vm.expectRevert(bytes("Check co-sigs failed")); + vm.expectRevert(AgentPayErrors.InvalidCoSignatures.selector); celerLedger.cooperativeWithdraw(request); } @@ -487,7 +492,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendWithdraw(channelId, 30, bytes32(0)); - vm.expectRevert(bytes("Pending withdraw intent exists")); + vm.expectRevert(AgentPayErrors.WithdrawIntentExists.selector); vm.prank(peer1); celerLedger.intendWithdraw(channelId, 50, bytes32(0)); } @@ -530,7 +535,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendWithdraw(channelId, 50, bytes32(0)); - vm.expectRevert(bytes("Dispute not timeout")); + vm.expectRevert(AgentPayErrors.DisputeNotElapsed.selector); celerLedger.confirmWithdraw(channelId); } @@ -616,7 +621,7 @@ contract CelerLedgerEthTest is LedgerTestBase { states[1] = sLow; bytes memory array = Fixtures.encSignedSimplexStateArray(states); - vm.expectRevert(bytes("Non-ascending channelIds")); + vm.expectRevert(AgentPayErrors.NonAscendingChannelIds.selector); celerLedger.snapshotStates(array); } @@ -646,7 +651,7 @@ contract CelerLedgerEthTest is LedgerTestBase { states[0] = signed; bytes memory array = Fixtures.encSignedSimplexStateArray(states); - vm.expectRevert(bytes("Check co-sigs failed")); + vm.expectRevert(AgentPayErrors.InvalidCoSignatures.selector); celerLedger.snapshotStates(array); } @@ -676,7 +681,7 @@ contract CelerLedgerEthTest is LedgerTestBase { // Total = 200 but settleBalance = 100 + 50 = 150 → revert. bytes memory request = _buildCoopSettle(channelId, 1, [uint256(100), 50], block.timestamp + 1000); - vm.expectRevert(bytes("Balance sum mismatch")); + vm.expectRevert(AgentPayErrors.SettleBalanceSumMismatch.selector); celerLedger.cooperativeSettle(request); } @@ -687,7 +692,7 @@ contract CelerLedgerEthTest is LedgerTestBase { // Deadline already in the past. bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.timestamp - 1); - vm.expectRevert(bytes("Settle deadline passed")); + vm.expectRevert(AgentPayErrors.DeadlinePassed.selector); celerLedger.cooperativeSettle(request); } @@ -701,7 +706,7 @@ contract CelerLedgerEthTest is LedgerTestBase { // settleInfo seqNum must be > both peer seqNums; 1 fails (peer0 already 1). bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.timestamp + 1000); - vm.expectRevert(bytes("seqNum error")); + vm.expectPartialRevert(AgentPayErrors.SeqNumOutOfOrder.selector); celerLedger.cooperativeSettle(request); } @@ -721,7 +726,7 @@ contract CelerLedgerEthTest is LedgerTestBase { sigs[0] = SignUtil.sign(peer0Pk, body); bytes memory request = Fixtures.encCooperativeSettleRequest(body, sigs); - vm.expectRevert(bytes("Check co-sigs failed")); + vm.expectRevert(AgentPayErrors.InvalidCoSignatures.selector); celerLedger.cooperativeSettle(request); } @@ -968,7 +973,7 @@ contract CelerLedgerEthTest is LedgerTestBase { // Past dispute timeout but before pay_clear_deadline → reverts. vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); assertTrue(block.timestamp < clearDeadline); - vm.expectRevert(bytes("Payments are not finalized")); + vm.expectRevert(AgentPayErrors.PaymentNotFinalized.selector); celerLedger.confirmSettle(channelId); // Snapshot just before the post-deadline confirmSettle: head was cleared @@ -1037,8 +1042,8 @@ contract CelerLedgerEthTest is LedgerTestBase { ids[0] = bytes32(uint256(1)); bytes memory list = Fixtures.encPayIdList(ids, bytes32(0)); - // Channel is Operable, not Settling. - vm.expectRevert(bytes("Channel status error")); + // Channel is Operable; clearPays requires Settling. + vm.expectRevert(AgentPayErrors.ChannelNotSettling.selector); celerLedger.clearPays(channelId, peer0, list); } @@ -1058,7 +1063,7 @@ contract CelerLedgerEthTest is LedgerTestBase { ids[0] = bytes32(uint256(1)); bytes memory list = Fixtures.encPayIdList(ids, bytes32(0)); - vm.expectRevert(bytes("List hash mismatch")); + vm.expectRevert(AgentPayErrors.PayListHashMismatch.selector); celerLedger.clearPays(channelId, peer0, list); } @@ -1072,7 +1077,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendSettle(array); - vm.expectRevert(bytes("Settle is not finalized")); + vm.expectRevert(AgentPayErrors.ConfirmSettleTooEarly.selector); celerLedger.confirmSettle(channelId); } @@ -1080,8 +1085,8 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.disableBalanceLimits(); bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); - // Channel is Operable, not Settling. - vm.expectRevert(bytes("Channel status error")); + // Channel is Operable; confirmSettle requires Settling. + vm.expectRevert(AgentPayErrors.ChannelNotSettling.selector); celerLedger.confirmSettle(channelId); } diff --git a/test/CelerLedger.Migrate.t.sol b/test/CelerLedger.Migrate.t.sol index d839b48..ec70243 100644 --- a/test/CelerLedger.Migrate.t.sol +++ b/test/CelerLedger.Migrate.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {LedgerTestBase} from "./utils/LedgerTestBase.t.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {LedgerStruct} from "../src/lib/ledgerlib/LedgerStruct.sol"; import {CelerLedger} from "../src/CelerLedger.sol"; import {Fixtures} from "./utils/Fixtures.sol"; @@ -95,7 +96,7 @@ contract CelerLedgerMigrateTest is LedgerTestBase { bytes memory request = _buildMigrationRequest(channelId, address(celerLedger), address(celerLedgerNew), block.timestamp - 1); - vm.expectRevert(bytes("Passed migration deadline")); + vm.expectRevert(AgentPayErrors.DeadlinePassed.selector); celerLedgerNew.migrateChannelFrom(address(celerLedger), request); } @@ -105,7 +106,7 @@ contract CelerLedgerMigrateTest is LedgerTestBase { // Migration request claims a different fromLedger (not address(celerLedger)). bytes memory request = _buildMigrationRequest(channelId, stranger, address(celerLedgerNew), 99_999_999); - vm.expectRevert(bytes("From ledger address is not this")); + vm.expectRevert(AgentPayErrors.FromLedgerAddressMismatch.selector); celerLedgerNew.migrateChannelFrom(address(celerLedger), request); } diff --git a/test/CelerWallet.t.sol b/test/CelerWallet.t.sol index 7ce1226..48dde7c 100644 --- a/test/CelerWallet.t.sol +++ b/test/CelerWallet.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {CelerWallet} from "../src/CelerWallet.sol"; import {WalletTestHelper} from "../src/helper/WalletTestHelper.sol"; import {ERC20ExampleToken} from "../src/helper/ERC20ExampleToken.sol"; @@ -106,7 +107,7 @@ contract CelerWalletTest is Test { address[] memory owners = new address[](2); owners[0] = owner0; owners[1] = owner1; - vm.expectRevert(bytes("New operator is address(0)")); + vm.expectRevert(AgentPayErrors.ZeroAddress.selector); wallet.create(owners, address(0), bytes32(uint256(99))); } @@ -115,7 +116,7 @@ contract CelerWalletTest is Test { owners[0] = owner0; owners[1] = owner1; wallet.create(owners, operator, bytes32(uint256(42))); - vm.expectRevert(bytes("Occupied wallet id")); + vm.expectRevert(AgentPayErrors.WalletIdOccupied.selector); wallet.create(owners, operator, bytes32(uint256(42))); } @@ -168,13 +169,13 @@ contract CelerWalletTest is Test { } function test_withdraw_revertsForNonOperator() public { - vm.expectRevert(bytes("msg.sender is not operator")); + vm.expectRevert(AgentPayErrors.NotOperator.selector); vm.prank(stranger); wallet.withdraw(walletId, address(token), owner0, 50); } function test_withdraw_revertsForNonOwnerReceiver() public { - vm.expectRevert(bytes("Given address is not wallet owner")); + vm.expectRevert(AgentPayErrors.NotWalletOwner.selector); vm.prank(operator); wallet.withdraw(walletId, address(token), stranger, 50); } @@ -194,7 +195,7 @@ contract CelerWalletTest is Test { } function test_transferToWallet_revertsForReceiverNotInBoth() public { - vm.expectRevert(bytes("Given address is not wallet owner")); + vm.expectRevert(AgentPayErrors.NotWalletOwner.selector); vm.prank(operator); wallet.transferToWallet(walletId, walletId2, address(token), stranger, 50); } @@ -213,7 +214,7 @@ contract CelerWalletTest is Test { } function test_transferOperatorship_revertsForNonOperator() public { - vm.expectRevert(bytes("msg.sender is not operator")); + vm.expectRevert(AgentPayErrors.NotOperator.selector); vm.prank(stranger); wallet.transferOperatorship(walletId, newOperator); } @@ -257,13 +258,13 @@ contract CelerWalletTest is Test { } function test_proposeNewOperator_revertsForZeroAddress() public { - vm.expectRevert(bytes("New operator is address(0)")); + vm.expectRevert(AgentPayErrors.ZeroAddress.selector); vm.prank(owner0); wallet.proposeNewOperator(walletId, address(0)); } function test_proposeNewOperator_revertsForNonOwner() public { - vm.expectRevert(bytes("Given address is not wallet owner")); + vm.expectRevert(AgentPayErrors.NotWalletOwner.selector); vm.prank(stranger); wallet.proposeNewOperator(walletId, newOperator); } @@ -378,7 +379,7 @@ contract CelerWalletTest is Test { // ========================================================================= function test_getProposalVote_revertsForNonOwner() public { - vm.expectRevert(bytes("Given address is not wallet owner")); + vm.expectRevert(AgentPayErrors.NotWalletOwner.selector); wallet.getProposalVote(walletId, stranger); } diff --git a/test/PayRegistry.t.sol b/test/PayRegistry.t.sol index f9956cd..957bb66 100644 --- a/test/PayRegistry.t.sol +++ b/test/PayRegistry.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {PayRegistry} from "../src/PayRegistry.sol"; /** @@ -135,7 +136,9 @@ contract PayRegistryTest is Test { uint256[] memory amts = new uint256[](1); amts[0] = 11; - vm.expectRevert(bytes("Lengths do not match")); + // Full-payload assertion — locks down the (a, b) lengths so a future + // edit that swaps argument order or returns the wrong sides still fails. + vm.expectRevert(abi.encodeWithSelector(AgentPayErrors.LengthMismatch.selector, uint256(2), uint256(1))); vm.prank(setterA); registry.setPayAmounts(hashes, amts); } @@ -186,7 +189,7 @@ contract PayRegistryTest is Test { uint256[] memory amts = new uint256[](2); uint256[] memory deadlines = new uint256[](1); - vm.expectRevert(bytes("Lengths do not match")); + vm.expectPartialRevert(AgentPayErrors.LengthMismatch.selector); vm.prank(setterA); registry.setPayInfos(hashes, amts, deadlines); } @@ -218,7 +221,7 @@ contract PayRegistryTest is Test { ids[0] = registry.calculatePayId(payHash1, setterA); // Per-pay deadline is in the future; should revert. - vm.expectRevert(bytes("Payment is not finalized")); + vm.expectRevert(AgentPayErrors.PaymentNotFinalized.selector); registry.getPayAmounts(ids, block.timestamp); } @@ -239,7 +242,7 @@ contract PayRegistryTest is Test { ids[0] = registry.calculatePayId(payHash1, setterA); // Channel-level payClearDeadline is in the future → revert. - vm.expectRevert(bytes("Payment is not finalized")); + vm.expectRevert(AgentPayErrors.PaymentNotFinalized.selector); registry.getPayAmounts(ids, block.timestamp + 100); } diff --git a/test/PayResolver.t.sol b/test/PayResolver.t.sol index 9b58c6f..4f62ced 100644 --- a/test/PayResolver.t.sol +++ b/test/PayResolver.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {PayResolver} from "../src/PayResolver.sol"; import {PayRegistry} from "../src/PayRegistry.sol"; import {VirtContractResolver} from "../src/VirtContractResolver.sol"; @@ -268,14 +269,15 @@ contract PayResolverTest is Test { // Resolve via conditions to bump to 35 first, then try a vouched 30: _resolveByConditions(payBytes, TRUE_PREIMAGE); - vm.expectRevert(bytes("New amount is not larger")); + vm.expectRevert(AgentPayErrors.AmountNotGreater.selector); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 30)); } function test_resolveByVouchedResult_exceedingMax_reverts() public { bytes memory payBytes = _buildPay(0, 5, 3, 100, RESOLVE_DEADLINE); - vm.expectRevert(bytes("Exceed max transfer amount")); + // Full-payload assertion — locks down (attempted, max). + vm.expectRevert(abi.encodeWithSelector(AgentPayErrors.MaxTransferExceeded.selector, uint256(200), uint256(100))); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 200)); } @@ -287,14 +289,14 @@ contract PayResolverTest is Test { // resolveDeadline already in the past. bytes memory payBytes = _buildPay(5, 1, 0, 10, block.timestamp - 1); - vm.expectRevert(bytes("Passed pay resolve deadline in condPay msg")); + vm.expectRevert(AgentPayErrors.DeadlinePassed.selector); _resolveByConditions(payBytes, TRUE_PREIMAGE); } function test_resolveByVouchedResult_pastResolveDeadline_reverts() public { bytes memory payBytes = _buildPay(6, 1, 0, 100, block.timestamp - 1); - vm.expectRevert(bytes("Passed pay resolve deadline in condPay msg")); + vm.expectRevert(AgentPayErrors.DeadlinePassed.selector); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); } @@ -307,7 +309,7 @@ contract PayResolverTest is Test { // Roll past the onchain resolve deadline. vm.warp(block.timestamp + RESOLVE_TIMEOUT + 1); - vm.expectRevert(bytes("Passed onchain resolve pay deadline")); + vm.expectRevert(AgentPayErrors.ResolveUpdateWindowClosed.selector); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 30)); } @@ -317,7 +319,7 @@ contract PayResolverTest is Test { payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); vm.warp(block.timestamp + RESOLVE_TIMEOUT + 1); - vm.expectRevert(bytes("Passed onchain resolve pay deadline")); + vm.expectRevert(AgentPayErrors.ResolveUpdateWindowClosed.selector); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -329,7 +331,7 @@ contract PayResolverTest is Test { // type 4: [hashLock, deployedTrue, hashLock] bytes memory payBytes = _buildPay(9, 4, 1, 200, RESOLVE_DEADLINE); - vm.expectRevert(bytes("Wrong preimage")); + vm.expectRevert(AgentPayErrors.PreimageMismatch.selector); _resolveByConditionsTwo(payBytes, TRUE_PREIMAGE, FALSE_PREIMAGE); } @@ -379,7 +381,7 @@ contract PayResolverTest is Test { _buildPaySingleDeployed(20, address(boolMock), 0, NOT_FINALIZED_QUERY, abi.encodePacked(bytes1(0x01))); bytes[] memory preimages = new bytes[](0); - vm.expectRevert(bytes("Condition is not finalized")); + vm.expectRevert(AgentPayErrors.ConditionNotFinalized.selector); payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); } @@ -389,7 +391,7 @@ contract PayResolverTest is Test { _buildPaySingleDeployed(21, address(boolMock), 1, NOT_FINALIZED_QUERY, abi.encodePacked(bytes1(0x01))); bytes[] memory preimages = new bytes[](0); - vm.expectRevert(bytes("Condition is not finalized")); + vm.expectRevert(AgentPayErrors.ConditionNotFinalized.selector); payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); } @@ -399,7 +401,7 @@ contract PayResolverTest is Test { _buildPaySingleDeployed(22, address(numMock), 3, NOT_FINALIZED_QUERY, abi.encodePacked(uint8(10))); bytes[] memory preimages = new bytes[](0); - vm.expectRevert(bytes("Condition is not finalized")); + vm.expectRevert(AgentPayErrors.ConditionNotFinalized.selector); payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); } @@ -434,7 +436,12 @@ contract PayResolverTest is Test { bytes[] memory preimages = new bytes[](1); preimages[0] = TRUE_PREIMAGE; - vm.expectRevert(bytes("Wrong chain id for pay")); + // Full-payload assertion — locks down the (expected, actual) args so a + // future edit that swaps argument order or returns the wrong values + // still fails the test. + vm.expectRevert( + abi.encodeWithSelector(AgentPayErrors.ChainIdMismatch.selector, block.chainid, block.chainid + 1) + ); payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); } @@ -442,7 +449,7 @@ contract PayResolverTest is Test { // Same wrong-chainid pay submitted via the vouched-result path. bytes memory payBytes = _buildPayWithChainId(31, block.chainid + 1); - vm.expectRevert(bytes("Wrong chain id for pay")); + vm.expectPartialRevert(AgentPayErrors.ChainIdMismatch.selector); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); } @@ -478,7 +485,7 @@ contract PayResolverTest is Test { bytes[] memory preimages = new bytes[](1); preimages[0] = TRUE_PREIMAGE; - vm.expectRevert(bytes("Wrong resolver for pay")); + vm.expectRevert(AgentPayErrors.ResolverAddressMismatch.selector); payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); } @@ -487,7 +494,7 @@ contract PayResolverTest is Test { PayResolver siblingResolver = new PayResolver(address(payRegistry), address(virtResolver)); bytes memory payBytes = _buildPayWithResolver(41, address(siblingResolver)); - vm.expectRevert(bytes("Wrong resolver for pay")); + vm.expectRevert(AgentPayErrors.ResolverAddressMismatch.selector); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); } diff --git a/test/RouterRegistry.t.sol b/test/RouterRegistry.t.sol index b80fe61..505493d 100644 --- a/test/RouterRegistry.t.sol +++ b/test/RouterRegistry.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {RouterRegistry} from "../src/RouterRegistry.sol"; import {IRouterRegistry} from "../src/interfaces/IRouterRegistry.sol"; @@ -45,7 +46,7 @@ contract RouterRegistryTest is Test { vm.prank(router0); registry.registerRouter(); - vm.expectRevert(bytes("Router address already exists")); + vm.expectRevert(AgentPayErrors.RouterAlreadyRegistered.selector); vm.prank(router0); registry.registerRouter(); } @@ -68,7 +69,7 @@ contract RouterRegistryTest is Test { } function test_deregisterRouter_revertsForUnregistered() public { - vm.expectRevert(bytes("Router address does not exist")); + vm.expectRevert(AgentPayErrors.RouterNotRegistered.selector); vm.prank(router0); registry.deregisterRouter(); } @@ -95,7 +96,7 @@ contract RouterRegistryTest is Test { } function test_refreshRouter_revertsForUnregistered() public { - vm.expectRevert(bytes("Router address does not exist")); + vm.expectRevert(AgentPayErrors.RouterNotRegistered.selector); vm.prank(router0); registry.refreshRouter(); } diff --git a/test/VirtContractResolver.t.sol b/test/VirtContractResolver.t.sol index 1b8d082..8ea12b1 100644 --- a/test/VirtContractResolver.t.sol +++ b/test/VirtContractResolver.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {AgentPayErrors} from "../src/lib/AgentPayErrors.sol"; import {VirtContractResolver} from "../src/VirtContractResolver.sol"; /** @@ -29,7 +30,7 @@ contract VirtContractResolverTest is Test { } function test_resolve_revertsForUnknownVirtAddr() public { - vm.expectRevert(bytes("Nonexistent virtual address")); + vm.expectRevert(AgentPayErrors.VirtAddressUnresolved.selector); resolver.resolve(bytes32(uint256(1))); } @@ -51,7 +52,7 @@ contract VirtContractResolverTest is Test { function test_deploy_revertsIfAlreadyDeployed() public { resolver.deploy(SAMPLE_CODE, 7); - vm.expectRevert(bytes("Current real address is not 0")); + vm.expectRevert(AgentPayErrors.VirtAddressOccupied.selector); resolver.deploy(SAMPLE_CODE, 7); } } diff --git a/test/gas_logs/CelerLedger-ERC20.txt b/test/gas_logs/CelerLedger-ERC20.txt index 480be38..955f84a 100644 --- a/test/gas_logs/CelerLedger-ERC20.txt +++ b/test/gas_logs/CelerLedger-ERC20.txt @@ -1,8 +1,8 @@ ********** Gas Measurement: CelerLedger ERC20 ********** ***** Function Calls Gas Used ***** -openChannel() with zero deposit: 305397 -openChannel() with non-zero ERC20 deposits: 469427 +openChannel() with zero deposit: 305410 +openChannel() with non-zero ERC20 deposits: 469440 deposit(): 149979 -cooperativeWithdraw(): 89116 -cooperativeSettle(): 88959 +cooperativeWithdraw(): 89247 +cooperativeSettle(): 89090 diff --git a/test/gas_logs/CelerLedger-ETH.txt b/test/gas_logs/CelerLedger-ETH.txt index b7ea920..59ffdaf 100644 --- a/test/gas_logs/CelerLedger-ETH.txt +++ b/test/gas_logs/CelerLedger-ETH.txt @@ -1,29 +1,29 @@ ********** Gas Measurement: CelerLedger ETH ********** ***** Deploy Gas Used ***** -VirtContractResolver Deploy Gas: 206156 -NativeWrapMock Deploy Gas: 416592 -PayRegistry Deploy Gas: 565639 -CelerWallet Deploy Gas: 1149117 -PayResolver Deploy Gas: 1910790 -CelerLedger Deploy Gas: 1830162 +VirtContractResolver Deploy Gas: 176893 +NativeWrapMock Deploy Gas: 416605 +PayRegistry Deploy Gas: 554814 +CelerWallet Deploy Gas: 1100195 +PayResolver Deploy Gas: 1806957 +CelerLedger Deploy Gas: 1820981 ***** Function Calls Gas Used ***** -openChannel() with zero deposit: 301424 -openChannel() using nativeWrap and msg.value: 432975 -setBalanceLimits(): 24540 +openChannel() with zero deposit: 301437 +openChannel() using nativeWrap and msg.value: 433046 +setBalanceLimits(): 24553 disableBalanceLimits(): 1132 enableBalanceLimits(): 29544 deposit() via msg.value: 77964 deposit() via nativeWrap: 121766 -depositInBatch() with 5 deposits: 526507 +depositInBatch() with 5 deposits: 526525 intendWithdraw(): 72783 vetoWithdraw(): 4017 -confirmWithdraw(): 57569 -cooperativeWithdraw(): 92320 -snapshotStates() with one non-null simplex state: 77696 +confirmWithdraw(): 57582 +cooperativeWithdraw(): 92451 +snapshotStates() with one non-null simplex state: 77707 intendSettle() with a null state: 87577 -intendSettle() with two 2-payment-hashList states: 279865 +intendSettle() with two 2-payment-hashList states: 279885 clearPays() with 2 payments: 17012 confirmSettle(): 48742 -cooperativeSettle(): 93149 +cooperativeSettle(): 93280 diff --git a/test/gas_logs/PayResolver.txt b/test/gas_logs/PayResolver.txt index 5bff482..fa89070 100644 --- a/test/gas_logs/PayResolver.txt +++ b/test/gas_logs/PayResolver.txt @@ -1,5 +1,5 @@ ********** Gas Measurement: PayResolver ********** ***** Function Calls Gas Used ***** -resolvePaymentByConditions(): 78494 -resolvePaymentByVouchedResult(): 96778 +resolvePaymentByConditions(): 78507 +resolvePaymentByVouchedResult(): 96810 diff --git a/test/gas_logs/fine_granularity/DepositEthInBatch.txt b/test/gas_logs/fine_granularity/DepositEthInBatch.txt index 5681243..3bae485 100644 --- a/test/gas_logs/fine_granularity/DepositEthInBatch.txt +++ b/test/gas_logs/fine_granularity/DepositEthInBatch.txt @@ -1,9 +1,9 @@ ********** Gas Measurement of depositInBatch() - ETH ********** batch size used gas -1 123333 -5 526460 -10 1030643 -25 2544840 -50 5075198 -75 7619636 +1 123351 +5 526478 +10 1030661 +25 2544858 +50 5075216 +75 7619654 diff --git a/test/gas_logs/fine_granularity/IntendSettle-OneState.txt b/test/gas_logs/fine_granularity/IntendSettle-OneState.txt index 0577dd0..f44c425 100644 --- a/test/gas_logs/fine_granularity/IntendSettle-OneState.txt +++ b/test/gas_logs/fine_granularity/IntendSettle-OneState.txt @@ -1,10 +1,10 @@ ********** Gas Measurement of intendSettle() one state with multi pays ********** pay number in head payIdList used gas -1 219405 -5 241272 -10 268556 -25 352065 -50 497538 -100 812347 -200 1545242 +1 219425 +5 241292 +10 268576 +25 352085 +50 497558 +100 812367 +200 1545262