Sitelet https://github.com/Dargon789/account/commit/74f5a0c52bf9a7e8b394e5560ec26bb2306128ca
Skip to content

Commit 74f5a0c

Browse files
Multichain Mode Refactor For Easier Simulations (ithacaxyz#229)
* refactor: multichain intent mode, to allow for a cleaner simulation flow * chore: compiler warnings * chore: bump version to 0.4.3 due to bytecode changes * chore: revert automatic version bumps with old CI * chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount,Orchestrator * fix: trying to fix version bump CI * fix: trying CI again * fix: trying final fix for CI * chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount --------- Co-authored-by: GitHub Action <action@github.com>
1 parent c2a5806 commit 74f5a0c

13 files changed

Lines changed: 168 additions & 165 deletions

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎prep/check-bytecode-changes.js‎

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ function checkManualVersionBumps(contractsToBump) {
8888
try {
8989
// Check which specific contracts have already had their versions manually bumped
9090
const baseRef = process.env.GITHUB_BASE_REF || 'main';
91-
const versionRegex = /version = "(\d+\.\d+\.\d+)";/;
9291
const alreadyBumpedContracts = [];
9392

9493
// Get the diff for Solidity files
@@ -97,32 +96,64 @@ function checkManualVersionBumps(contractsToBump) {
9796
encoding: "utf8",
9897
});
9998

99+
// Split diff into file sections
100+
const fileSections = gitDiff.split(/^diff --git/m).filter(Boolean);
101+
100102
// Check each contract that needs bumping
101103
for (const contractName of contractsToBump) {
102-
// Look for version changes for this specific contract in the diff
103-
const contractPattern = new RegExp(`contract\\s+${contractName}[\\s\\S]*?version = "\\d+\\.\\d+\\.\\d+";`, 'g');
104-
const contractSection = gitDiff.match(contractPattern);
104+
let foundVersionBump = false;
105105

106-
if (contractSection) {
107-
// Check if there's a version change in this contract's section
108-
const lines = gitDiff.split('\n');
109-
let inContract = false;
110-
let foundVersionChange = false;
106+
// Find the file section that contains this contract
107+
for (const fileSection of fileSections) {
108+
// Skip if this file section doesn't contain our contract
109+
if (!fileSection.includes(`contract ${contractName}`)) {
110+
continue;
111+
}
112+
113+
// Look for version changes in this file section
114+
const lines = fileSection.split('\n');
115+
let currentContract = null;
116+
let oldVersion = null;
117+
let newVersion = null;
111118

112119
for (const line of lines) {
113-
if (line.includes(`contract ${contractName}`)) {
114-
inContract = true;
115-
}
116-
if (inContract && line.startsWith('+') && versionRegex.test(line) && !line.startsWith('+++')) {
117-
foundVersionChange = true;
118-
alreadyBumpedContracts.push(contractName);
119-
break;
120+
// Track which contract we're currently in based on context lines or added lines
121+
// Match contract declarations in various diff formats
122+
const contractMatch = line.match(/^([@\s\+\-])?.*\bcontract\s+(\w+)/);
123+
if (contractMatch && !line.startsWith('---') && !line.startsWith('+++')) {
124+
const potentialContract = contractMatch[2];
125+
// Only update currentContract if it's a contract declaration line
126+
if (line.includes('{') || line.match(/\bcontract\s+\w+\s*(is|{)/)) {
127+
currentContract = potentialContract;
128+
}
120129
}
121-
if (inContract && line.includes('contract ') && !line.includes(contractName)) {
122-
// We've moved to a different contract
123-
break;
130+
131+
// Only look for version changes if we're in the right contract
132+
if (currentContract === contractName) {
133+
// Check for removed version line
134+
const removedVersionMatch = line.match(/^-\s*version = "(\d+\.\d+\.\d+)";/);
135+
if (removedVersionMatch) {
136+
oldVersion = removedVersionMatch[1];
137+
}
138+
// Check for added version line
139+
const addedVersionMatch = line.match(/^\+\s*version = "(\d+\.\d+\.\d+)";/);
140+
if (addedVersionMatch) {
141+
newVersion = addedVersionMatch[1];
142+
}
124143
}
125144
}
145+
146+
// If we found both old and new versions and they're different, the version was bumped
147+
if (oldVersion && newVersion && oldVersion !== newVersion) {
148+
foundVersionBump = true;
149+
alreadyBumpedContracts.push(contractName);
150+
console.log(`Contract ${contractName} already manually bumped from ${oldVersion} to ${newVersion}`);
151+
break;
152+
}
153+
}
154+
155+
if (!foundVersionBump) {
156+
console.log(`Contract ${contractName} needs automatic version bump`);
126157
}
127158
}
128159

@@ -164,23 +195,19 @@ function main() {
164195
console.log(`Contracts still needing version bumps: ${contractsStillNeedingBump.join(", ")}`);
165196
console.log("Automatic bump required for remaining contracts");
166197
// Use modern GitHub Actions output syntax
167-
console.log(`::set-output name=needs_version_bump::true`);
168-
console.log(`::set-output name=contracts_to_bump::${contractsStillNeedingBump.join(",")}`);
169198
fs.appendFileSync(
170199
process.env.GITHUB_OUTPUT || "/dev/null",
171200
`needs_version_bump=true\ncontracts_to_bump=${contractsStillNeedingBump.join(",")}\n`
172201
);
173202
} else {
174203
console.log("All required contract versions have already been updated");
175-
console.log(`::set-output name=needs_version_bump::false`);
176204
fs.appendFileSync(
177205
process.env.GITHUB_OUTPUT || "/dev/null",
178206
"needs_version_bump=false\n"
179207
);
180208
}
181209
} else {
182210
console.log("No bytecode changes detected");
183-
console.log(`::set-output name=needs_version_bump::false`);
184211
fs.appendFileSync(
185212
process.env.GITHUB_OUTPUT || "/dev/null",
186213
"needs_version_bump=false\n"

‎prep/update-version.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ async function main() {
2323
const src = readSync(srcPath);
2424
if (src.indexOf('_domainNameAndVersion()') === -1) return;
2525

26-
// Extract contract name from the file
27-
const contractNameMatch = src.match(/contract\s+(\w+)/);
26+
// Extract contract name from the file - match actual contract declarations
27+
// This regex ensures we're matching actual contract definitions, not just any "contract" word
28+
const contractNameMatch = src.match(/^\s*(?:abstract\s+)?contract\s+(\w+)/m);
2829
if (!contractNameMatch) return;
2930

3031
const contractName = contractNameMatch[1];

‎src/IthacaAccount.sol‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,6 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
726726
returns (string memory name, string memory version)
727727
{
728728
name = "IthacaAccount";
729-
version = "0.4.2";
729+
version = "0.4.3";
730730
}
731731
}

‎src/Orchestrator.sol‎

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,6 @@ contract Orchestrator is
5252
using EfficientHashLib for bytes32[];
5353
using LibBitmap for LibBitmap.Bitmap;
5454

55-
enum Flags {
56-
NORMAL_MODE,
57-
SIMULATION_MODE,
58-
MULTICHAIN_INTENT_MODE
59-
}
60-
6155
////////////////////////////////////////////////////////////////////////
6256
// Errors
6357
////////////////////////////////////////////////////////////////////////
@@ -154,6 +148,12 @@ contract Orchestrator is
154148
/// Should be enough for a cold zero to non-zero SSTORE + a warm SSTORE + a few SLOADs.
155149
uint256 internal constant _REFUND_GAS = 50000;
156150

151+
/// @dev Flag for normal execution mode.
152+
uint256 internal constant _NORMAL_MODE_FLAG = 0;
153+
154+
/// @dev Flag for simulation mode.
155+
uint256 internal constant _SIMULATION_MODE_FLAG = 1;
156+
157157
////////////////////////////////////////////////////////////////////////
158158
// Constructor
159159
////////////////////////////////////////////////////////////////////////
@@ -176,24 +176,20 @@ contract Orchestrator is
176176
/// `encodedIntent` is given by `abi.encode(intent)`, where `intent` is a struct of type `Intent`.
177177
/// If sufficient gas is provided, returns an error selector that is non-zero
178178
/// if there is an error during the payment, verification, and call execution.
179-
function execute(bool isMultichain, bytes calldata encodedIntent)
179+
function execute(bytes calldata encodedIntent)
180180
public
181181
payable
182182
virtual
183183
nonReentrant
184184
returns (bytes4 err)
185185
{
186-
(, err) = _execute(
187-
encodedIntent,
188-
0,
189-
uint256(isMultichain ? Flags.MULTICHAIN_INTENT_MODE : Flags.NORMAL_MODE)
190-
);
186+
(, err) = _execute(encodedIntent, 0, _NORMAL_MODE_FLAG);
191187
}
192188

193189
/// @dev Executes the array of encoded intents.
194190
/// Each element in `encodedIntents` is given by `abi.encode(intent)`,
195191
/// where `intent` is a struct of type `Intent`.
196-
function execute(bool isMultichain, bytes[] calldata encodedIntents)
192+
function execute(bytes[] calldata encodedIntents)
197193
public
198194
payable
199195
virtual
@@ -206,11 +202,7 @@ contract Orchestrator is
206202
// We reluctantly use regular Solidity to access `encodedIntents[i]`.
207203
// This generates an unnecessary check for `i < encodedIntents.length`, but helps
208204
// generate all the implicit calldata bound checks on `encodedIntents[i]`.
209-
(, errs[i]) = _execute(
210-
encodedIntents[i],
211-
0,
212-
uint256(isMultichain ? Flags.MULTICHAIN_INTENT_MODE : Flags.NORMAL_MODE)
213-
);
205+
(, errs[i]) = _execute(encodedIntents[i], 0, _NORMAL_MODE_FLAG);
214206
}
215207
}
216208

@@ -229,7 +221,7 @@ contract Orchestrator is
229221
) external payable returns (uint256) {
230222
// If Simulation Fails, then it will revert here.
231223
(uint256 gUsed, bytes4 err) =
232-
_execute(encodedIntent, combinedGasOverride, uint256(Flags.SIMULATION_MODE));
224+
_execute(encodedIntent, combinedGasOverride, _SIMULATION_MODE_FLAG);
233225

234226
if (err != 0) {
235227
assembly ("memory-safe") {
@@ -308,7 +300,7 @@ contract Orchestrator is
308300
) {
309301
err = PaymentError.selector;
310302

311-
if (flags == uint256(Flags.SIMULATION_MODE)) {
303+
if (flags == _SIMULATION_MODE_FLAG) {
312304
revert PaymentError();
313305
}
314306
}
@@ -318,7 +310,7 @@ contract Orchestrator is
318310
// via the 63/64 rule. This is for gas estimation. If the total amount of gas
319311
// for the whole transaction is insufficient, revert.
320312
if (((gasleft() * 63) >> 6) < Math.saturatingAdd(g, _INNER_GAS_OVERHEAD)) {
321-
if (flags != uint256(Flags.SIMULATION_MODE)) {
313+
if (flags != _SIMULATION_MODE_FLAG) {
322314
revert InsufficientGas();
323315
}
324316
}
@@ -327,7 +319,7 @@ contract Orchestrator is
327319
if (i.supportedAccountImplementation != address(0)) {
328320
if (accountImplementationOf(i.eoa) != i.supportedAccountImplementation) {
329321
err = UnsupportedAccountImplementation.selector;
330-
if (flags == uint256(Flags.SIMULATION_MODE)) {
322+
if (flags == _SIMULATION_MODE_FLAG) {
331323
revert UnsupportedAccountImplementation();
332324
}
333325
}
@@ -338,14 +330,11 @@ contract Orchestrator is
338330
// Early skip the entire pay-verify-call workflow if the payer lacks tokens,
339331
// so that less gas is wasted when the Intent fails.
340332
// For multi chain mode, we skip this check, as the funding happens inside the self call.
341-
if (
342-
flags != uint256(Flags.MULTICHAIN_INTENT_MODE)
343-
&& LibBit.and(i.prePaymentAmount != 0, err == 0)
344-
) {
333+
if (!i.isMultichain && LibBit.and(i.prePaymentAmount != 0, err == 0)) {
345334
if (TokenTransferLib.balanceOf(i.paymentToken, payer) < i.prePaymentAmount) {
346335
err = PaymentError.selector;
347336

348-
if (flags == uint256(Flags.SIMULATION_MODE)) {
337+
if (flags == _SIMULATION_MODE_FLAG) {
349338
revert PaymentError();
350339
}
351340
}
@@ -373,7 +362,7 @@ contract Orchestrator is
373362

374363
if iszero(selfCallSuccess) {
375364
// If it is a simulation, we simply revert with the full error.
376-
if eq(flags, 1) {
365+
if eq(flags, _SIMULATION_MODE_FLAG) {
377366
returndatacopy(mload(0x40), 0x00, returndatasize())
378367
revert(mload(0x40), returndatasize())
379368
}
@@ -454,7 +443,7 @@ contract Orchestrator is
454443

455444
bool isValid;
456445
bytes32 keyHash;
457-
if (flags == uint256(Flags.MULTICHAIN_INTENT_MODE)) {
446+
if (i.isMultichain) {
458447
// For multi chain intents, we have to verify using merkle sigs.
459448
(isValid, keyHash) = _verifyMerkleSig(digest, eoa, i.signature);
460449

@@ -468,7 +457,7 @@ contract Orchestrator is
468457
(isValid, keyHash) = _verify(digest, eoa, i.signature);
469458
}
470459

471-
if (flags == uint256(Flags.SIMULATION_MODE)) {
460+
if (flags == _SIMULATION_MODE_FLAG) {
472461
isValid = true;
473462
}
474463

@@ -515,7 +504,7 @@ contract Orchestrator is
515504
if iszero(
516505
call(gas(), address(), 0, add(m, 0x1c), add(0x64, encodedIntentLength), m, 0x20)
517506
) {
518-
if eq(flags, 1) {
507+
if eq(flags, _SIMULATION_MODE_FLAG) {
519508
returndatacopy(mload(0x40), 0x00, returndatasize())
520509
revert(mload(0x40), returndatasize())
521510
}
@@ -556,7 +545,7 @@ contract Orchestrator is
556545
assembly ("memory-safe") {
557546
mstore(0x00, 0) // Zeroize the return slot.
558547
if iszero(call(gas(), eoa, 0, add(0x20, data), mload(data), 0x00, 0x20)) {
559-
if eq(flags, 1) {
548+
if eq(flags, _SIMULATION_MODE_FLAG) {
560549
returndatacopy(mload(0x40), 0x00, returndatasize())
561550
revert(mload(0x40), returndatasize())
562551
}
@@ -597,7 +586,7 @@ contract Orchestrator is
597586

598587
(bool isValid, bytes32 keyHash) = _verify(_computeDigest(p), eoa, p.signature);
599588

600-
if (flags == uint256(Flags.SIMULATION_MODE)) {
589+
if (flags == _SIMULATION_MODE_FLAG) {
601590
isValid = true;
602591
}
603592
if (!isValid) revert PreCallVerificationError();
@@ -616,7 +605,7 @@ contract Orchestrator is
616605
mstore(0x00, 0) // Zeroize the return slot.
617606
if iszero(call(gas(), eoa, 0, add(0x20, data), mload(data), 0x00, 0x20)) {
618607
// If this is a simulation via `simulateFailed`, bubble up the whole revert.
619-
if eq(flags, 1) {
608+
if eq(flags, _SIMULATION_MODE_FLAG) {
620609
returndatacopy(mload(0x40), 0x00, returndatasize())
621610
revert(mload(0x40), returndatasize())
622611
}
@@ -898,7 +887,7 @@ contract Orchestrator is
898887
returns (string memory name, string memory version)
899888
{
900889
name = "Orchestrator";
901-
version = "0.4.2";
890+
version = "0.4.3";
902891
}
903892

904893
////////////////////////////////////////////////////////////////////////

‎src/interfaces/ICommon.sol‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ interface ICommon {
5555
////////////////////////////////////////////////////////////////////////
5656
// Additional Fields (Not included in EIP-712)
5757
////////////////////////////////////////////////////////////////////////
58+
/// @dev Whether the intent should use the multichain mode - i.e verify with merkle sigs
59+
/// and send the cross chain message.
60+
bool isMultichain;
5861
/// @dev The funder address.
5962
address funder;
6063
/// @dev The funder signature.

‎src/interfaces/IOrchestrator.sol‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,14 @@ import {ICommon} from "../interfaces/ICommon.sol";
88
interface IOrchestrator is ICommon {
99
/// @dev Executes a single encoded intent.
1010
/// @param encodedIntent The encoded intent
11-
/// @param isMultichain Whether to execute intent in multichain mode.
1211
/// @return err The error selector (non-zero if there is an error)
1312

14-
function execute(bool isMultichain, bytes calldata encodedIntent)
15-
external
16-
payable
17-
returns (bytes4 err);
13+
function execute(bytes calldata encodedIntent) external payable returns (bytes4 err);
1814

1915
/// @dev Executes an array of encoded intents.
2016
/// @param encodedIntents Array of encoded intents
21-
/// @param isMultichain Whether to execute intents in multichain mode.
2217
/// @return errs Array of error selectors (non-zero if there are errors)
23-
function execute(bool isMultichain, bytes[] calldata encodedIntents)
18+
function execute(bytes[] calldata encodedIntents)
2419
external
2520
payable
2621
returns (bytes4[] memory errs);

0 commit comments

Comments
 (0)