diff --git a/docs/contracts.md b/docs/contracts.md index 595fc40..006671c 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -205,7 +205,7 @@ constructor(address _registryAddr, address _virtResolverAddr) ### Resolution rules -- A payment must be resolved before `pay.resolveDeadline` (block number). +- A payment must be resolved before `pay.resolveDeadline` (Unix timestamp, seconds). - A result equal to the maximum-transfer amount **finalizes immediately** — no challenge window. - A partial result opens a challenge window of length `pay.resolveTimeout`. During the @@ -313,7 +313,8 @@ Etherscan-friendly metadata: `name = "EthInPool"`, `symbol = "EthIP"`, `decimals [Source](../src/RouterRegistry.sol) · [Interface](../src/lib/interface/IRouterRegistry.sol) · **Permanent** Optional global registry where relay-router operators advertise their addresses to the -network. Each entry stores the latest registration / refresh `block.number`. +network. Each entry stores the Unix timestamp (seconds) of the latest registration / +refresh. ### External functions @@ -321,7 +322,7 @@ network. Each entry stores the latest registration / refresh `block.number`. |---|---| | [`registerRouter`](../src/RouterRegistry.sol#L18) | Add `msg.sender` to the registry. Reverts if already present. | | [`deregisterRouter`](../src/RouterRegistry.sol#L29) | Remove `msg.sender`. | -| [`refreshRouter`](../src/RouterRegistry.sol#L40) | Update the stored block number for `msg.sender`. | +| [`refreshRouter`](../src/RouterRegistry.sol#L40) | Update the stored timestamp for `msg.sender`. | ### Events diff --git a/src/CelerLedgerMock.sol b/src/CelerLedgerMock.sol index 9b9ef17..235e43c 100644 --- a/src/CelerLedgerMock.sol +++ b/src/CelerLedgerMock.sol @@ -137,7 +137,7 @@ contract CelerLedgerMock { withdrawIntent.receiver = _receiver; withdrawIntent.amount = _amount; - withdrawIntent.requestTime = block.number; + withdrawIntent.requestTime = block.timestamp; withdrawIntent.recipientChannelId = _recipientChannelId; tmpChannelId = _channelId; @@ -542,7 +542,7 @@ contract CelerLedgerMock { function _updateOverallStatesByIntendState(bytes32 _channelId) internal { LedgerStruct.Channel storage c = ledger.channelMap[_channelId]; - c.settleFinalizedTime = block.number + c.disputeTimeout; + c.settleFinalizedTime = block.timestamp + c.disputeTimeout; _updateChannelStatus(c, LedgerStruct.ChannelStatus.Settling); } diff --git a/src/PayRegistry.sol b/src/PayRegistry.sol index a6c3b7d..6ce65fb 100644 --- a/src/PayRegistry.sol +++ b/src/PayRegistry.sol @@ -112,10 +112,10 @@ contract PayRegistry is IPayRegistry { for (uint256 i = 0; i < _payIds.length; i++) { if (payInfoMap[_payIds[i]].resolveDeadline == 0) { // should pass last pay resolve deadline if never resolved - require(block.number > _lastPayResolveDeadline, "Payment is not finalized"); + require(block.timestamp > _lastPayResolveDeadline, "Payment is not finalized"); } else { // should pass resolve deadline if resolved - require(block.number > payInfoMap[_payIds[i]].resolveDeadline, "Payment is not finalized"); + require(block.timestamp > payInfoMap[_payIds[i]].resolveDeadline, "Payment is not finalized"); } amounts[i] = payInfoMap[_payIds[i]].amount; } diff --git a/src/PayResolver.sol b/src/PayResolver.sol index 84fc9d9..69afbe0 100644 --- a/src/PayResolver.sol +++ b/src/PayResolver.sol @@ -91,14 +91,14 @@ contract PayResolver is IPayResolver { * @param _amount payment amount to resolve */ function _resolvePayment(PbEntity.ConditionalPay memory _pay, bytes32 _payHash, uint256 _amount) internal { - uint256 blockNumber = block.number; - require(blockNumber <= _pay.resolveDeadline, "Passed pay resolve deadline in condPay msg"); + uint256 nowTs = block.timestamp; + require(nowTs <= _pay.resolveDeadline, "Passed pay resolve deadline in condPay msg"); 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 || blockNumber <= currentDeadline, "Passed onchain resolve pay deadline"); + require(currentDeadline == 0 || nowTs <= currentDeadline, "Passed onchain resolve pay deadline"); if (currentDeadline > 0) { // currentDeadline > 0 implies that this pay has been updated @@ -106,9 +106,9 @@ contract PayResolver is IPayResolver { require(_amount > currentAmt, "New amount is not larger"); if (_amount == _pay.transferFunc.maxTransfer.receiver.amt) { - // set resolve deadline = current block number if amount = max - payRegistry.setPayInfo(_payHash, _amount, blockNumber); - emit ResolvePayment(payId, _amount, blockNumber); + // set resolve deadline = current timestamp if amount = max + payRegistry.setPayInfo(_payHash, _amount, nowTs); + emit ResolvePayment(payId, _amount, nowTs); } else { // should not update the onchain resolve deadline if not max amount payRegistry.setPayAmount(_payHash, _amount); @@ -117,9 +117,9 @@ contract PayResolver is IPayResolver { } else { uint256 newDeadline; if (_amount == _pay.transferFunc.maxTransfer.receiver.amt) { - newDeadline = blockNumber; + newDeadline = nowTs; } else { - newDeadline = Math.min(blockNumber + _pay.resolveTimeout, _pay.resolveDeadline); + 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"); } diff --git a/src/RouterRegistry.sol b/src/RouterRegistry.sol index ad12a85..b0c2615 100644 --- a/src/RouterRegistry.sol +++ b/src/RouterRegistry.sol @@ -6,13 +6,13 @@ import "./lib/interface/IRouterRegistry.sol"; /** * @title RouterRegistry * @notice Optional global registry where relay-router operators advertise themselves - * to the AgentPay network. Each registered router stores the latest registration - * or refresh `block.number`; off-chain consumers may use this for liveness signaling - * and router discovery. + * to the AgentPay network. Each registered router stores the unix timestamp + * (seconds) of its most recent registration or refresh; off-chain consumers may + * use this for liveness signaling and router discovery. * @dev See {IRouterRegistry} for canonical NatSpec on each function. */ contract RouterRegistry is IRouterRegistry { - /// @notice Registered router addresses → most recent register/refresh block number. + /// @notice Registered router addresses → unix timestamp (seconds) of most recent register/refresh. mapping(address => uint256) public routerInfo; /** @@ -21,7 +21,7 @@ contract RouterRegistry is IRouterRegistry { function registerRouter() external { require(routerInfo[msg.sender] == 0, "Router address already exists"); - routerInfo[msg.sender] = block.number; + routerInfo[msg.sender] = block.timestamp; emit RouterUpdated(RouterOperation.Add, msg.sender); } @@ -38,12 +38,12 @@ contract RouterRegistry is IRouterRegistry { } /** - * @notice Refresh the existed router's block number + * @notice Refresh the existed router's stored timestamp */ function refreshRouter() external { require(routerInfo[msg.sender] != 0, "Router address does not exist"); - routerInfo[msg.sender] = block.number; + routerInfo[msg.sender] = block.timestamp; emit RouterUpdated(RouterOperation.Refresh, msg.sender); } diff --git a/src/helper/BooleanCondMock.sol b/src/helper/BooleanCondMock.sol index bf2b753..237ffb5 100644 --- a/src/helper/BooleanCondMock.sol +++ b/src/helper/BooleanCondMock.sol @@ -5,25 +5,31 @@ import "../lib/interface/IBooleanCond.sol"; /** * @title BooleanCondMock - * @notice **Test-only.** Minimal {IBooleanCond} that always reports finalized and - * decodes its outcome straight from the query bytes. Used by PayResolver tests to - * exercise condition-evaluation paths. **Do not deploy to a production network.** + * @notice **Test-only.** Minimal {IBooleanCond} that decodes both `isFinalized` + * and `getOutcome` directly from their respective query bytes. + * + * Encoding for both queries: a single byte where `0x00 → false` and any other + * value → `true`. Empty query bytes default to `true` so callers that don't + * care about a particular flag can leave the corresponding `argsQuery*` field + * empty and get the "happy path" behavior. + * + * This shape lets a single deployed instance simulate every combination of + * (finalized, outcome) — useful for both Solidity tests and off-chain + * integration tests. **Do not deploy to a production network.** */ contract BooleanCondMock is IBooleanCond { - function isFinalized( - bytes calldata /* _query */ - ) - external - pure - returns (bool) - { - return true; + function isFinalized(bytes calldata _query) external pure returns (bool) { + if (_query.length == 0) { + return true; + } + return _bytesToBool(_query); } function getOutcome(bytes calldata _query) external pure returns (bool) { return _bytesToBool(_query); } + /// @dev Empty input → false (matches the "no outcome" notion for `getOutcome`). function _bytesToBool(bytes memory _b) internal pure returns (bool) { if (_b.length == 0) { return false; diff --git a/src/helper/NumericCondMock.sol b/src/helper/NumericCondMock.sol index f3e60a4..c5ff49b 100644 --- a/src/helper/NumericCondMock.sol +++ b/src/helper/NumericCondMock.sol @@ -5,19 +5,24 @@ import "../lib/interface/INumericCond.sol"; /** * @title NumericCondMock - * @notice **Test-only.** Minimal {INumericCond} that always reports finalized and - * decodes its outcome straight from the query bytes. Numeric counterpart to - * {BooleanCondMock}. **Do not deploy to a production network.** + * @notice **Test-only.** Minimal {INumericCond} that decodes both `isFinalized` + * and `getOutcome` directly from their respective query bytes. + * + * - `isFinalized(_query)`: a single byte where `0x00 → false` and any other + * value → `true`. Empty query defaults to `true`. + * - `getOutcome(_query)`: big-endian unsigned integer parsed from the query + * bytes. Empty query → `0`. + * + * This shape lets a single deployed instance simulate every combination of + * (finalized, outcome) — useful for both Solidity tests and off-chain + * integration tests. **Do not deploy to a production network.** */ contract NumericCondMock is INumericCond { - function isFinalized( - bytes calldata /* _query */ - ) - external - pure - returns (bool) - { - return true; + function isFinalized(bytes calldata _query) external pure returns (bool) { + if (_query.length == 0) { + return true; + } + return _bytesToUint(_query) != 0; } function getOutcome(bytes calldata _query) external pure returns (uint256) { diff --git a/src/lib/data/Pb.sol b/src/lib/data/Pb.sol index 5e394ee..8317129 100644 --- a/src/lib/data/Pb.sol +++ b/src/lib/data/Pb.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// Code generated by protoc-gen-sol. DO NOT EDIT. pragma solidity ^0.8.20; /** @@ -7,7 +8,6 @@ pragma solidity ^0.8.20; * [`pb3-gen-sol`](https://github.com/celer-network/pb3-gen-sol). Provides a * `Buffer` cursor type and primitive read helpers (varint, length-delimited bytes, * fixed widths) used by the generated `Pb*` libraries in this directory. - * @dev Hand-written; the generated `PbChain`/`PbEntity` libraries depend on it. */ library Pb { enum WireType { @@ -35,47 +35,49 @@ library Pb { return buf.idx < buf.b.length; } - // decode current field number and wiretype - function decKey(Buffer memory buf) internal pure returns (uint256 tag, WireType wiretype) { - uint256 v = decVarint(buf); - tag = v / 8; - wiretype = WireType(v & 7); - } - - // count tag occurrences, return an array due to no memory map support - // have to create array for (maxtag+1) size. cnts[tag] = occurrences - // should keep buf.idx unchanged because this is only a count function - function cntTags(Buffer memory buf, uint256 maxtag) internal pure returns (uint256[] memory cnts) { - uint256 originalIdx = buf.idx; - cnts = new uint256[](maxtag + 1); // protobuf's tags are from 1 rather than 0 - uint256 tag; - WireType wire; - while (hasMore(buf)) { - (tag, wire) = decKey(buf); - cnts[tag] += 1; - skipValue(buf, wire); - } - buf.idx = originalIdx; - } - // read varint from current buf idx, move buf.idx to next read, return the int value function decVarint(Buffer memory buf) internal pure returns (uint256 v) { - bytes10 tmp; // proto int is at most 10 bytes (7 bits can be used per byte) - bytes memory bb = buf.b; // get buf.b mem addr to use in assembly - v = buf.idx; // use v to save one additional uint variable - assembly { - tmp := mload(add(add(bb, 32), v)) // load 10 bytes from buf.b[buf.idx] to tmp + bytes memory bb = buf.b; + uint256 idx = buf.idx; + uint256 len = bb.length; + // Cache the bytes data pointer once. Solidity's `bb[idx]` would re-derive + // it and run an implicit bounds check every iteration; the inline mload + // plus explicit `idx < len` check below is the same safety with less per- + // byte overhead. + uint256 dataPtr; + assembly ("memory-safe") { + dataPtr := add(bb, 32) } - uint256 b; // store current byte content - v = 0; // reset to 0 for return value - for (uint256 i = 0; i < 10; i++) { - assembly { - b := byte(i, tmp) // don't use tmp[i] because it does bound check and costs extra - } - v |= (b & 0x7F) << (i * 7); - if (b & 0x80 == 0) { - buf.idx += i + 1; - return v; + // The loop is wrapped in `unchecked` because every arithmetic op inside + // is bounded: `i` runs 0..9, `idx` is bounded by `len` via the require, + // and `(i * 7)` peaks at 63. The explicit `require(idx < len)` retains + // the truncation revert. + unchecked { + for (uint256 i = 0; i < 10; ++i) { + require(idx < len); + uint256 b; + assembly ("memory-safe") { + // mload reads 32 bytes; we only consume byte 0 (idx itself), and + // idx < len guarantees that byte is within the buffer. Bytes 1..31 + // may sit past the buffer's data but are never used. + b := byte(0, mload(add(dataPtr, idx))) + } + idx++; + v |= (b & 0x7F) << (i * 7); + if (b < 0x80) { + // Bytes 0..8 cover bits 0..62. The 10th byte (i == 9) + // can only contribute bit 63 to keep the value within + // the protobuf uint64 range, so its low 7 bits must be + // 0 or 1. This guards every decVarint consumer + // uniformly — keys, length prefixes, and field values + // — so that malformed structural varints do not + // acquire a defined on-chain interpretation. Hoisted + // to the success path so it runs once per varint + // instead of once per byte. + if (i == 9) require(b < 2); + buf.idx = idx; + return v; + } } } revert(); // i=10, invalid varint stream @@ -90,14 +92,19 @@ library Pb { bytes memory bufB = buf.b; // get buf.b mem addr to use in assembly uint256 bStart; uint256 bufBStart = buf.idx; - assembly { + assembly ("memory-safe") { bStart := add(b, 32) bufBStart := add(add(bufB, 32), bufBStart) } - for (uint256 i = 0; i < len; i += 32) { - assembly { + // i is bounded by len (≤ payload size); the trailing partial-word write + // stays within `b`'s 32-byte-padded allocation. unchecked is safe. + for (uint256 i = 0; i < len;) { + assembly ("memory-safe") { mstore(add(bStart, i), mload(add(bufBStart, i))) } + unchecked { + i += 32; + } } buf.idx = end; } @@ -107,22 +114,27 @@ library Pb { uint256 len = decVarint(buf); uint256 end = buf.idx + len; require(end <= buf.b.length); // avoid overflow - // array in memory must be init w/ known length - // so we have to create a tmp array w/ max possible len first - uint256[] memory tmp = new uint256[](len); + // array in memory must be init w/ known length, so allocate the max + // possible element count first and then shrink the final length in place. + t = new uint256[](len); uint256 i = 0; // count how many ints are there while (buf.idx < end) { - tmp[i] = decVarint(buf); - i++; + t[i] = decVarint(buf); + unchecked { + i++; + } } - t = new uint256[](i); // init t with correct length - for (uint256 j = 0; j < i; j++) { - t[j] = tmp[j]; + assembly ("memory-safe") { + mstore(t, i) } - return t; } - // move idx pass current value field, to beginning of next tag or msg end + // move idx pass current value field, to beginning of next tag or msg end. + // Skips all proto3 wire types so that unknown fields (including types this + // generator doesn't otherwise emit, like fixed32 / fixed64 / double / float) + // round-trip through forward-compatible decoders without reverting. Group + // wire types (StartGroup/EndGroup) are proto2 / deprecated and are not + // expected on any proto3 wire — they revert. function skipValue(Buffer memory buf, WireType wire) internal pure { if (wire == WireType.Varint) { decVarint(buf); @@ -130,9 +142,15 @@ library Pb { uint256 len = decVarint(buf); buf.idx += len; // skip len bytes value data require(buf.idx <= buf.b.length); // avoid overflow + } else if (wire == WireType.Fixed64) { + buf.idx += 8; + require(buf.idx <= buf.b.length); // avoid overflow + } else if (wire == WireType.Fixed32) { + buf.idx += 4; + require(buf.idx <= buf.b.length); // avoid overflow } else { - revert(); - } // unsupported wiretype + revert(); // unsupported wiretype (StartGroup / EndGroup) + } } // type conversion help utils @@ -140,59 +158,106 @@ library Pb { return x != 0; } - function _uint256(bytes memory b) internal pure returns (uint256 v) { - require(b.length <= 32); // b's length must be smaller than or equal to 32 - assembly { - v := mload(add(b, 32)) - } // load all 32bytes to v - v = v >> (8 * (32 - b.length)); // only first b.length is valid - } + // Fixed-width length-delimited readers. Each one reads its own varint + // length prefix from `buf`, validates the length, mloads the payload + // directly into the target type, and advances `buf.idx`. Saves the + // intermediate `bytes` allocation that `decBytes` + a helper conversion + // would otherwise pay. - function _address(bytes memory b) internal pure returns (address v) { - v = _addressPayable(b); + // Read a length-delimited field of exactly 20 bytes as an address. + // Returns `address payable` so the same call can be used for both + // `address` and `address payable` schema fields (Solidity allows the + // implicit `address payable` → `address` direction on assignment). + function decAddress(Buffer memory buf) internal pure returns (address payable v) { + uint256 len = decVarint(buf); + require(len == 20); + uint256 idx = buf.idx; + require(idx + 20 <= buf.b.length); + bytes memory bb = buf.b; + assembly ("memory-safe") { + // address occupies the high-order 20 bytes of the loaded word; shr + // by 96 bits drops the trailing 12 bytes of (possibly past-buffer) + // memory which are never used. + v := shr(96, mload(add(add(bb, 32), idx))) + } + buf.idx = idx + 20; } - function _addressPayable(bytes memory b) internal pure returns (address payable v) { - require(b.length == 20); - //load 32bytes then shift right 12 bytes - assembly { - v := div(mload(add(b, 32)), 0x1000000000000000000000000) + // Read a length-delimited field of exactly 32 bytes as bytes32. + function decBytes32(Buffer memory buf) internal pure returns (bytes32 v) { + uint256 len = decVarint(buf); + require(len == 32); + uint256 idx = buf.idx; + require(idx + 32 <= buf.b.length); + bytes memory bb = buf.b; + assembly ("memory-safe") { + v := mload(add(add(bb, 32), idx)) } + buf.idx = idx + 32; } - function _bytes32(bytes memory b) internal pure returns (bytes32 v) { - require(b.length == 32); - assembly { - v := mload(add(b, 32)) + // Read a length-delimited field of <= 32 bytes as a big-endian uint256. + // This mirrors the existing soltype="uint256" wire shape: leading-zero + // bytes are stripped on the wire, so we right-shift the loaded word by + // (32 - len) bytes to align the value into the low-order bits. + function decUint256(Buffer memory buf) internal pure returns (uint256 v) { + uint256 len = decVarint(buf); + require(len <= 32); + uint256 idx = buf.idx; + uint256 end = idx + len; + require(end <= buf.b.length); + bytes memory bb = buf.b; + // For len == 0 the EVM `shr` opcode returns 0 (shift >= 256 is + // defined as zero). buf.idx is unchanged in that case, which matches + // the proto3 default-zero semantics for an empty bytes field. + assembly ("memory-safe") { + v := shr(mul(sub(32, len), 8), mload(add(add(bb, 32), idx))) } + buf.idx = end; } // uint[] to uint8[] function uint8s(uint256[] memory arr) internal pure returns (uint8[] memory t) { - t = new uint8[](arr.length); - for (uint256 i = 0; i < t.length; i++) { + uint256 n = arr.length; + t = new uint8[](n); + for (uint256 i = 0; i < n;) { t[i] = uint8(arr[i]); + unchecked { + ++i; + } } } function uint32s(uint256[] memory arr) internal pure returns (uint32[] memory t) { - t = new uint32[](arr.length); - for (uint256 i = 0; i < t.length; i++) { + uint256 n = arr.length; + t = new uint32[](n); + for (uint256 i = 0; i < n;) { t[i] = uint32(arr[i]); + unchecked { + ++i; + } } } function uint64s(uint256[] memory arr) internal pure returns (uint64[] memory t) { - t = new uint64[](arr.length); - for (uint256 i = 0; i < t.length; i++) { + uint256 n = arr.length; + t = new uint64[](n); + for (uint256 i = 0; i < n;) { t[i] = uint64(arr[i]); + unchecked { + ++i; + } } } function bools(uint256[] memory arr) internal pure returns (bool[] memory t) { - t = new bool[](arr.length); - for (uint256 i = 0; i < t.length; i++) { + uint256 n = arr.length; + t = new bool[](n); + for (uint256 i = 0; i < n;) { t[i] = arr[i] != 0; + unchecked { + ++i; + } } } } diff --git a/src/lib/data/PbChain.sol b/src/lib/data/PbChain.sol index a30a6ab..c1c5890 100644 --- a/src/lib/data/PbChain.sol +++ b/src/lib/data/PbChain.sol @@ -24,24 +24,33 @@ library PbChain { function decOpenChannelRequest(bytes memory raw) internal pure returns (OpenChannelRequest memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.sigs = new bytes[](cnts[2]); - cnts[2] = 0; // reset counter for later use + uint256[] memory _arr2 = new uint256[](raw.length / 2); + uint256 _cnt2 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.channelInitializer = bytes(buf.decBytes()); - } else if (tag == 2) { - m.sigs[cnts[2]] = bytes(buf.decBytes()); - cnts[2]++; + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.channelInitializer = buf.decBytes(); + } else if (key == 18) { + // tag 2 + bytes memory _v2 = buf.decBytes(); + assembly ("memory-safe") { mstore(add(add(_arr2, 32), shl(5, _cnt2)), _v2) } + unchecked { + _cnt2++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } + + bytes[] memory _result2; + assembly ("memory-safe") { + mstore(_arr2, _cnt2) + _result2 := _arr2 + } + m.sigs = _result2; } // end decoder OpenChannelRequest struct CooperativeWithdrawRequest { @@ -56,24 +65,33 @@ library PbChain { { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.sigs = new bytes[](cnts[2]); - cnts[2] = 0; // reset counter for later use + uint256[] memory _arr2 = new uint256[](raw.length / 2); + uint256 _cnt2 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.withdrawInfo = bytes(buf.decBytes()); - } else if (tag == 2) { - m.sigs[cnts[2]] = bytes(buf.decBytes()); - cnts[2]++; + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.withdrawInfo = buf.decBytes(); + } else if (key == 18) { + // tag 2 + bytes memory _v2 = buf.decBytes(); + assembly ("memory-safe") { mstore(add(add(_arr2, 32), shl(5, _cnt2)), _v2) } + unchecked { + _cnt2++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } + } + + bytes[] memory _result2; + assembly ("memory-safe") { + mstore(_arr2, _cnt2) + _result2 := _arr2 } + m.sigs = _result2; } // end decoder CooperativeWithdrawRequest struct CooperativeSettleRequest { @@ -84,24 +102,33 @@ library PbChain { function decCooperativeSettleRequest(bytes memory raw) internal pure returns (CooperativeSettleRequest memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.sigs = new bytes[](cnts[2]); - cnts[2] = 0; // reset counter for later use + uint256[] memory _arr2 = new uint256[](raw.length / 2); + uint256 _cnt2 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.settleInfo = bytes(buf.decBytes()); - } else if (tag == 2) { - m.sigs[cnts[2]] = bytes(buf.decBytes()); - cnts[2]++; + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.settleInfo = buf.decBytes(); + } else if (key == 18) { + // tag 2 + bytes memory _v2 = buf.decBytes(); + assembly ("memory-safe") { mstore(add(add(_arr2, 32), shl(5, _cnt2)), _v2) } + unchecked { + _cnt2++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } + + bytes[] memory _result2; + assembly ("memory-safe") { + mstore(_arr2, _cnt2) + _result2 := _arr2 + } + m.sigs = _result2; } // end decoder CooperativeSettleRequest struct ResolvePayByConditionsRequest { @@ -116,24 +143,33 @@ library PbChain { { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.hashPreimages = new bytes[](cnts[2]); - cnts[2] = 0; // reset counter for later use + uint256[] memory _arr2 = new uint256[](raw.length / 2); + uint256 _cnt2 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.condPay = bytes(buf.decBytes()); - } else if (tag == 2) { - m.hashPreimages[cnts[2]] = bytes(buf.decBytes()); - cnts[2]++; + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.condPay = buf.decBytes(); + } else if (key == 18) { + // tag 2 + bytes memory _v2 = buf.decBytes(); + assembly ("memory-safe") { mstore(add(add(_arr2, 32), shl(5, _cnt2)), _v2) } + unchecked { + _cnt2++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } + } + + bytes[] memory _result2; + assembly ("memory-safe") { + mstore(_arr2, _cnt2) + _result2 := _arr2 } + m.hashPreimages = _result2; } // end decoder ResolvePayByConditionsRequest struct SignedSimplexState { @@ -144,24 +180,33 @@ library PbChain { function decSignedSimplexState(bytes memory raw) internal pure returns (SignedSimplexState memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.sigs = new bytes[](cnts[2]); - cnts[2] = 0; // reset counter for later use + uint256[] memory _arr2 = new uint256[](raw.length / 2); + uint256 _cnt2 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.simplexState = bytes(buf.decBytes()); - } else if (tag == 2) { - m.sigs[cnts[2]] = bytes(buf.decBytes()); - cnts[2]++; + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.simplexState = buf.decBytes(); + } else if (key == 18) { + // tag 2 + bytes memory _v2 = buf.decBytes(); + assembly ("memory-safe") { mstore(add(add(_arr2, 32), shl(5, _cnt2)), _v2) } + unchecked { + _cnt2++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } + } + + bytes[] memory _result2; + assembly ("memory-safe") { + mstore(_arr2, _cnt2) + _result2 := _arr2 } + m.sigs = _result2; } // end decoder SignedSimplexState struct SignedSimplexStateArray { @@ -171,22 +216,30 @@ library PbChain { function decSignedSimplexStateArray(bytes memory raw) internal pure returns (SignedSimplexStateArray memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(1); - m.signedSimplexStates = new SignedSimplexState[](cnts[1]); - cnts[1] = 0; // reset counter for later use + uint256[] memory _arr1 = new uint256[](raw.length / 2); + uint256 _cnt1 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.signedSimplexStates[cnts[1]] = decSignedSimplexState(buf.decBytes()); - cnts[1]++; + key = buf.decVarint(); + if (key == 10) { + // tag 1 + SignedSimplexState memory _v1 = decSignedSimplexState(buf.decBytes()); + assembly ("memory-safe") { mstore(add(add(_arr1, 32), shl(5, _cnt1)), _v1) } + unchecked { + _cnt1++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } + + SignedSimplexState[] memory _result1; + assembly ("memory-safe") { + mstore(_arr1, _cnt1) + _result1 := _arr1 + } + m.signedSimplexStates = _result1; } // end decoder SignedSimplexStateArray struct ChannelMigrationRequest { @@ -197,23 +250,32 @@ library PbChain { function decChannelMigrationRequest(bytes memory raw) internal pure returns (ChannelMigrationRequest memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.sigs = new bytes[](cnts[2]); - cnts[2] = 0; // reset counter for later use + uint256[] memory _arr2 = new uint256[](raw.length / 2); + uint256 _cnt2 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.channelMigrationInfo = bytes(buf.decBytes()); - } else if (tag == 2) { - m.sigs[cnts[2]] = bytes(buf.decBytes()); - cnts[2]++; + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.channelMigrationInfo = buf.decBytes(); + } else if (key == 18) { + // tag 2 + bytes memory _v2 = buf.decBytes(); + assembly ("memory-safe") { mstore(add(add(_arr2, 32), shl(5, _cnt2)), _v2) } + unchecked { + _cnt2++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } + } + + bytes[] memory _result2; + assembly ("memory-safe") { + mstore(_arr2, _cnt2) + _result2 := _arr2 } + m.sigs = _result2; } // end decoder ChannelMigrationRequest } diff --git a/src/lib/data/PbEntity.sol b/src/lib/data/PbEntity.sol index e08a08f..2640eca 100644 --- a/src/lib/data/PbEntity.sol +++ b/src/lib/data/PbEntity.sol @@ -69,18 +69,18 @@ library PbEntity { function decAccountAmtPair(bytes memory raw) internal pure returns (AccountAmtPair memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.account = Pb._address(buf.decBytes()); - } else if (tag == 2) { - m.amt = Pb._uint256(buf.decBytes()); + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.account = buf.decAddress(); + } else if (key == 18) { + // tag 2 + m.amt = buf.decUint256(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder AccountAmtPair @@ -92,18 +92,18 @@ library PbEntity { function decTokenInfo(bytes memory raw) internal pure returns (TokenInfo memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { + key = buf.decVarint(); + if (key == 8) { + // tag 1 m.tokenType = TokenType(buf.decVarint()); - } else if (tag == 2) { - m.tokenAddress = Pb._address(buf.decBytes()); + } else if (key == 18) { + // tag 2 + m.tokenAddress = buf.decAddress(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder TokenInfo @@ -115,24 +115,33 @@ library PbEntity { function decTokenDistribution(bytes memory raw) internal pure returns (TokenDistribution memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.distribution = new AccountAmtPair[](cnts[2]); - cnts[2] = 0; // reset counter for later use + uint256[] memory _arr2 = new uint256[](raw.length / 2); + uint256 _cnt2 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { + key = buf.decVarint(); + if (key == 10) { + // tag 1 m.token = decTokenInfo(buf.decBytes()); - } else if (tag == 2) { - m.distribution[cnts[2]] = decAccountAmtPair(buf.decBytes()); - cnts[2]++; + } else if (key == 18) { + // tag 2 + AccountAmtPair memory _v2 = decAccountAmtPair(buf.decBytes()); + assembly ("memory-safe") { mstore(add(add(_arr2, 32), shl(5, _cnt2)), _v2) } + unchecked { + _cnt2++; + } } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } + + AccountAmtPair[] memory _result2; + assembly ("memory-safe") { + mstore(_arr2, _cnt2) + _result2 := _arr2 + } + m.distribution = _result2; } // end decoder TokenDistribution struct TokenTransfer { @@ -143,18 +152,18 @@ library PbEntity { function decTokenTransfer(bytes memory raw) internal pure returns (TokenTransfer memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { + key = buf.decVarint(); + if (key == 10) { + // tag 1 m.token = decTokenInfo(buf.decBytes()); - } else if (tag == 2) { + } else if (key == 18) { + // tag 2 m.receiver = decAccountAmtPair(buf.decBytes()); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder TokenTransfer @@ -171,28 +180,33 @@ library PbEntity { function decSimplexPaymentChannel(bytes memory raw) internal pure returns (SimplexPaymentChannel memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.channelId = Pb._bytes32(buf.decBytes()); - } else if (tag == 2) { - m.peerFrom = Pb._address(buf.decBytes()); - } else if (tag == 3) { + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.channelId = buf.decBytes32(); + } else if (key == 18) { + // tag 2 + m.peerFrom = buf.decAddress(); + } else if (key == 24) { + // tag 3 m.seqNum = buf.decVarint(); - } else if (tag == 4) { + } else if (key == 34) { + // tag 4 m.transferToPeer = decTokenTransfer(buf.decBytes()); - } else if (tag == 5) { + } else if (key == 42) { + // tag 5 m.pendingPayIds = decPayIdList(buf.decBytes()); - } else if (tag == 6) { + } else if (key == 48) { + // tag 6 m.lastPayResolveDeadline = buf.decVarint(); - } else if (tag == 7) { - m.totalPendingAmount = Pb._uint256(buf.decBytes()); + } else if (key == 58) { + // tag 7 + m.totalPendingAmount = buf.decUint256(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder SimplexPaymentChannel @@ -204,24 +218,28 @@ library PbEntity { function decPayIdList(bytes memory raw) internal pure returns (PayIdList memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(2); - m.payIds = new bytes32[](cnts[1]); - cnts[1] = 0; // reset counter for later use + bytes32[] memory _arr1 = new bytes32[](raw.length / 34); + uint256 _cnt1 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.payIds[cnts[1]] = Pb._bytes32(buf.decBytes()); - cnts[1]++; - } else if (tag == 2) { - m.nextListHash = Pb._bytes32(buf.decBytes()); + key = buf.decVarint(); + if (key == 10) { + // tag 1 + _arr1[_cnt1] = buf.decBytes32(); + unchecked { + _cnt1++; + } + } else if (key == 18) { + // tag 2 + m.nextListHash = buf.decBytes32(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } + + assembly ("memory-safe") { mstore(_arr1, _cnt1) } + m.payIds = _arr1; } // end decoder PayIdList struct TransferFunction { @@ -232,18 +250,18 @@ library PbEntity { function decTransferFunction(bytes memory raw) internal pure returns (TransferFunction memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { + key = buf.decVarint(); + if (key == 8) { + // tag 1 m.logicType = TransferFunctionType(buf.decVarint()); - } else if (tag == 2) { + } else if (key == 18) { + // tag 2 m.maxTransfer = decTokenTransfer(buf.decBytes()); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder TransferFunction @@ -261,36 +279,51 @@ library PbEntity { function decConditionalPay(bytes memory raw) internal pure returns (ConditionalPay memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(8); - m.conditions = new Condition[](cnts[4]); - cnts[4] = 0; // reset counter for later use + uint256[] memory _arr4 = new uint256[](raw.length / 2); + uint256 _cnt4 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { + key = buf.decVarint(); + if (key == 8) { + // tag 1 m.payTimestamp = buf.decVarint(); - } else if (tag == 2) { - m.src = Pb._address(buf.decBytes()); - } else if (tag == 3) { - m.dest = Pb._address(buf.decBytes()); - } else if (tag == 4) { - m.conditions[cnts[4]] = decCondition(buf.decBytes()); - cnts[4]++; - } else if (tag == 5) { + } else if (key == 18) { + // tag 2 + m.src = buf.decAddress(); + } else if (key == 26) { + // tag 3 + m.dest = buf.decAddress(); + } else if (key == 34) { + // tag 4 + Condition memory _v4 = decCondition(buf.decBytes()); + assembly ("memory-safe") { mstore(add(add(_arr4, 32), shl(5, _cnt4)), _v4) } + unchecked { + _cnt4++; + } + } else if (key == 42) { + // tag 5 m.transferFunc = decTransferFunction(buf.decBytes()); - } else if (tag == 6) { + } else if (key == 48) { + // tag 6 m.resolveDeadline = buf.decVarint(); - } else if (tag == 7) { + } else if (key == 56) { + // tag 7 m.resolveTimeout = buf.decVarint(); - } else if (tag == 8) { - m.payResolver = Pb._address(buf.decBytes()); + } else if (key == 66) { + // tag 8 + m.payResolver = buf.decAddress(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } + + Condition[] memory _result4; + assembly ("memory-safe") { + mstore(_arr4, _cnt4) + _result4 := _arr4 + } + m.conditions = _result4; } // end decoder ConditionalPay struct CondPayResult { @@ -301,18 +334,18 @@ library PbEntity { function decCondPayResult(bytes memory raw) internal pure returns (CondPayResult memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.condPay = bytes(buf.decBytes()); - } else if (tag == 2) { - m.amount = Pb._uint256(buf.decBytes()); + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.condPay = buf.decBytes(); + } else if (key == 18) { + // tag 2 + m.amount = buf.decUint256(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder CondPayResult @@ -325,20 +358,21 @@ library PbEntity { function decVouchedCondPayResult(bytes memory raw) internal pure returns (VouchedCondPayResult memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.condPayResult = bytes(buf.decBytes()); - } else if (tag == 2) { - m.sigOfSrc = bytes(buf.decBytes()); - } else if (tag == 3) { - m.sigOfDest = bytes(buf.decBytes()); + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.condPayResult = buf.decBytes(); + } else if (key == 18) { + // tag 2 + m.sigOfSrc = buf.decBytes(); + } else if (key == 26) { + // tag 3 + m.sigOfDest = buf.decBytes(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder VouchedCondPayResult @@ -354,26 +388,30 @@ library PbEntity { function decCondition(bytes memory raw) internal pure returns (Condition memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { + key = buf.decVarint(); + if (key == 8) { + // tag 1 m.conditionType = ConditionType(buf.decVarint()); - } else if (tag == 2) { - m.hashLock = Pb._bytes32(buf.decBytes()); - } else if (tag == 3) { - m.deployedContractAddress = Pb._address(buf.decBytes()); - } else if (tag == 4) { - m.virtualContractAddress = Pb._bytes32(buf.decBytes()); - } else if (tag == 5) { - m.argsQueryFinalization = bytes(buf.decBytes()); - } else if (tag == 6) { - m.argsQueryOutcome = bytes(buf.decBytes()); + } else if (key == 18) { + // tag 2 + m.hashLock = buf.decBytes32(); + } else if (key == 26) { + // tag 3 + m.deployedContractAddress = buf.decAddress(); + } else if (key == 34) { + // tag 4 + m.virtualContractAddress = buf.decBytes32(); + } else if (key == 42) { + // tag 5 + m.argsQueryFinalization = buf.decBytes(); + } else if (key == 50) { + // tag 6 + m.argsQueryOutcome = buf.decBytes(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder Condition @@ -388,24 +426,27 @@ library PbEntity { function decCooperativeWithdrawInfo(bytes memory raw) internal pure returns (CooperativeWithdrawInfo memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.channelId = Pb._bytes32(buf.decBytes()); - } else if (tag == 2) { + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.channelId = buf.decBytes32(); + } else if (key == 16) { + // tag 2 m.seqNum = buf.decVarint(); - } else if (tag == 3) { + } else if (key == 26) { + // tag 3 m.withdraw = decAccountAmtPair(buf.decBytes()); - } else if (tag == 4) { + } else if (key == 32) { + // tag 4 m.withdrawDeadline = buf.decVarint(); - } else if (tag == 5) { - m.recipientChannelId = Pb._bytes32(buf.decBytes()); + } else if (key == 42) { + // tag 5 + m.recipientChannelId = buf.decBytes32(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder CooperativeWithdrawInfo @@ -419,22 +460,24 @@ library PbEntity { function decPaymentChannelInitializer(bytes memory raw) internal pure returns (PaymentChannelInitializer memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { + key = buf.decVarint(); + if (key == 10) { + // tag 1 m.initDistribution = decTokenDistribution(buf.decBytes()); - } else if (tag == 2) { + } else if (key == 16) { + // tag 2 m.openDeadline = buf.decVarint(); - } else if (tag == 3) { + } else if (key == 24) { + // tag 3 m.disputeTimeout = buf.decVarint(); - } else if (tag == 4) { + } else if (key == 32) { + // tag 4 m.msgValueReceiver = buf.decVarint(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder PaymentChannelInitializer @@ -448,28 +491,39 @@ library PbEntity { function decCooperativeSettleInfo(bytes memory raw) internal pure returns (CooperativeSettleInfo memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256[] memory cnts = buf.cntTags(4); - m.settleBalance = new AccountAmtPair[](cnts[3]); - cnts[3] = 0; // reset counter for later use + uint256[] memory _arr3 = new uint256[](raw.length / 2); + uint256 _cnt3 = 0; - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.channelId = Pb._bytes32(buf.decBytes()); - } else if (tag == 2) { + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.channelId = buf.decBytes32(); + } else if (key == 16) { + // tag 2 m.seqNum = buf.decVarint(); - } else if (tag == 3) { - m.settleBalance[cnts[3]] = decAccountAmtPair(buf.decBytes()); - cnts[3]++; - } else if (tag == 4) { + } else if (key == 26) { + // tag 3 + AccountAmtPair memory _v3 = decAccountAmtPair(buf.decBytes()); + assembly ("memory-safe") { mstore(add(add(_arr3, 32), shl(5, _cnt3)), _v3) } + unchecked { + _cnt3++; + } + } else if (key == 32) { + // tag 4 m.settleDeadline = buf.decVarint(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } + } + + AccountAmtPair[] memory _result3; + assembly ("memory-safe") { + mstore(_arr3, _cnt3) + _result3 := _arr3 } + m.settleBalance = _result3; } // end decoder CooperativeSettleInfo struct ChannelMigrationInfo { @@ -482,22 +536,24 @@ library PbEntity { function decChannelMigrationInfo(bytes memory raw) internal pure returns (ChannelMigrationInfo memory m) { Pb.Buffer memory buf = Pb.fromBytes(raw); - uint256 tag; - Pb.WireType wire; + uint256 key; while (buf.hasMore()) { - (tag, wire) = buf.decKey(); - if (false) {} // solidity has no switch/case - else if (tag == 1) { - m.channelId = Pb._bytes32(buf.decBytes()); - } else if (tag == 2) { - m.fromLedgerAddress = Pb._address(buf.decBytes()); - } else if (tag == 3) { - m.toLedgerAddress = Pb._address(buf.decBytes()); - } else if (tag == 4) { + key = buf.decVarint(); + if (key == 10) { + // tag 1 + m.channelId = buf.decBytes32(); + } else if (key == 18) { + // tag 2 + m.fromLedgerAddress = buf.decAddress(); + } else if (key == 26) { + // tag 3 + m.toLedgerAddress = buf.decAddress(); + } else if (key == 32) { + // tag 4 m.migrationDeadline = buf.decVarint(); } else { - buf.skipValue(wire); - } // skip value of unknown tag + buf.skipValue(Pb.WireType(key & 7)); // unknown tag or wrong wire + } } } // end decoder ChannelMigrationInfo } diff --git a/src/lib/data/proto/chain.proto b/src/lib/data/proto/chain.proto index 3139a91..48ad1e9 100644 --- a/src/lib/data/proto/chain.proto +++ b/src/lib/data/proto/chain.proto @@ -1,10 +1,11 @@ -// Copyright 2018-2025 Celer Network +// Copyright 2018-2026 Celer Network syntax = "proto3"; -option go_package = "github.com/celer-network/agent-pay/chain"; package chain; +option go_package = "github.com/celer-network/agent-pay/chain"; + // Next Tag: 3 message OpenChannelRequest { // serialized entity.PaymentChannelInitializer message @@ -48,12 +49,12 @@ message SignedSimplexState { // Next Tag: 2 // Using this message to intendSettle is flexible: // * you can put multiple signed simplex states in one array as long as not exceeding gas limit -// * you can put signed simplex states of multiple channels in one array +// * you can put signed simplex states of multiple channels in one array // to intendSettle these channels in one function call // How to use: // * channelIds of these simplex states must be ascending // * non-null simplex states should be cosigned by both peers of the channel -// * null simplex states should set seqNum as 0 and be signed by +// * null simplex states should set seqNum as 0 and be signed by // exactly one of the peers of the channel // Note: if you are submitting one simplex state of the duplex channel, // you are intending to settle the whole duplex channel. diff --git a/src/lib/data/proto/entity.proto b/src/lib/data/proto/entity.proto index 567553b..aca6b94 100644 --- a/src/lib/data/proto/entity.proto +++ b/src/lib/data/proto/entity.proto @@ -1,11 +1,12 @@ -// Copyright 2018-2025 Celer Network +// Copyright 2018-2026 Celer Network syntax = "proto3"; -option go_package = "github.com/celer-network/agent-pay/entity"; + +package entity; import "google/protobuf/descriptor.proto"; -package entity; +option go_package = "github.com/celer-network/agent-pay/entity"; extend google.protobuf.FieldOptions { string soltype = 1001; @@ -51,7 +52,7 @@ message SimplexPaymentChannel { TokenTransfer transfer_to_peer = 4; // head of the idlist chain of all pending conditional pays. PayIdList pending_pay_ids = 5; - // The last resolve deadline of all pending conditonal pays. + // The last resolve deadline of all pending conditional pays. // confirmSettle must be called after all pending pays have been finalized, // namely all pending pays have been resolved in the pay registry, // or after the last_pay_resolve_deadline. @@ -62,11 +63,11 @@ message SimplexPaymentChannel { // Next Tag: 3 message PayIdList { - // array of ids of serialized ConditionalPay - // pay_id is computed as hash(hash(cond_pay), pay_resolver_address) - repeated bytes pay_ids = 1 [(soltype) = "bytes32"]; - // hash of serialized next PayIdList - bytes next_list_hash = 2 [(soltype) = "bytes32"]; + // array of ids of serialized ConditionalPay + // pay_id is computed as hash(hash(cond_pay), pay_resolver_address) + repeated bytes pay_ids = 1 [(soltype) = "bytes32"]; + // hash of serialized next PayIdList + bytes next_list_hash = 2 [(soltype) = "bytes32"]; } enum TransferFunctionType { @@ -95,7 +96,10 @@ message TransferFunction { message ConditionalPay { // pay_timestamp is set by payment source, to ensure no same condpay between src-dst // global unique pay id used on-chain is computed as hash(hash(cond_pay), pay_resolver_address) - uint64 pay_timestamp = 1 [(soltype) = "uint", jstype = JS_STRING]; + uint64 pay_timestamp = 1 [ + (soltype) = "uint", + jstype = JS_STRING + ]; // src and dest are public keys of payment sender and receiver used to vouch the payment result. // For simplicity, current off-chain backend implementation requires these two fields to be the // sender and receiver account addresses. With better off-chain communication protocols in the @@ -109,7 +113,7 @@ message ConditionalPay { // transfer_func.max_transfer.receiver.account is not needed for unicast payment TransferFunction transfer_func = 5; // resolve_deadline is the deadline for a cond_pay to be resolved on chain in the - // pay resgistry by either condition or vouched results, and the payment result + // pay registry by either condition or vouched results, and the payment result // is finalized after resolve_deadline. // Payment channel peers should apply a safe_margin for off-chain processing, // i.e., should take dispute action before [resolve_deadline - safe_margin] diff --git a/src/lib/interface/ICelerLedger.sol b/src/lib/interface/ICelerLedger.sol index 2123aa2..e5cb2b5 100644 --- a/src/lib/interface/ICelerLedger.sol +++ b/src/lib/interface/ICelerLedger.sol @@ -195,7 +195,7 @@ interface ICelerLedger { // LedgerChannel — view functions and channel-state derivations // ========================================================================= - /// @notice Block number after which a settling channel can be confirmed. + /// @notice Unix timestamp (seconds) after which a settling channel can be confirmed. function getSettleFinalizedTime(bytes32 _channelId) external view returns (uint256); /// @notice ERC-20 token contract address for this channel (`address(0)` for ETH). @@ -275,7 +275,7 @@ interface ICelerLedger { * @notice Active unilateral withdrawal intent for a channel, if any. * @return receiver Withdrawer address. * @return amount Pending withdraw amount. - * @return requestTime Block number when {intendWithdraw} fired. + * @return requestTime Unix timestamp (seconds) when {intendWithdraw} fired. * @return recipientChannelId Optional redirect target. */ function getWithdrawIntent(bytes32 _channelId) diff --git a/src/lib/interface/IPayRegistry.sol b/src/lib/interface/IPayRegistry.sol index 2d1abeb..fb4951c 100644 --- a/src/lib/interface/IPayRegistry.sol +++ b/src/lib/interface/IPayRegistry.sol @@ -29,7 +29,7 @@ interface IPayRegistry { /** * @notice Set the resolve deadline for a payment under `msg.sender`'s namespace. * @param _payHash `keccak256(serializedConditionalPay)`. - * @param _deadline Block number after which the result is finalized. + * @param _deadline Unix timestamp (seconds) after which the result is finalized. */ function setPayDeadline(bytes32 _payHash, uint256 _deadline) external; @@ -37,7 +37,7 @@ interface IPayRegistry { * @notice Set both the amount and the deadline for a payment in one call. * @param _payHash `keccak256(serializedConditionalPay)`. * @param _amt Resolved payment amount. - * @param _deadline Block number after which the result is finalized. + * @param _deadline Unix timestamp (seconds) after which the result is finalized. */ function setPayInfo(bytes32 _payHash, uint256 _amt, uint256 _deadline) external; @@ -82,7 +82,7 @@ interface IPayRegistry { * @notice Read the (amount, deadline) tuple for a single payment. * @param _payId Pay id. * @return amount Resolved payment amount. - * @return resolveDeadline Block number after which the result is finalized. + * @return resolveDeadline Unix timestamp (seconds) after which the result is finalized. */ function getPayInfo(bytes32 _payId) external view returns (uint256 amount, uint256 resolveDeadline); diff --git a/src/lib/interface/IRouterRegistry.sol b/src/lib/interface/IRouterRegistry.sol index 4d50eeb..81b0bfb 100644 --- a/src/lib/interface/IRouterRegistry.sol +++ b/src/lib/interface/IRouterRegistry.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.20; /** * @title RouterRegistry interface * @notice Optional global registry where relay-router operators advertise themselves - * to the AgentPay network. The registry stores the latest registration / refresh - * block number per router address; consumers may use it to discover live routers - * off-chain. + * to the AgentPay network. The registry stores the unix timestamp (seconds) of + * the latest registration / refresh per router address; consumers may use it to + * discover live routers off-chain. */ interface IRouterRegistry { /// @notice Type of a {RouterUpdated} event. @@ -18,7 +18,7 @@ interface IRouterRegistry { /** * @notice Register `msg.sender` as a router; reverts if already registered. - * @dev Stores the current `block.number` against the caller's address. + * @dev Stores the current `block.timestamp` against the caller's address. */ function registerRouter() external; @@ -28,7 +28,7 @@ interface IRouterRegistry { function deregisterRouter() external; /** - * @notice Refresh `msg.sender`'s stored block number; reverts if not registered. + * @notice Refresh `msg.sender`'s stored timestamp; reverts if not registered. * @dev Used by routers to signal liveness without removing/re-adding. */ function refreshRouter() external; diff --git a/src/lib/ledgerlib/LedgerMigrate.sol b/src/lib/ledgerlib/LedgerMigrate.sol index 49c89b0..6ee1786 100644 --- a/src/lib/ledgerlib/LedgerMigrate.sol +++ b/src/lib/ledgerlib/LedgerMigrate.sol @@ -44,7 +44,7 @@ library LedgerMigrate { 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.number <= migrationInfo.migrationDeadline, "Passed migration deadline"); + require(block.timestamp <= migrationInfo.migrationDeadline, "Passed migration deadline"); _self._updateChannelStatus(c, LedgerStruct.ChannelStatus.Migrated); c.migratedTo = toLedgerAddr; diff --git a/src/lib/ledgerlib/LedgerOperation.sol b/src/lib/ledgerlib/LedgerOperation.sol index 1423b50..8b51865 100644 --- a/src/lib/ledgerlib/LedgerOperation.sol +++ b/src/lib/ledgerlib/LedgerOperation.sol @@ -32,7 +32,7 @@ library LedgerOperation { PbEntity.PaymentChannelInitializer memory channelInitializer = PbEntity.decPaymentChannelInitializer(openRequest.channelInitializer); require(channelInitializer.initDistribution.distribution.length == 2, "Wrong length"); - require(block.number <= channelInitializer.openDeadline, "Open deadline passed"); + require(block.timestamp <= channelInitializer.openDeadline, "Open deadline passed"); PbEntity.TokenInfo memory token = channelInitializer.initDistribution.token; uint256[2] memory amounts = [ @@ -221,7 +221,7 @@ library LedgerOperation { withdrawIntent.receiver = receiver; withdrawIntent.amount = _amount; - withdrawIntent.requestTime = block.number; + withdrawIntent.requestTime = block.timestamp; withdrawIntent.recipientChannelId = _recipientChannelId; emit IntendWithdraw(_channelId, receiver, _amount); @@ -237,7 +237,7 @@ library LedgerOperation { 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.number >= c.withdrawIntent.requestTime + c.disputeTimeout, "Dispute not timeout"); + require(block.timestamp >= c.withdrawIntent.requestTime + c.disputeTimeout, "Dispute not timeout"); address receiver = c.withdrawIntent.receiver; uint256 amount = c.withdrawIntent.amount; @@ -299,7 +299,7 @@ library LedgerOperation { require(c._checkCoSignatures(h, cooperativeWithdrawRequest.sigs), "Check co-sigs failed"); // require an increment of exactly 1 for seqNum of each cooperative withdraw request require(withdrawInfo.seqNum - c.cooperativeWithdrawSeqNum == 1, "seqNum error"); - require(block.number <= withdrawInfo.withdrawDeadline, "Withdraw deadline passed"); + require(block.timestamp <= withdrawInfo.withdrawDeadline, "Withdraw deadline passed"); address receiver = withdrawInfo.withdraw.account; c.cooperativeWithdrawSeqNum = withdrawInfo.seqNum; @@ -345,7 +345,9 @@ library LedgerOperation { // A nonpeer cannot be the first one to call intendSettle require(c.status == LedgerStruct.ChannelStatus.Settling, "Nonpeer channel status error"); } - require(c.settleFinalizedTime == 0 || block.number < c.settleFinalizedTime, "Settle has already finalized"); + require( + c.settleFinalizedTime == 0 || block.timestamp < c.settleFinalizedTime, "Settle has already finalized" + ); bytes32 stateHash = keccak256(signedSimplexStateArray.signedSimplexStates[i].simplexState); bytes[] memory sigs = signedSimplexStateArray.signedSimplexStates[i].sigs; @@ -438,25 +440,25 @@ library LedgerOperation { function confirmSettle(LedgerStruct.Ledger storage _self, bytes32 _channelId) external { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; LedgerStruct.PeerProfile[2] storage peerProfiles = c.peerProfiles; - uint256 blockNumber = block.number; + uint256 nowTs = block.timestamp; require(c.status == LedgerStruct.ChannelStatus.Settling, "Channel status error"); // require no new intendSettle can be called - require(blockNumber >= c.settleFinalizedTime, "Settle is not finalized"); + require(nowTs >= c.settleFinalizedTime, "Settle is not finalized"); // require channel status of current intendSettle has been finalized, // namely all payments have already been either cleared or expired // Note: this lastPayResolveDeadline should use // (the actual last resolve deadline of all pays + clearPays safe margin) // to ensure that peers have enough time to clearPays before confirmSettle. - // However this only matters if there are multiple blocks of pending pay list - // i.e. the nextPayIdListHash after intendSettle is not bytes32(0). + // However this only matters if there are multiple segments of the pending + // pay list, i.e. the nextPayIdListHash after intendSettle is not bytes32(0). // TODO: add an additional clearSafeMargin param or change the semantics of // lastPayResolveDeadline to also include clearPays safe margin and rename it. require( (peerProfiles[0].state.nextPayIdListHash == bytes32(0) - || blockNumber > peerProfiles[0].state.lastPayResolveDeadline) + || nowTs > peerProfiles[0].state.lastPayResolveDeadline) && (peerProfiles[1].state.nextPayIdListHash == bytes32(0) - || blockNumber > peerProfiles[1].state.lastPayResolveDeadline), + || nowTs > peerProfiles[1].state.lastPayResolveDeadline), "Payments are not finalized" ); @@ -501,7 +503,7 @@ library LedgerOperation { settleInfo.seqNum > c.peerProfiles[0].state.seqNum && settleInfo.seqNum > c.peerProfiles[1].state.seqNum, "seqNum error" ); - require(settleInfo.settleDeadline >= block.number, "Settle deadline passed"); + require(settleInfo.settleDeadline >= block.timestamp, "Settle deadline passed"); // require distribution is consistent with the order of peerAddrs in channel require( settleInfo.settleBalance[0].account == peerAddrs[0] && settleInfo.settleBalance[1].account == peerAddrs[1], @@ -729,7 +731,7 @@ library LedgerOperation { */ function _updateOverallStatesByIntendState(LedgerStruct.Ledger storage _self, bytes32 _channelId) internal { LedgerStruct.Channel storage c = _self.channelMap[_channelId]; - c.settleFinalizedTime = block.number + c.disputeTimeout; + c.settleFinalizedTime = block.timestamp + c.disputeTimeout; _updateChannelStatus(_self, c, LedgerStruct.ChannelStatus.Settling); emit IntendSettle(_channelId, c._getStateSeqNums()); diff --git a/src/lib/ledgerlib/LedgerStruct.sol b/src/lib/ledgerlib/LedgerStruct.sol index 5325659..c781e10 100644 --- a/src/lib/ledgerlib/LedgerStruct.sol +++ b/src/lib/ledgerlib/LedgerStruct.sol @@ -68,9 +68,10 @@ library LedgerStruct { * ledger. */ struct Channel { - // Block number after which peers may call confirmSettle, and before which - // peers may still call intendSettle. + // Unix timestamp (seconds) after which peers may call confirmSettle, and before + // which peers may still call intendSettle. uint256 settleFinalizedTime; + // Dispute-challenge window length in seconds. uint256 disputeTimeout; PbEntity.TokenInfo token; ChannelStatus status; diff --git a/test/CelerLedger.ERC20.t.sol b/test/CelerLedger.ERC20.t.sol index 16f004d..732412a 100644 --- a/test/CelerLedger.ERC20.t.sol +++ b/test/CelerLedger.ERC20.t.sol @@ -104,7 +104,7 @@ contract CelerLedgerErc20Test is LedgerTestBase { celerLedger.disableBalanceLimits(); bytes32 channelId = _openFundedErc20Channel([uint256(200), 0]); - bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.number + 1000); + bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.timestamp + 1000); uint256 peer0Before = erc20.balanceOf(peer0); uint256 peer1Before = erc20.balanceOf(peer1); @@ -126,7 +126,7 @@ contract CelerLedgerErc20Test is LedgerTestBase { vm.prank(peer0); celerLedger.intendSettle(array); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 peer0Before = erc20.balanceOf(peer0); celerLedger.confirmSettle(channelId); diff --git a/test/CelerLedger.ETH.t.sol b/test/CelerLedger.ETH.t.sol index 8a51ee4..5315b3f 100644 --- a/test/CelerLedger.ETH.t.sol +++ b/test/CelerLedger.ETH.t.sol @@ -51,9 +51,7 @@ contract CelerLedgerEthTest is LedgerTestBase { } function test_openChannel_afterDeadline_reverts() public { - // Roll forward so any small openDeadline is already past. - vm.roll(100); - (bytes memory request,,) = _buildOpenEth([uint256(0), 0], 0, 1); + (bytes memory request,,) = _buildOpenEth([uint256(0), 0], 0, block.timestamp - 1); vm.expectRevert(bytes("Open deadline passed")); celerLedger.openChannel(request); @@ -232,7 +230,7 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.disableBalanceLimits(); bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); - bytes memory request = _buildCoopWithdraw(channelId, 1, peer0, 100, block.number + 1000, bytes32(0)); + bytes memory request = _buildCoopWithdraw(channelId, 1, peer0, 100, block.timestamp + 1000, bytes32(0)); uint256 peer0BalBefore = peer0.balance; celerLedger.cooperativeWithdraw(request); @@ -247,7 +245,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes32 srcChannel = _openFundedEthChannel([uint256(200), 0]); bytes32 dstChannel = _openZeroEthChannel(); - bytes memory request = _buildCoopWithdraw(srcChannel, 1, peer0, 80, block.number + 1000, dstChannel); + bytes memory request = _buildCoopWithdraw(srcChannel, 1, peer0, 80, block.timestamp + 1000, dstChannel); celerLedger.cooperativeWithdraw(request); // Source channel debited 80; destination channel credited 80 to peer0. @@ -263,8 +261,7 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.disableBalanceLimits(); bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); - vm.roll(2); - bytes memory request = _buildCoopWithdraw(channelId, 1, peer0, 100, 1, bytes32(0)); + bytes memory request = _buildCoopWithdraw(channelId, 1, peer0, 100, block.timestamp - 1, bytes32(0)); vm.expectRevert(bytes("Withdraw deadline passed")); celerLedger.cooperativeWithdraw(request); @@ -275,11 +272,11 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); // First withdraw at seqNum=1 (must be exactly current+1). - bytes memory r1 = _buildCoopWithdraw(channelId, 1, peer0, 50, block.number + 1000, bytes32(0)); + bytes memory r1 = _buildCoopWithdraw(channelId, 1, peer0, 50, block.timestamp + 1000, bytes32(0)); celerLedger.cooperativeWithdraw(r1); // Try seqNum=1 again (should be 2 next) — reverts. - bytes memory r2 = _buildCoopWithdraw(channelId, 1, peer0, 50, block.number + 1000, bytes32(0)); + bytes memory r2 = _buildCoopWithdraw(channelId, 1, peer0, 50, block.timestamp + 1000, bytes32(0)); vm.expectRevert(bytes("seqNum error")); celerLedger.cooperativeWithdraw(r2); } @@ -300,7 +297,7 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.openChannel(openReq); bytes32 ethChannel = _openFundedEthChannel([uint256(200), 0]); - bytes memory request = _buildCoopWithdraw(ethChannel, 1, peer0, 50, block.number + 1000, erc20Channel); + bytes memory request = _buildCoopWithdraw(ethChannel, 1, peer0, 50, block.timestamp + 1000, erc20Channel); vm.expectRevert(bytes("Token mismatch of recipient channel")); celerLedger.cooperativeWithdraw(request); @@ -315,7 +312,7 @@ contract CelerLedgerEthTest is LedgerTestBase { seqNum: 1, withdrawAccount: peer0, withdrawAmount: 50, - withdrawDeadline: block.number + 1000, + withdrawDeadline: block.timestamp + 1000, recipientChannelId: bytes32(0) }); bytes memory body = Fixtures.encCooperativeWithdrawInfo(w); @@ -376,7 +373,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendWithdraw(channelId, 50, bytes32(0)); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 peer0BalBefore = peer0.balance; celerLedger.confirmWithdraw(channelId); @@ -393,7 +390,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendWithdraw(srcChannel, 60, dstChannel); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); celerLedger.confirmWithdraw(srcChannel); assertEq(celerLedger.getTotalBalance(srcChannel), 140); @@ -418,7 +415,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendWithdraw(channelId, 50, bytes32(0)); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); celerLedger.confirmWithdraw(channelId); vm.expectRevert(); @@ -535,7 +532,7 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.disableBalanceLimits(); bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); - bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.number + 1000); + bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.timestamp + 1000); uint256 peer0Before = peer0.balance; uint256 peer1Before = peer1.balance; @@ -551,7 +548,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); // Total = 200 but settleBalance = 100 + 50 = 150 → revert. - bytes memory request = _buildCoopSettle(channelId, 1, [uint256(100), 50], block.number + 1000); + bytes memory request = _buildCoopSettle(channelId, 1, [uint256(100), 50], block.timestamp + 1000); vm.expectRevert(bytes("Balance sum mismatch")); celerLedger.cooperativeSettle(request); @@ -561,9 +558,8 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.disableBalanceLimits(); bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); - // Deadline = 1, then roll past. - bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], 1); - vm.roll(2); + // Deadline already in the past. + bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.timestamp - 1); vm.expectRevert(bytes("Settle deadline passed")); celerLedger.cooperativeSettle(request); @@ -578,7 +574,7 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.snapshotStates(_wrapStateArray(simplex, "")); // settleInfo seqNum must be > both peer seqNums; 1 fails (peer0 already 1). - bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.number + 1000); + bytes memory request = _buildCoopSettle(channelId, 1, [uint256(120), 80], block.timestamp + 1000); vm.expectRevert(bytes("seqNum error")); celerLedger.cooperativeSettle(request); } @@ -592,7 +588,7 @@ contract CelerLedgerEthTest is LedgerTestBase { seqNum: 1, settleAccounts: [peer0, peer1], settleAmounts: [uint256(120), 80], - settleDeadline: block.number + 1000 + settleDeadline: block.timestamp + 1000 }); bytes memory body = Fixtures.encCooperativeSettleInfo(s); bytes[] memory sigs = new bytes[](1); @@ -620,7 +616,7 @@ contract CelerLedgerEthTest is LedgerTestBase { celerLedger.intendSettle(array); assertEq(uint256(celerLedger.getChannelStatus(channelId)), uint256(LedgerStruct.ChannelStatus.Settling)); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 peer0Before = peer0.balance; celerLedger.confirmSettle(channelId); @@ -641,7 +637,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendSettle(array); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 peer0Before = peer0.balance; uint256 peer1Before = peer1.balance; celerLedger.confirmSettle(channelId); @@ -714,7 +710,7 @@ contract CelerLedgerEthTest is LedgerTestBase { (, uint256[2] memory transferOuts) = celerLedger.getTransferOutMap(channelId); assertEq(transferOuts[0], 60); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 peer1Before = peer1.balance; celerLedger.confirmSettle(channelId); assertEq(peer1.balance, peer1Before + 60); @@ -733,7 +729,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes memory array = _wrapStateArray(signedSimplexWithPays, s1); // Roll past the pay's onchain resolve deadline so getPayAmounts reads it. - vm.roll(block.number + 10); + vm.warp(block.timestamp + 10); vm.prank(peer0); celerLedger.intendSettle(array); @@ -741,7 +737,7 @@ contract CelerLedgerEthTest is LedgerTestBase { (, uint256[2] memory transferOuts) = celerLedger.getTransferOutMap(channelId); assertEq(transferOuts[0], 25); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 peer1Before = peer1.balance; celerLedger.confirmSettle(channelId); @@ -772,7 +768,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes memory s1 = _buildSignedSimplex(channelId, peer1, 1, 0); bytes memory array = _wrapStateArray(signedSimplex, s1); - vm.roll(block.number + 10); + vm.warp(block.timestamp + 10); // intendSettle clears the head list. vm.prank(peer0); @@ -860,7 +856,7 @@ contract CelerLedgerEthTest is LedgerTestBase { vm.prank(peer0); celerLedger.intendSettle(array); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); celerLedger.confirmSettle(channelId); // Channel must reset to Operable, not Closed. @@ -897,7 +893,7 @@ contract CelerLedgerEthTest is LedgerTestBase { bytes32 channelId = _openFundedEthChannel([uint256(200), 0]); assertEq(celerLedger.getCooperativeWithdrawSeqNum(channelId), 0); - bytes memory request = _buildCoopWithdraw(channelId, 1, peer0, 50, block.number + 1000, bytes32(0)); + bytes memory request = _buildCoopWithdraw(channelId, 1, peer0, 50, block.timestamp + 1000, bytes32(0)); celerLedger.cooperativeWithdraw(request); assertEq(celerLedger.getCooperativeWithdrawSeqNum(channelId), 1); @@ -1034,7 +1030,7 @@ contract CelerLedgerEthTest is LedgerTestBase { seqNum: 1, transferAmount: 0, pendingPayIds: _payIdList, - lastPayResolveDeadline: block.number + 1000, + lastPayResolveDeadline: block.timestamp + 1000, totalPendingAmount: _totalPending }); bytes memory simplex = Fixtures.encSimplexPaymentChannel(s); diff --git a/test/CelerLedger.Migrate.t.sol b/test/CelerLedger.Migrate.t.sol index aa439c2..1908372 100644 --- a/test/CelerLedger.Migrate.t.sol +++ b/test/CelerLedger.Migrate.t.sol @@ -92,9 +92,9 @@ contract CelerLedgerMigrateTest is LedgerTestBase { function test_migrate_pastDeadline_reverts() public { bytes32 channelId = _openFundedEthChannel([uint256(100), 200]); - bytes memory request = _buildMigrationRequest(channelId, address(celerLedger), address(celerLedgerNew), 1); + bytes memory request = + _buildMigrationRequest(channelId, address(celerLedger), address(celerLedgerNew), block.timestamp - 1); - vm.roll(2); vm.expectRevert(bytes("Passed migration deadline")); celerLedgerNew.migrateChannelFrom(address(celerLedger), request); } @@ -134,8 +134,9 @@ contract CelerLedgerMigrateTest is LedgerTestBase { } function _migrate(bytes32 _channelId) internal { - bytes memory request = - _buildMigrationRequest(_channelId, address(celerLedger), address(celerLedgerNew), block.number + 100_000); + bytes memory request = _buildMigrationRequest( + _channelId, address(celerLedger), address(celerLedgerNew), block.timestamp + 100_000 + ); celerLedgerNew.migrateChannelFrom(address(celerLedger), request); } diff --git a/test/GasReport.t.sol b/test/GasReport.t.sol index 73c4982..5d9fef9 100644 --- a/test/GasReport.t.sol +++ b/test/GasReport.t.sol @@ -452,7 +452,7 @@ contract GasReport is LedgerTestBase { bytes32 ch = _openFundedEthChannel([uint256(200), 0]); vm.prank(peer0); celerLedger.intendWithdraw(ch, 50, bytes32(0)); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 g0 = gasleft(); celerLedger.confirmWithdraw(ch); g = g0 - gasleft(); @@ -462,7 +462,7 @@ contract GasReport is LedgerTestBase { function _measure_cooperativeWithdraw() internal returns (uint256 g) { uint256 snap = vm.snapshotState(); bytes32 ch = _openFundedEthChannel([uint256(200), 0]); - bytes memory request = _buildCoopWithdraw(ch, 1, peer0, 100, block.number + 1000, bytes32(0)); + bytes memory request = _buildCoopWithdraw(ch, 1, peer0, 100, block.timestamp + 1000, bytes32(0)); uint256 g0 = gasleft(); celerLedger.cooperativeWithdraw(request); g = g0 - gasleft(); @@ -473,7 +473,7 @@ contract GasReport is LedgerTestBase { uint256 snap = vm.snapshotState(); (bytes memory openReq,, bytes32 ch) = _buildOpenErc20(address(erc20), [uint256(200), 0], openDeadlineCursor++); celerLedger.openChannel(openReq); - bytes memory request = _buildCoopWithdraw(ch, 1, peer0, 100, block.number + 1000, bytes32(0)); + bytes memory request = _buildCoopWithdraw(ch, 1, peer0, 100, block.timestamp + 1000, bytes32(0)); uint256 g0 = gasleft(); celerLedger.cooperativeWithdraw(request); g = g0 - gasleft(); @@ -527,7 +527,7 @@ contract GasReport is LedgerTestBase { bytes memory s1 = _buildSimplexWithPayList(ch, peer1, 1, bListBytes, 13); bytes memory array = _wrapStateArray(s0, s1); - vm.roll(block.number + 10); + vm.warp(block.timestamp + 10); vm.prank(peer0); uint256 g0 = gasleft(); @@ -553,7 +553,7 @@ contract GasReport is LedgerTestBase { bytes memory s1 = _buildSignedSimplex(ch, peer1, 1, 0); bytes memory array = _wrapStateArray(s0, s1); - vm.roll(block.number + 10); + vm.warp(block.timestamp + 10); vm.prank(peer0); uint256 g0 = gasleft(); @@ -590,7 +590,7 @@ contract GasReport is LedgerTestBase { bytes memory s0 = _buildSimplexWithPayList(ch, peer0, 1, headList, 10 + tailTotal); bytes memory s1 = _buildSignedSimplex(ch, peer1, 1, 0); - vm.roll(block.number + 10); + vm.warp(block.timestamp + 10); vm.prank(peer0); celerLedger.intendSettle(_wrapStateArray(s0, s1)); @@ -608,7 +608,7 @@ contract GasReport is LedgerTestBase { bytes memory s1 = _buildSignedSimplex(ch, peer1, 1, 0); vm.prank(peer0); celerLedger.intendSettle(_wrapStateArray(s0, s1)); - vm.roll(block.number + DISPUTE_TIMEOUT + 1); + vm.warp(block.timestamp + DISPUTE_TIMEOUT + 1); uint256 g0 = gasleft(); celerLedger.confirmSettle(ch); g = g0 - gasleft(); @@ -618,7 +618,7 @@ contract GasReport is LedgerTestBase { function _measure_cooperativeSettle() internal returns (uint256 g) { uint256 snap = vm.snapshotState(); bytes32 ch = _openFundedEthChannel([uint256(200), 0]); - bytes memory request = _buildCoopSettle(ch, 1, [uint256(120), 80], block.number + 1000); + bytes memory request = _buildCoopSettle(ch, 1, [uint256(120), 80], block.timestamp + 1000); uint256 g0 = gasleft(); celerLedger.cooperativeSettle(request); g = g0 - gasleft(); @@ -629,7 +629,7 @@ contract GasReport is LedgerTestBase { uint256 snap = vm.snapshotState(); (bytes memory openReq,, bytes32 ch) = _buildOpenErc20(address(erc20), [uint256(200), 0], openDeadlineCursor++); celerLedger.openChannel(openReq); - bytes memory request = _buildCoopSettle(ch, 1, [uint256(120), 80], block.number + 1000); + bytes memory request = _buildCoopSettle(ch, 1, [uint256(120), 80], block.timestamp + 1000); uint256 g0 = gasleft(); celerLedger.cooperativeSettle(request); g = g0 - gasleft(); @@ -704,7 +704,7 @@ contract GasReport is LedgerTestBase { bytes32 ch = _openFundedEthChannel([uint256(100), 200]); CelerLedger newLedger = _deployNewLedgerSiblingAndApprove(); bytes memory request = - _buildMigrationRequest(ch, address(celerLedger), address(newLedger), block.number + 100_000); + _buildMigrationRequest(ch, address(celerLedger), address(newLedger), block.timestamp + 100_000); uint256 g0 = gasleft(); newLedger.migrateChannelFrom(address(celerLedger), request); @@ -722,7 +722,7 @@ contract GasReport is LedgerTestBase { CelerLedger newLedger = _deployNewLedgerSiblingAndApprove(); bytes memory request = - _buildMigrationRequest(ch, address(celerLedger), address(newLedger), block.number + 100_000); + _buildMigrationRequest(ch, address(celerLedger), address(newLedger), block.timestamp + 100_000); uint256 g0 = gasleft(); newLedger.migrateChannelFrom(address(celerLedger), request); @@ -737,7 +737,7 @@ contract GasReport is LedgerTestBase { CelerLedger newLedger = _deployNewLedgerSiblingAndApprove(); bytes memory request = - _buildMigrationRequest(ch, address(celerLedger), address(newLedger), block.number + 100_000); + _buildMigrationRequest(ch, address(celerLedger), address(newLedger), block.timestamp + 100_000); uint256 g0 = gasleft(); newLedger.migrateChannelFrom(address(celerLedger), request); @@ -853,7 +853,7 @@ contract GasReport is LedgerTestBase { seqNum: _seqNum, transferAmount: 0, pendingPayIds: _payIdList, - lastPayResolveDeadline: block.number + 1000, + lastPayResolveDeadline: block.timestamp + 1000, totalPendingAmount: _totalPending }); bytes memory simplex = Fixtures.encSimplexPaymentChannel(s); diff --git a/test/PayRegistry.t.sol b/test/PayRegistry.t.sol index 6b6ecb4..9515273 100644 --- a/test/PayRegistry.t.sol +++ b/test/PayRegistry.t.sol @@ -23,6 +23,10 @@ contract PayRegistryTest is Test { event PayInfoUpdate(bytes32 indexed payId, uint256 amount, uint256 resolveDeadline); function setUp() public { + // Anchor block.timestamp far above zero so deadline math like + // `block.timestamp - 1` cannot underflow. + vm.warp(1_000_000); + registry = new PayRegistry(); } @@ -192,31 +196,30 @@ contract PayRegistryTest is Test { // ------------------------------------------------------------------------- function test_getPayAmounts_returnsResolvedAmounts_whenDeadlinePassed() public { - // Set a pay with amount 50, deadline at block N. vm.prank(setterA); - registry.setPayInfo(payHash1, 50, block.number + 5); + registry.setPayInfo(payHash1, 50, block.timestamp + 5); bytes32[] memory ids = new bytes32[](1); ids[0] = registry.calculatePayId(payHash1, setterA); // Roll past the per-pay deadline so the pay is finalized. - vm.roll(block.number + 6); + vm.warp(block.timestamp + 6); - uint256[] memory amounts = registry.getPayAmounts(ids, block.number); + uint256[] memory amounts = registry.getPayAmounts(ids, block.timestamp); assertEq(amounts.length, 1); assertEq(amounts[0], 50); } function test_getPayAmounts_revertsIfPayNotFinalized() public { vm.prank(setterA); - registry.setPayInfo(payHash1, 50, block.number + 100); + registry.setPayInfo(payHash1, 50, block.timestamp + 100); bytes32[] memory ids = new bytes32[](1); ids[0] = registry.calculatePayId(payHash1, setterA); // Per-pay deadline is in the future; should revert. vm.expectRevert(bytes("Payment is not finalized")); - registry.getPayAmounts(ids, block.number); + registry.getPayAmounts(ids, block.timestamp); } function test_getPayAmounts_unsetPay_passesIfChannelDeadlineExceeded() public { @@ -226,8 +229,8 @@ contract PayRegistryTest is Test { ids[0] = registry.calculatePayId(payHash1, setterA); // Channel-level lastPayResolveDeadline is in the past → ok to read. - vm.roll(block.number + 10); - uint256[] memory amounts = registry.getPayAmounts(ids, block.number - 1); + vm.warp(block.timestamp + 10); + uint256[] memory amounts = registry.getPayAmounts(ids, block.timestamp - 1); assertEq(amounts[0], 0); } @@ -237,7 +240,7 @@ contract PayRegistryTest is Test { // Channel-level lastPayResolveDeadline is in the future → revert. vm.expectRevert(bytes("Payment is not finalized")); - registry.getPayAmounts(ids, block.number + 100); + registry.getPayAmounts(ids, block.timestamp + 100); } // ------------------------------------------------------------------------- diff --git a/test/PayResolver.t.sol b/test/PayResolver.t.sol index bae0c14..8bb2453 100644 --- a/test/PayResolver.t.sol +++ b/test/PayResolver.t.sol @@ -61,6 +61,10 @@ contract PayResolverTest is Test { event ResolvePayment(bytes32 indexed payId, uint256 amount, uint256 resolveDeadline); function setUp() public { + // Anchor block.timestamp far above zero so deadline math like + // `block.timestamp - 1` cannot underflow. + vm.warp(1_000_000); + virtResolver = new VirtContractResolver(); payRegistry = new PayRegistry(); payResolver = new PayResolver(address(payRegistry), address(virtResolver)); @@ -176,7 +180,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 10, block.number); + emit ResolvePayment(expectedPayId, 10, block.timestamp); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -186,7 +190,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 0, block.number + RESOLVE_TIMEOUT); + emit ResolvePayment(expectedPayId, 0, block.timestamp + RESOLVE_TIMEOUT); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -196,7 +200,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 30, block.number); + emit ResolvePayment(expectedPayId, 30, block.timestamp); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -206,7 +210,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 0, block.number + RESOLVE_TIMEOUT); + emit ResolvePayment(expectedPayId, 0, block.timestamp + RESOLVE_TIMEOUT); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -220,7 +224,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 20, block.number + RESOLVE_TIMEOUT); + emit ResolvePayment(expectedPayId, 20, block.timestamp + RESOLVE_TIMEOUT); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); } @@ -231,7 +235,7 @@ contract PayResolverTest is Test { // First resolve at 20 (deadline = N + RESOLVE_TIMEOUT). payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); - uint256 firstDeadline = block.number + RESOLVE_TIMEOUT; + uint256 firstDeadline = block.timestamp + RESOLVE_TIMEOUT; // Second resolve at 25; deadline must be unchanged because amount < max. vm.expectEmit(true, false, false, true, address(payResolver)); @@ -245,7 +249,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); - uint256 firstDeadline = block.number + RESOLVE_TIMEOUT; + uint256 firstDeadline = block.timestamp + RESOLVE_TIMEOUT; // NUMERIC_ADD over [hashLock, num10, num25] = 35. Higher than 20 → updates, // deadline preserved (still partial vs max=100). @@ -279,17 +283,15 @@ contract PayResolverTest is Test { // ------------------------------------------------------------------------- function test_resolveByConditions_pastResolveDeadline_reverts() public { - // resolveDeadline = 1; roll past it. - bytes memory payBytes = _buildPay(5, 1, 0, 10, 1); - vm.roll(2); + // 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")); _resolveByConditions(payBytes, TRUE_PREIMAGE); } function test_resolveByVouchedResult_pastResolveDeadline_reverts() public { - bytes memory payBytes = _buildPay(6, 1, 0, 100, 1); - vm.roll(2); + bytes memory payBytes = _buildPay(6, 1, 0, 100, block.timestamp - 1); vm.expectRevert(bytes("Passed pay resolve deadline in condPay msg")); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); @@ -302,7 +304,7 @@ contract PayResolverTest is Test { payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); // Roll past the onchain resolve deadline. - vm.roll(block.number + RESOLVE_TIMEOUT + 1); + vm.warp(block.timestamp + RESOLVE_TIMEOUT + 1); vm.expectRevert(bytes("Passed onchain resolve pay deadline")); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 30)); @@ -312,7 +314,7 @@ contract PayResolverTest is Test { bytes memory payBytes = _buildPay(8, 1, 0, 200, RESOLVE_DEADLINE); payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); - vm.roll(block.number + RESOLVE_TIMEOUT + 1); + vm.warp(block.timestamp + RESOLVE_TIMEOUT + 1); vm.expectRevert(bytes("Passed onchain resolve pay deadline")); _resolveByConditions(payBytes, TRUE_PREIMAGE); @@ -330,6 +332,75 @@ contract PayResolverTest is Test { _resolveByConditionsTwo(payBytes, TRUE_PREIMAGE, FALSE_PREIMAGE); } + // ------------------------------------------------------------------------- + // Dependent-contract not-finalized failure + // ------------------------------------------------------------------------- + + /// @dev Build a ConditionalPay containing a single deployed-contract condition + /// pointing at `_addr`, with explicit `argsQueryFinalization` so the new + /// unified mocks can simulate `isFinalized = false`. Used by the + /// not-finalized revert tests below. + function _buildPaySingleDeployed( + uint256 _payTimestamp, + address _addr, + uint256 _logicType, + bytes memory _argsFinalization, + bytes memory _argsOutcome + ) internal view returns (bytes memory) { + Fixtures.Condition[] memory conds = new Fixtures.Condition[](1); + conds[0].conditionType = 1; // DEPLOYED_CONTRACT + conds[0].deployedAddress = _addr; + conds[0].argsQueryFinalization = _argsFinalization; + conds[0].argsQueryOutcome = _argsOutcome; + + Fixtures.ConditionalPay memory pay = Fixtures.ConditionalPay({ + payTimestamp: _payTimestamp, + src: payerSrc, + dest: payerDest, + conditions: conds, + logicType: _logicType, + maxAmount: 50, + resolveDeadline: RESOLVE_DEADLINE, + resolveTimeout: RESOLVE_TIMEOUT, + payResolver: address(payResolver) + }); + return Fixtures.encConditionalPay(pay); + } + + /// @dev `isFinalized` query byte that the unified mocks decode as `false`. + bytes internal constant NOT_FINALIZED_QUERY = hex"00"; + + function test_resolveByConditions_booleanAnd_dependentNotFinalized_reverts() public { + // BOOLEAN_AND with a single deployed-contract condition where + // argsQueryFinalization decodes to `false`. + bytes memory payBytes = + _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")); + payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); + } + + function test_resolveByConditions_booleanOr_dependentNotFinalized_reverts() public { + // BOOLEAN_OR with the same shape. + bytes memory payBytes = + _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")); + payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); + } + + function test_resolveByConditions_numericLogic_dependentNotFinalized_reverts() public { + // NUMERIC_ADD with a numeric condition where argsQueryFinalization decodes to `false`. + bytes memory payBytes = + _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")); + payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); + } + // ------------------------------------------------------------------------- // Numeric logic // ------------------------------------------------------------------------- @@ -339,7 +410,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 35, block.number + RESOLVE_TIMEOUT); + emit ResolvePayment(expectedPayId, 35, block.timestamp + RESOLVE_TIMEOUT); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -349,7 +420,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 25, block.number + RESOLVE_TIMEOUT); + emit ResolvePayment(expectedPayId, 25, block.timestamp + RESOLVE_TIMEOUT); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -359,7 +430,7 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 10, block.number + RESOLVE_TIMEOUT); + emit ResolvePayment(expectedPayId, 10, block.timestamp + RESOLVE_TIMEOUT); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -377,28 +448,28 @@ contract PayResolverTest is Test { bytes32 expectedPayId = _payId(payBytes); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 50, block.number); + emit ResolvePayment(expectedPayId, 50, block.timestamp); _resolveByConditions(payBytes, TRUE_PREIMAGE); } } // ------------------------------------------------------------------------- - // Updating amount = max sets onchain deadline to current block + // Updating amount = max sets onchain deadline to current timestamp // ------------------------------------------------------------------------- - function test_updatedAmountEqualsMax_setsOnchainDeadlineToCurrentBlock() public { + function test_updatedAmountEqualsMax_setsOnchainDeadlineToCurrentTimestamp() public { bytes memory payBytes = _buildPay(0, 5, 3, 35, RESOLVE_DEADLINE); // max = 35 bytes32 expectedPayId = _payId(payBytes); - // First: vouched 20 → partial, deadline = block + timeout. + // First: vouched 20 → partial, deadline = timestamp + timeout. payResolver.resolvePaymentByVouchedResult(_vouched(payBytes, 20)); // Second: resolve by conditions → 35 (= max). Deadline must collapse to - // current block. - vm.roll(block.number + 2); + // current timestamp. + vm.warp(block.timestamp + 2); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 35, block.number); + emit ResolvePayment(expectedPayId, 35, block.timestamp); _resolveByConditions(payBytes, TRUE_PREIMAGE); } @@ -436,12 +507,12 @@ contract PayResolverTest is Test { bytes[] memory preimages = new bytes[](0); vm.expectEmit(true, false, false, true, address(payResolver)); - emit ResolvePayment(expectedPayId, 50, block.number); + emit ResolvePayment(expectedPayId, 50, block.timestamp); payResolver.resolvePaymentByConditions(Fixtures.encResolvePayByConditionsRequest(payBytes, preimages)); (uint256 amount, uint256 deadline) = payRegistry.getPayInfo(expectedPayId); assertEq(amount, 50); - assertEq(deadline, block.number); + assertEq(deadline, block.timestamp); } } diff --git a/test/RouterRegistry.t.sol b/test/RouterRegistry.t.sol index 09f508f..a49ed39 100644 --- a/test/RouterRegistry.t.sol +++ b/test/RouterRegistry.t.sol @@ -20,6 +20,10 @@ contract RouterRegistryTest is Test { event RouterUpdated(IRouterRegistry.RouterOperation indexed op, address indexed routerAddress); function setUp() public { + // Anchor block.timestamp far above zero so timestamp comparisons in + // tests can't underflow. + vm.warp(1_000_000); + registry = new RouterRegistry(); } @@ -34,7 +38,7 @@ contract RouterRegistryTest is Test { vm.prank(router0); registry.registerRouter(); - assertEq(registry.routerInfo(router0), block.number); + assertEq(registry.routerInfo(router0), block.timestamp); } function test_registerRouter_revertsForAlreadyRegistered() public { @@ -73,13 +77,13 @@ contract RouterRegistryTest is Test { // refreshRouter // ------------------------------------------------------------------------- - function test_refreshRouter_updatesBlockNumber_emitsRefresh() public { + function test_refreshRouter_updatesTimestamp_emitsRefresh() public { vm.prank(router0); registry.registerRouter(); - uint256 firstBlock = block.number; + uint256 firstTime = block.timestamp; // Advance and refresh. - vm.roll(firstBlock + 50); + vm.warp(firstTime + 50); vm.expectEmit(true, true, false, false, address(registry)); emit RouterUpdated(IRouterRegistry.RouterOperation.Refresh, router0); @@ -87,7 +91,7 @@ contract RouterRegistryTest is Test { vm.prank(router0); registry.refreshRouter(); - assertEq(registry.routerInfo(router0), firstBlock + 50); + assertEq(registry.routerInfo(router0), firstTime + 50); } function test_refreshRouter_revertsForUnregistered() public { @@ -100,11 +104,11 @@ contract RouterRegistryTest is Test { // routerInfo public getter // ------------------------------------------------------------------------- - function test_routerInfo_returnsBlockNumberForRegistered() public { + function test_routerInfo_returnsTimestampForRegistered() public { vm.prank(router0); registry.registerRouter(); - assertEq(registry.routerInfo(router0), block.number); + assertEq(registry.routerInfo(router0), block.timestamp); } function test_routerInfo_returnsZeroForUnregistered() public { diff --git a/test/gas_logs/CelerLedger-ERC20.txt b/test/gas_logs/CelerLedger-ERC20.txt index 16f0bc1..188f23c 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: 324983 -openChannel() with non-zero ERC20 deposits: 488691 +openChannel() with zero deposit: 302980 +openChannel() with non-zero ERC20 deposits: 466694 deposit(): 150001 -cooperativeWithdraw(): 100936 -cooperativeSettle(): 111536 +cooperativeWithdraw(): 89177 +cooperativeSettle(): 88887 diff --git a/test/gas_logs/CelerLedger-ETH.txt b/test/gas_logs/CelerLedger-ETH.txt index 29bc007..176e09f 100644 --- a/test/gas_logs/CelerLedger-ETH.txt +++ b/test/gas_logs/CelerLedger-ETH.txt @@ -5,12 +5,12 @@ VirtContractResolver Deploy Gas: 206156 EthPool Deploy Gas: 558320 PayRegistry Deploy Gas: 565565 CelerWallet Deploy Gas: 1147513 -PayResolver Deploy Gas: 1930640 -CelerLedger Deploy Gas: 1920257 +PayResolver Deploy Gas: 1871491 +CelerLedger Deploy Gas: 1920297 ***** Function Calls Gas Used ***** -openChannel() with zero deposit: 320196 -openChannel() using EthPool and msg.value: 431156 +openChannel() with zero deposit: 299046 +openChannel() using EthPool and msg.value: 410006 setBalanceLimits(): 26122 disableBalanceLimits(): 1688 enableBalanceLimits(): 32589 @@ -20,10 +20,10 @@ depositInBatch() with 5 deposits: 382069 intendWithdraw(): 72739 vetoWithdraw(): 3995 confirmWithdraw(): 57548 -cooperativeWithdraw(): 104112 -snapshotStates() with one non-null simplex state: 92579 -intendSettle() with a null state: 108974 -intendSettle() with two 2-payment-hashList states: 324509 -clearPays() with 2 payments: 22595 +cooperativeWithdraw(): 92353 +snapshotStates() with one non-null simplex state: 77679 +intendSettle() with a null state: 87581 +intendSettle() with two 2-payment-hashList states: 279839 +clearPays() with 2 payments: 16990 confirmSettle(): 48721 -cooperativeSettle(): 115705 +cooperativeSettle(): 93056 diff --git a/test/gas_logs/CelerLedger-Migrate.txt b/test/gas_logs/CelerLedger-Migrate.txt index 5befe68..22d3f95 100644 --- a/test/gas_logs/CelerLedger-Migrate.txt +++ b/test/gas_logs/CelerLedger-Migrate.txt @@ -1,6 +1,6 @@ ********** Gas Measurement: CelerLedger Migration ********** ***** Function Calls Gas Used ***** -migrateChannelFrom() an Operable ETH channel: 312572 -migrateChannelFrom() a Settling ETH channel: 332521 -migrateChannelFrom() an Operable ERC20 channel: 312572 +migrateChannelFrom() an Operable ETH channel: 302074 +migrateChannelFrom() a Settling ETH channel: 322023 +migrateChannelFrom() an Operable ERC20 channel: 302074 diff --git a/test/gas_logs/PayResolver.txt b/test/gas_logs/PayResolver.txt index 3aa5127..90f50d0 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(): 104039 -resolvePaymentByVouchedResult(): 128510 +resolvePaymentByConditions(): 77247 +resolvePaymentByVouchedResult(): 95538 diff --git a/test/gas_logs/VirtContractResolver.txt b/test/gas_logs/VirtContractResolver.txt index 285a536..c474317 100644 --- a/test/gas_logs/VirtContractResolver.txt +++ b/test/gas_logs/VirtContractResolver.txt @@ -1,4 +1,4 @@ ********** Gas Measurement: VirtContractResolver ********** ***** Function Calls Gas Used ***** -deploy() - BooleanCondMock: 157249 +deploy() - BooleanCondMock: 171522 diff --git a/test/gas_logs/fine_granularity/ClearPays.txt b/test/gas_logs/fine_granularity/ClearPays.txt index a85cb1b..ceeeda0 100644 --- a/test/gas_logs/fine_granularity/ClearPays.txt +++ b/test/gas_logs/fine_granularity/ClearPays.txt @@ -1,9 +1,9 @@ ********** Gas Measurement of clearPays() - N pays per following list ********** pay number per following payIdList used gas -1 15203 -5 44774 -10 81743 -25 193526 -50 382734 -100 771773 +1 12196 +5 31370 +10 55339 +25 128115 +50 252267 +100 511051 diff --git a/test/gas_logs/fine_granularity/IntendSettle-OneState.txt b/test/gas_logs/fine_granularity/IntendSettle-OneState.txt index 2fab845..888c579 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 253618 -5 285770 -10 325574 -25 445424 -50 648519 -100 1065694 -200 1954934 +1 219426 +5 241280 +10 268574 +25 351962 +50 497143 +100 810884 +200 1540279 diff --git a/test/utils/Base.t.sol b/test/utils/Base.t.sol index 2f68d9e..2722a91 100644 --- a/test/utils/Base.t.sol +++ b/test/utils/Base.t.sol @@ -54,6 +54,11 @@ contract BaseTest is Test { address internal stranger; function setUp() public virtual { + // Anchor block.timestamp far above zero so deadline math like + // `block.timestamp - DISPUTE_TIMEOUT` cannot underflow in tests that + // simulate states predating the channel's open time. + vm.warp(1_000_000); + // Deploy the full contract graph in dependency order. ethPool = new EthPool(); payRegistry = new PayRegistry();