Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit b46cab7

Browse files
authored
Fix reimbursment. (#59)
1 parent 3cf2f57 commit b46cab7

File tree

10 files changed

+300
-144
lines changed

10 files changed

+300
-144
lines changed

.gitignore

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,4 @@
1-
# Compiler files
21
cache/
32
out/
4-
5-
# Ignores development broadcast logs
6-
!/broadcast
7-
/broadcast/*/31337/
8-
/broadcast/**/dry-run/
9-
10-
# Docs
11-
docs/
12-
13-
# Dotenv file
14-
.env
15-
16-
# MacOS
17-
**/.DS_Store
18-
19-
# built artifacts
20-
src/artifacts/
21-
22-
23-
# Soldeer
24-
/dependencies
3+
dependencies/
4+
gas.csv

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ out = "out"
55
libs = ["dependencies"]
66
auto_detect_remappings = false
77
# Permissions
8-
fs_permissions = [{ access = "read", path = "./lib/universal-factory/abi" }]
8+
fs_permissions = [{ access = "read-write", path = "gas.csv" }]
99

1010
########
1111
# Lint #

plot_gas.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import csv
2+
import matplotlib.pyplot as plt
3+
4+
5+
def read_csv(path):
6+
data = {}
7+
with open(path, 'r') as f:
8+
r = csv.reader(f)
9+
r.__next__()
10+
for row in r:
11+
data[int(row[0])] = (int(row[1]), int(row[2]), int(row[3]))
12+
data = dict(sorted(data.items()))
13+
x, y0, y1, y2 = [], [], [], []
14+
for (k, (v0, v1, v2)) in data.items():
15+
x.append(k)
16+
y0.append(v0)
17+
y1.append(v1)
18+
y2.append(v2)
19+
return (x, y0, y1, y2)
20+
21+
22+
def lin(x, y):
23+
slope = (y[-1] - y[0]) / (x[-1] - x[0])
24+
offset = y[0] - slope * x[0]
25+
return (offset, slope)
26+
27+
28+
def plot(c0, c1, max_x):
29+
xp = [x in range(0, max_x, 100)]
30+
yp = [c1 * x + c0 for x in xp]
31+
plt.plot(xp, yp)
32+
33+
34+
msg_size, exec, reimb, base = read_csv('gas.csv')
35+
#print(msg_size, exec, reimb, base)
36+
c_exec = lin(msg_size, exec)
37+
c_reimb = lin(msg_size, reimb)
38+
c_base = lin(msg_size, base)
39+
print('measured functions')
40+
print('==================')
41+
print('exec(msg_size, gas_limit):', c_exec[0], '+', c_exec[1], '*', 'msg_size', '+', 'gas_limit')
42+
print('reimb(msg_size):', c_reimb[0], '+', c_reimb[1], '*', 'msg_size')
43+
print('base(msg_size):', c_base[0], '+', c_base[1], '*', 'msg_size')
44+
print()
45+
46+
# cost per session
47+
cb = c_base[0] + c_reimb[0]
48+
# cost per session and message byte
49+
cm = c_base[1] + c_exec[1]
50+
# cost of execution
51+
cd = c_exec[0] - c_reimb[0]
52+
53+
print('sessions size independent constants')
54+
print('===================================')
55+
print('session cost (cb)', cb)
56+
print('message byte cost (cm)', cm)
57+
print('execution cost (cd)', cd)
58+
print()
59+
60+
def coefs(s):
61+
c0 = int(s * cb + cd)
62+
c1 = int(s * cm)
63+
print('session_size', s, 'c0', c0, 'c1', c1)
64+
return (s, c0, c1)
65+
66+
# number of sessions for shard
67+
def sessions(n, t):
68+
return n - t + 1
69+
70+
print('session size dependent constants')
71+
print('================================')
72+
s1 = coefs(sessions(1, 1))
73+
s2 = coefs(sessions(3, 2))
74+
s3 = coefs(sessions(6, 4))
75+
print()
76+
77+
def msg_gas(s, msg_size):
78+
gas = s[2] * msg_size + s[1]
79+
print('gas(s=%s, x=%s) = %s' % (s[0], msg_size, gas))
80+
81+
print('gas for message size')
82+
print('====================')
83+
max_msg_size = 0x6000
84+
msg_gas(s1, 0)
85+
msg_gas(s1, max_msg_size)
86+
msg_gas(s2, 0)
87+
msg_gas(s2, max_msg_size)
88+
msg_gas(s3, 0)
89+
msg_gas(s3, max_msg_size)

src/GasUtils.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import {PrimitiveUtils} from "./Primitives.sol";
1111
library GasUtils {
1212
using PrimitiveUtils for uint256;
1313

14+
function calldataSize(uint16 messageSize) internal pure returns (uint256) {
15+
return uint256(messageSize).align32() + 708; // selector + Signature + Batch
16+
}
17+
1418
/**
1519
* @dev Compute the amount of gas used by the `GatewayProxy`.
1620
* @param calldataLen The length of the calldata in bytes

src/Gateway.sol

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
6868
*/
6969
event ShardsUnregistered(TssKey[] keys);
7070

71+
// number of signing sessions per batch
72+
mapping(uint64 => uint16) internal _signingSessions;
73+
7174
// GMP message status
7275
mapping(bytes32 => GmpStatus) public messages;
7376

@@ -247,7 +250,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
247250
/**
248251
* @dev Dispatch a single GMP message.
249252
*/
250-
function _gmpCommand(bytes calldata params) private returns (bytes32 operationHash) {
253+
function _gmpCommand(bytes calldata params, bool dry) private returns (bytes32 operationHash) {
251254
require(params.length >= 256, "invalid GmpMessage");
252255
GmpMessage calldata gmp;
253256
assembly {
@@ -266,6 +269,10 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
266269
GmpCallback memory callback = gmp.toCallback();
267270
operationHash = callback.opHash;
268271

272+
if (dry) {
273+
return operationHash;
274+
}
275+
269276
// Verify if this GMP message was already executed
270277
bytes32 msgId = callback.messageId();
271278
require(messages[msgId] == GmpStatus.NOT_FOUND, "message already executed");
@@ -325,14 +332,18 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
325332
/**
326333
* @dev Register a single shard and returns the GatewayOp hash.
327334
*/
328-
function _registerShardCommand(bytes calldata params) private returns (bytes32 operationHash) {
335+
function _registerShardCommand(bytes calldata params, bool dry) private returns (bytes32 operationHash) {
329336
require(params.length == 64, "invalid TssKey");
330337
TssKey calldata publicKey;
331338
assembly {
332339
publicKey := params.offset
333340
}
334341
operationHash = PrimitiveUtils.hash(publicKey.yParity, publicKey.xCoord);
335342

343+
if (dry) {
344+
return operationHash;
345+
}
346+
336347
bool isSuccess = ShardStore.getMainStorage().register(publicKey);
337348
if (isSuccess) {
338349
TssKey[] memory keys = new TssKey[](1);
@@ -344,14 +355,18 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
344355
/**
345356
* @dev Removes a single shard from the set.
346357
*/
347-
function _unregisterShardCommand(bytes calldata params) private returns (bytes32 operationHash) {
358+
function _unregisterShardCommand(bytes calldata params, bool dry) private returns (bytes32 operationHash) {
348359
require(params.length == 64, "invalid TssKey");
349360
TssKey calldata publicKey;
350361
assembly {
351362
publicKey := params.offset
352363
}
353364
operationHash = PrimitiveUtils.hash(publicKey.yParity, publicKey.xCoord);
354365

366+
if (dry) {
367+
return operationHash;
368+
}
369+
355370
bool isSuccess = ShardStore.getMainStorage().revoke(publicKey);
356371
if (isSuccess) {
357372
TssKey[] memory keys = new TssKey[](1);
@@ -363,7 +378,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
363378
/**
364379
* Cast the command function into a uint256.
365380
*/
366-
function fnToPtr(function(bytes calldata) internal returns (bytes32) fn) private pure returns (uint256 ptr) {
381+
function fnToPtr(function(bytes calldata, bool) internal returns (bytes32) fn) private pure returns (uint256 ptr) {
367382
assembly {
368383
ptr := fn
369384
}
@@ -393,7 +408,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
393408
function _cmdTableLookup(CommandsLookUpTable lut, Command command)
394409
private
395410
pure
396-
returns (function(bytes calldata) internal returns (bytes32) fn)
411+
returns (function(bytes calldata, bool) internal returns (bytes32) fn)
397412
{
398413
unchecked {
399414
// Extract the function pointer from the table using the `Command` as index.
@@ -421,7 +436,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
421436
* increase the cost exponentially.
422437
* @return (uint256, bytes32) Returns a tuple containing the maximum amount of memory used in bytes and the operations root hash.
423438
*/
424-
function _executeCommands(GatewayOp[] calldata operations) private returns (uint256, bytes32) {
439+
function _executeCommands(GatewayOp[] calldata operations, bool dry) private returns (uint256, bytes32) {
425440
// Track the free memory pointer, to reset the memory after each command executed.
426441
uint256 freeMemPointer = PrimitiveUtils.readAllocatedMemory();
427442
uint256 maxAllocatedMemory = freeMemPointer;
@@ -434,10 +449,11 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
434449
GatewayOp calldata operation = operations[i];
435450

436451
// Lookup the command function pointer
437-
function(bytes calldata) internal returns (bytes32) commandFN = _cmdTableLookup(lut, operation.command);
452+
function(bytes calldata, bool) internal returns (bytes32) commandFN =
453+
_cmdTableLookup(lut, operation.command);
438454

439455
// Execute the command
440-
bytes32 operationHash = commandFN(operation.params);
456+
bytes32 operationHash = commandFN(operation.params, dry);
441457

442458
// Update the operations root hash
443459
operationsRootHash =
@@ -462,12 +478,24 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
462478
function execute(Signature calldata signature, Batch calldata batch) external {
463479
uint256 initialGas = gasleft();
464480

481+
uint16 numSigningSessions = _signingSessions[batch.batchId];
482+
bool dry;
483+
if (numSigningSessions == 0) {
484+
numSigningSessions = batch.numSigningSessions;
485+
} else if (numSigningSessions == 1) {
486+
revert("batch already executed");
487+
} else if (numSigningSessions > 1) {
488+
numSigningSessions -= 1;
489+
dry = true;
490+
}
491+
_signingSessions[batch.batchId] = numSigningSessions;
492+
465493
// Execute the commands and compute the operations root hash
466-
(, bytes32 rootHash) = _executeCommands(batch.ops);
494+
(, bytes32 rootHash) = _executeCommands(batch.ops, dry);
467495
emit BatchExecuted(batch.batchId);
468496

469497
// Compute the Batch signing hash
470-
rootHash = PrimitiveUtils.hash(batch.version, batch.batchId, uint256(rootHash));
498+
rootHash = PrimitiveUtils.hash(batch.version, batch.batchId, batch.numSigningSessions, uint256(rootHash));
471499
bytes32 signingHash = keccak256(
472500
abi.encodePacked("Analog GMP v2", networkId(), bytes32(uint256(uint160(address(this)))), rootHash)
473501
);
@@ -484,7 +512,7 @@ contract Gateway is IGateway, UUPSUpgradeable, OwnableUpgradeable {
484512
// Refund the chronicle gas
485513
unchecked {
486514
// Extra gas overhead used to execute the refund logic + selector overhead
487-
uint256 gasUsed = 2964;
515+
uint256 gasUsed = 2890;
488516

489517
// Compute the gas used + base cost + proxy overhead
490518
gasUsed += GasUtils.txBaseGas();

src/Primitives.sol

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,13 @@ struct GatewayOp {
8484
* @param ops List of operations to execute.
8585
*/
8686
struct Batch {
87+
/// @dev The batch format version
8788
uint8 version;
8889
/// @dev The batch identifier
8990
uint64 batchId;
90-
/// @dev
91+
/// @dev The number of signing sessions
92+
uint16 numSigningSessions;
93+
/// @dev The ops to execute
9194
GatewayOp[] ops;
9295
}
9396

@@ -153,6 +156,12 @@ library PrimitiveUtils {
153156
*/
154157
uint256 internal constant ALLOCATED_MEMORY = 0x40;
155158

159+
/**
160+
* @dev Solidity's reserved location for the scratch memory.
161+
* Reference: https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html
162+
*/
163+
uint256 internal constant SCRATCH_MEMORY = 0x60;
164+
156165
/**
157166
* @dev Read the current allocated size (a.k.a free memory pointer).
158167
*/
@@ -207,6 +216,36 @@ library PrimitiveUtils {
207216
}
208217
}
209218

219+
/**
220+
* @dev Hashes four 256-bit words without memory allocation, uses the memory between 0x00~0x80.
221+
*
222+
* The reserverd memory region `0x40~0x80` is restored to its previous state after execution.
223+
* See https://docs.soliditylang.org/en/v0.8.28/internals/layout_in_memory.html for more details.
224+
*/
225+
function hash(uint256 a, uint256 b, uint256 c, uint256 d) internal pure returns (bytes32 h) {
226+
assembly ("memory-safe") {
227+
mstore(0x00, a)
228+
mstore(0x20, b)
229+
230+
// Backup the free memory pointer
231+
let freeMemBackup := mload(ALLOCATED_MEMORY)
232+
mstore(ALLOCATED_MEMORY, c)
233+
{
234+
// Backup the scratch space 0x60
235+
let backup := mload(0x60)
236+
237+
// Compute the hash
238+
mstore(SCRATCH_MEMORY, d)
239+
h := keccak256(0x00, 0x80)
240+
241+
// Restore the scratch space 0x60
242+
mstore(SCRATCH_MEMORY, backup)
243+
}
244+
// Restore the free memory pointer
245+
mstore(ALLOCATED_MEMORY, freeMemBackup)
246+
}
247+
}
248+
210249
/**
211250
* @dev Returns the smallest of two numbers.
212251
*/

test/Batching.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ contract Batching is Test {
112112
})
113113
)
114114
});
115-
Batch memory batch = Batch({version: 0, batchId: uint64(uint256(keccak256("some batch"))), ops: ops});
115+
Batch memory batch =
116+
Batch({version: 0, batchId: uint64(uint256(keccak256("some batch"))), numSigningSessions: 1, ops: ops});
116117

117118
Signature memory sig = TestUtils.sign(shard, gateway, batch, SIGNING_NONCE);
118119
gateway.execute(sig, batch);

0 commit comments

Comments
 (0)