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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -313,15 +313,16 @@ 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

| Function | Purpose |
|---|---|
| [`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

Expand Down
4 changes: 2 additions & 2 deletions src/CelerLedgerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
4 changes: 2 additions & 2 deletions src/PayRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
16 changes: 8 additions & 8 deletions src/PayResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,24 @@ 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
// payment amount must be monotone increasing
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);
Expand All @@ -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");
}
Expand Down
14 changes: 7 additions & 7 deletions src/RouterRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
28 changes: 17 additions & 11 deletions src/helper/BooleanCondMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 16 additions & 11 deletions src/helper/NumericCondMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading
Loading