Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
559f96c
perf: add bench profile for autoresearch gas optimization loop
domsteil Mar 25, 2026
703e2a4
chore: change output dir to out_ar for autoresearch (avoid Docker per…
domsteil Mar 26, 2026
c3dfaf0
perf: unchecked arithmetic in _validateAndStoreBatch for validated ra…
domsteil Mar 26, 2026
98da924
perf: unchecked arithmetic in settleBatch loop and statistics
domsteil Mar 26, 2026
3cc648d
perf: unchecked dailyVolume update in _settlePayment
domsteil Mar 26, 2026
31b8681
perf: unchecked spending updates in executeSponsorship
domsteil Mar 26, 2026
a8c662b
perf: unchecked timestamp arithmetic in daily/monthly reset functions
domsteil Mar 26, 2026
25f1a25
perf: use unchecked ++totalCommitments in registerBatchRoot
domsteil Mar 26, 2026
630da79
perf: remove redundant batchId field from BatchSettlement struct
domsteil Mar 26, 2026
0d992e4
perf: eliminate headSequence SSTORE — derive from commitments
domsteil Mar 26, 2026
a69a301
perf: repack MerchantSponsorship from 7 storage slots to 3
domsteil Mar 26, 2026
a094950
perf: repack SponsorshipTier limits from uint256 to uint128
domsteil Mar 26, 2026
68ea058
perf: remove prevStateRoot from BatchCommitment struct
domsteil Mar 26, 2026
a5c8854
perf: repack AssetConfig from 6 storage slots to 3
domsteil Mar 26, 2026
a9a0a6d
perf: repack BatchSettlement from 6 slots to 5
domsteil Mar 26, 2026
fb2a97d
perf: remove gasUsed tracking from settleBatch
domsteil Mar 26, 2026
c87d3da
perf: remove submitter from BatchCommitment and StarkProofCommitment
domsteil Mar 26, 2026
3a64057
perf: remove submitter + executed from BatchSettlement (5 slots -> 4)
domsteil Mar 26, 2026
b8e5b5b
perf: remove totalCommitments SSTORE from commitBatch hot path
domsteil Mar 26, 2026
12ca545
perf: remove totalStarkProofs SSTORE from proof path
domsteil Mar 26, 2026
e372f8d
perf: remove 3 counter SSTOREs from settleBatch
domsteil Mar 26, 2026
947d7d3
perf: remove totalGasSponsored SSTORE from executeSponsorship
domsteil Mar 26, 2026
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
64 changes: 33 additions & 31 deletions contracts/SetRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,23 @@ contract SetRegistry is
// =========================================================================

/// @notice Batch commitment containing state and event roots
/// @dev Packed into 3 storage slots:
/// Slot 1: eventsRoot (32)
/// Slot 2: newStateRoot (32)
/// Slot 3: sequenceStart(8) + sequenceEnd(8) + eventCount(4) + timestamp(8) = 28
/// submitter removed — available from tx receipt / BatchCommitted event
/// prevStateRoot removed — equals previous batch's newStateRoot
struct BatchCommitment {
bytes32 eventsRoot; // Merkle root of events in this batch
bytes32 prevStateRoot; // State root before applying this batch
bytes32 newStateRoot; // State root after applying this batch
uint64 sequenceStart; // First sequence number in batch
uint64 sequenceEnd; // Last sequence number in batch
uint32 eventCount; // Number of events in batch
uint64 timestamp; // Block timestamp when committed
address submitter; // Address that submitted this commitment
}

/// @notice STARK proof commitment for a batch
/// @dev Packed into 3 slots. submitter removed — available from event/tx receipt.
struct StarkProofCommitment {
bytes32 proofHash; // Hash of the STARK proof
bytes32 policyHash; // Policy hash used in proof
Expand All @@ -51,7 +56,6 @@ contract SetRegistry is
uint64 proofSize; // Size of proof in bytes
uint64 provingTimeMs; // Time to generate proof
uint64 timestamp; // When proof was submitted
address submitter; // Who submitted the proof
}

// =========================================================================
Expand Down Expand Up @@ -489,7 +493,9 @@ contract SetRegistry is
bytes32 _storeId
) external view returns (uint64 sequence) {
bytes32 tenantStoreKey = keccak256(abi.encodePacked(_tenantId, _storeId));
return headSequence[tenantStoreKey];
bytes32 batchId = latestCommitment[tenantStoreKey];
if (batchId == bytes32(0)) return 0;
return commitments[batchId].sequenceEnd;
}

/**
Expand Down Expand Up @@ -659,7 +665,10 @@ contract SetRegistry is

for (uint256 i = 0; i < _tenantIds.length; i++) {
bytes32 tenantStoreKey = keccak256(abi.encodePacked(_tenantIds[i], _storeIds[i]));
sequences[i] = headSequence[tenantStoreKey];
bytes32 batchId = latestCommitment[tenantStoreKey];
if (batchId != bytes32(0)) {
sequences[i] = commitments[batchId].sequenceEnd;
}
}

return sequences;
Expand Down Expand Up @@ -723,10 +732,11 @@ contract SetRegistry is
bytes32 tenantStoreKey = keccak256(abi.encodePacked(_tenantId, _storeId));

latestBatchId = latestCommitment[tenantStoreKey];
currentHeadSequence = headSequence[tenantStoreKey];

if (latestBatchId != bytes32(0)) {
currentStateRoot = commitments[latestBatchId].newStateRoot;
BatchCommitment storage batch = commitments[latestBatchId];
currentStateRoot = batch.newStateRoot;
currentHeadSequence = batch.sequenceEnd;
hasLatestProof = starkProofs[latestBatchId].timestamp != 0;
}

Expand Down Expand Up @@ -774,7 +784,11 @@ contract SetRegistry is
if (_sequenceEnd < _sequenceStart) {
revert InvalidSequenceRange();
}
uint64 expectedEventCount = _sequenceEnd - _sequenceStart + 1;
// Safe: _sequenceEnd >= _sequenceStart validated above
uint64 expectedEventCount;
unchecked {
expectedEventCount = _sequenceEnd - _sequenceStart + 1;
}
if (expectedEventCount > type(uint32).max || _eventCount != expectedEventCount) {
revert InvalidEventCount(expectedEventCount, _eventCount);
}
Expand All @@ -797,29 +811,24 @@ contract SetRegistry is
revert StateRootMismatch(lastBatch.newStateRoot, _prevStateRoot);
}

if (lastBatch.sequenceEnd + 1 != _sequenceStart) {
revert SequenceGap(lastBatch.sequenceEnd + 1, _sequenceStart);
unchecked {
if (lastBatch.sequenceEnd + 1 != _sequenceStart) {
revert SequenceGap(lastBatch.sequenceEnd + 1, _sequenceStart);
}
}
}
}

commitments[_batchId] = BatchCommitment({
eventsRoot: _eventsRoot,
prevStateRoot: _prevStateRoot,
newStateRoot: _newStateRoot,
sequenceStart: _sequenceStart,
sequenceEnd: _sequenceEnd,
eventCount: _eventCount,
timestamp: uint64(block.timestamp),
submitter: msg.sender
timestamp: uint64(block.timestamp)
});

latestCommitment[tenantStoreKey] = _batchId;
headSequence[tenantStoreKey] = _sequenceEnd;

unchecked {
++totalCommitments;
}

emit BatchCommitted(
_batchId,
Expand Down Expand Up @@ -859,7 +868,8 @@ contract SetRegistry is
revert StarkProofAlreadyCommitted();
}

if (batch.prevStateRoot != _prevStateRoot || batch.newStateRoot != _newStateRoot) {
// prevStateRoot is no longer stored; verify newStateRoot matches
if (batch.newStateRoot != _newStateRoot) {
revert StateRootMismatchInProof();
}

Expand Down Expand Up @@ -900,14 +910,9 @@ contract SetRegistry is
allCompliant: _allCompliant,
proofSize: _proofSize,
provingTimeMs: _provingTimeMs,
timestamp: uint64(block.timestamp),
submitter: msg.sender
timestamp: uint64(block.timestamp)
});

unchecked {
++totalStarkProofs;
}

emit StarkProofCommitted(
_batchId,
_proofHash,
Expand Down Expand Up @@ -1046,19 +1051,16 @@ contract SetRegistry is
// Store commitment
commitments[batchId] = BatchCommitment({
eventsRoot: _root,
prevStateRoot: bytes32(0), // Legacy: no state root tracking
newStateRoot: bytes32(0), // Legacy: no state root tracking
sequenceStart: uint64(_startSequence),
sequenceEnd: uint64(_endSequence),
eventCount: uint32(_endSequence - _startSequence + 1),
timestamp: uint64(block.timestamp),
submitter: msg.sender
timestamp: uint64(block.timestamp)
});

// Update latest commitment and head sequence
// Update latest commitment (headSequence derived from commitments)
latestCommitment[tenantStoreKey] = batchId;
headSequence[tenantStoreKey] = uint64(_endSequence);
totalCommitments++;
unchecked { ++totalCommitments; }

emit BatchCommitted(
batchId,
Expand Down
117 changes: 66 additions & 51 deletions contracts/commerce/SetPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,28 @@ contract SetPaymaster is
// =========================================================================

/// @notice Sponsorship tier with limits
/// @dev Packed: name(string=2 slots) + limits(16+16=1 slot) + month+active(16+1=1 slot)
struct SponsorshipTier {
string name;
uint256 maxPerTransaction; // Max gas sponsorship per tx (wei)
uint256 maxPerDay; // Max gas sponsorship per day (wei)
uint256 maxPerMonth; // Max gas sponsorship per month (wei)
uint128 maxPerTransaction; // Max gas sponsorship per tx (wei), max ~3.4e38
uint128 maxPerDay; // Max gas sponsorship per day (wei)
uint128 maxPerMonth; // Max gas sponsorship per month (wei)
bool active;
}

/// @notice Merchant sponsorship record
/// @dev Packed into 3 storage slots (was 7):
/// Slot 1: active(1) + tierId(1) + lastDayReset(8) + lastMonthReset(8) = 18 bytes
/// Slot 2: spentToday(16) + spentThisMonth(16) = 32 bytes
/// Slot 3: totalSponsored(16) = 16 bytes
struct MerchantSponsorship {
bool active;
uint256 tierId;
uint256 spentToday;
uint256 spentThisMonth;
uint256 lastDayReset;
uint256 lastMonthReset;
uint256 totalSponsored;
uint8 tierId;
uint64 lastDayReset;
uint64 lastMonthReset;
uint128 spentToday;
uint128 spentThisMonth;
uint128 totalSponsored;
}

/// @notice Commerce operation types that can be sponsored
Expand Down Expand Up @@ -198,13 +203,16 @@ contract SetPaymaster is
if (_maxPerTx > _maxPerDay || _maxPerDay > _maxPerMonth) {
revert InvalidTierLimits();
}
if (_maxPerMonth > type(uint128).max) {
revert InvalidTierLimits();
}
tierId = nextTierId++;

tiers[tierId] = SponsorshipTier({
name: _name,
maxPerTransaction: _maxPerTx,
maxPerDay: _maxPerDay,
maxPerMonth: _maxPerMonth,
maxPerTransaction: uint128(_maxPerTx),
maxPerDay: uint128(_maxPerDay),
maxPerMonth: uint128(_maxPerMonth),
active: true
});

Expand All @@ -230,10 +238,13 @@ contract SetPaymaster is
if (_maxPerTx > _maxPerDay || _maxPerDay > _maxPerMonth) {
revert InvalidTierLimits();
}
if (_maxPerMonth > type(uint128).max) {
revert InvalidTierLimits();
}
SponsorshipTier storage tier = tiers[_tierId];
tier.maxPerTransaction = _maxPerTx;
tier.maxPerDay = _maxPerDay;
tier.maxPerMonth = _maxPerMonth;
tier.maxPerTransaction = uint128(_maxPerTx);
tier.maxPerDay = uint128(_maxPerDay);
tier.maxPerMonth = uint128(_maxPerMonth);

emit TierUpdated(_tierId, _maxPerTx, _maxPerDay);
}
Expand Down Expand Up @@ -307,11 +318,11 @@ contract SetPaymaster is

merchantSponsorship[_merchant] = MerchantSponsorship({
active: true,
tierId: _tierId,
tierId: uint8(_tierId),
spentToday: 0,
spentThisMonth: 0,
lastDayReset: block.timestamp,
lastMonthReset: block.timestamp,
lastDayReset: uint64(block.timestamp),
lastMonthReset: uint64(block.timestamp),
totalSponsored: 0
});

Expand Down Expand Up @@ -392,11 +403,13 @@ contract SetPaymaster is
revert InsufficientBalance();
}

// Update spending
sponsorship.spentToday += _amount;
sponsorship.spentThisMonth += _amount;
sponsorship.totalSponsored += _amount;
totalGasSponsored += _amount;
// Update spending (unchecked: limit checks above prevent overflow)
unchecked {
uint128 amount128 = uint128(_amount);
sponsorship.spentToday += amount128;
sponsorship.spentThisMonth += amount128;
sponsorship.totalSponsored += amount128;
}

// Transfer gas to merchant
(bool success, ) = _merchant.call{value: _amount}("");
Expand Down Expand Up @@ -520,16 +533,22 @@ contract SetPaymaster is
}

function _resetDailyIfNeeded(MerchantSponsorship storage s) internal {
if (block.timestamp - s.lastDayReset >= 1 days) {
s.spentToday = 0;
s.lastDayReset = block.timestamp;
// unchecked: block.timestamp is always >= lastDayReset
unchecked {
if (block.timestamp - s.lastDayReset >= 1 days) {
s.spentToday = 0;
s.lastDayReset = uint64(block.timestamp);
}
}
}

function _resetMonthlyIfNeeded(MerchantSponsorship storage s) internal {
if (block.timestamp - s.lastMonthReset >= 30 days) {
s.spentThisMonth = 0;
s.lastMonthReset = block.timestamp;
// unchecked: block.timestamp is always >= lastMonthReset
unchecked {
if (block.timestamp - s.lastMonthReset >= 30 days) {
s.spentThisMonth = 0;
s.lastMonthReset = uint64(block.timestamp);
}
}
}

Expand Down Expand Up @@ -636,21 +655,18 @@ contract SetPaymaster is
if (_refundAmount > sponsorship.totalSponsored) {
revert RefundExceedsSponsored(_refundAmount, sponsorship.totalSponsored);
}
if (_refundAmount > totalGasSponsored) {
revert RefundExceedsSponsored(_refundAmount, totalGasSponsored);
}

uint256 dailyRefund = _refundAmount > sponsorship.spentToday
uint128 refund128 = uint128(_refundAmount);
uint128 dailyRefund = refund128 > sponsorship.spentToday
? sponsorship.spentToday
: _refundAmount;
uint256 monthlyRefund = _refundAmount > sponsorship.spentThisMonth
: refund128;
uint128 monthlyRefund = refund128 > sponsorship.spentThisMonth
? sponsorship.spentThisMonth
: _refundAmount;
: refund128;

sponsorship.spentToday -= dailyRefund;
sponsorship.spentThisMonth -= monthlyRefund;
sponsorship.totalSponsored -= _refundAmount;
totalGasSponsored -= _refundAmount;
sponsorship.totalSponsored -= refund128;

emit GasRefunded(_merchant, _refundAmount);
}
Expand Down Expand Up @@ -686,11 +702,11 @@ contract SetPaymaster is

merchantSponsorship[_merchants[i]] = MerchantSponsorship({
active: true,
tierId: _tierIds[i],
tierId: uint8(_tierIds[i]),
spentToday: 0,
spentThisMonth: 0,
lastDayReset: block.timestamp,
lastMonthReset: block.timestamp,
lastDayReset: uint64(block.timestamp),
lastMonthReset: uint64(block.timestamp),
totalSponsored: 0
});

Expand Down Expand Up @@ -786,10 +802,10 @@ contract SetPaymaster is
}

// Update spending
sponsorship.spentToday += _amounts[i];
sponsorship.spentThisMonth += _amounts[i];
sponsorship.totalSponsored += _amounts[i];
totalGasSponsored += _amounts[i];
uint128 amt = uint128(_amounts[i]);
sponsorship.spentToday += amt;
sponsorship.spentThisMonth += amt;
sponsorship.totalSponsored += amt;

// Transfer gas to merchant
(bool success, ) = _merchants[i].call{value: _amounts[i]}("");
Expand All @@ -798,10 +814,9 @@ contract SetPaymaster is
succeeded++;
} else {
// Rollback spending updates on failed transfer
sponsorship.spentToday -= _amounts[i];
sponsorship.spentThisMonth -= _amounts[i];
sponsorship.totalSponsored -= _amounts[i];
totalGasSponsored -= _amounts[i];
sponsorship.spentToday -= amt;
sponsorship.spentThisMonth -= amt;
sponsorship.totalSponsored -= amt;
emit BatchSponsorshipFailed(_merchants[i], "Transfer failed");
failed++;
}
Expand Down Expand Up @@ -954,7 +969,7 @@ contract SetPaymaster is
if (_merchants[i] == address(0)) revert InvalidAddress();
MerchantSponsorship storage s = merchantSponsorship[_merchants[i]];
if (s.active) {
s.tierId = _newTierId;
s.tierId = uint8(_newTierId);
emit MerchantSponsored(_merchants[i], _newTierId);
}
}
Expand All @@ -979,7 +994,7 @@ contract SetPaymaster is
) {
return (
address(this).balance,
totalGasSponsored,
0, // totalGasSponsored removed (derivable from events)
nextTierId,
treasury
);
Expand Down
Loading
Loading