Skip to content

Commit 4a7b5d3

Browse files
authored
Merge pull request #129 from PracticalParticle/work
chore: consolidate versioning approach in EngineBlox library and rela…
2 parents e8d9dcb + 97faa65 commit 4a7b5d3

8 files changed

Lines changed: 110 additions & 147 deletions

File tree

.github/workflows/sync-contract-versions.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,15 @@ jobs:
7373
DIFF_OUTPUT=$(git diff contracts/core/lib/EngineBlox.sol)
7474
7575
# Check if diff contains only version constant changes
76-
# Version constants are: VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH
77-
if echo "$DIFF_OUTPUT" | grep -qE "^[-+].*VERSION_(MAJOR|MINOR|PATCH)" && \
78-
! echo "$DIFF_OUTPUT" | grep -vE "^[-+].*VERSION_(MAJOR|MINOR|PATCH)|^@|^index|^---|^\+\+\+" | grep -qE "^[-+]"; then
79-
echo "✓ Only version constants changed, safe to commit"
76+
# Version constant is: string public constant VERSION = "..."
77+
if echo "$DIFF_OUTPUT" | grep -qE "^[-+].*constant\s+VERSION\s*=" && \
78+
! echo "$DIFF_OUTPUT" | grep -vE "^[-+].*constant\s+VERSION\s*=|^@|^index|^---|^\+\+\+" | grep -qE "^[-+]"; then
79+
echo "✓ Only version constant changed, safe to commit"
8080
echo "safe=true" >> $GITHUB_OUTPUT
8181
else
82-
echo "❌ ERROR: Changes detected in EngineBlox.sol are not limited to version constants!"
82+
echo "❌ ERROR: Changes detected in EngineBlox.sol are not limited to version constant!"
8383
echo ""
84-
echo "The workflow detected changes beyond VERSION_MAJOR, VERSION_MINOR, or VERSION_PATCH."
84+
echo "The workflow detected changes beyond the VERSION constant."
8585
echo "This could indicate uncommitted changes were accidentally included."
8686
echo ""
8787
echo "Full diff:"

abi/EngineBlox.abi.json

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -689,38 +689,12 @@
689689
},
690690
{
691691
"inputs": [],
692-
"name": "VERSION_MAJOR",
692+
"name": "VERSION",
693693
"outputs": [
694694
{
695-
"internalType": "uint8",
696-
"name": "",
697-
"type": "uint8"
698-
}
699-
],
700-
"stateMutability": "view",
701-
"type": "function"
702-
},
703-
{
704-
"inputs": [],
705-
"name": "VERSION_MINOR",
706-
"outputs": [
707-
{
708-
"internalType": "uint8",
695+
"internalType": "string",
709696
"name": "",
710-
"type": "uint8"
711-
}
712-
],
713-
"stateMutability": "view",
714-
"type": "function"
715-
},
716-
{
717-
"inputs": [],
718-
"name": "VERSION_PATCH",
719-
"outputs": [
720-
{
721-
"internalType": "uint8",
722-
"name": "",
723-
"type": "uint8"
697+
"type": "string"
724698
}
725699
],
726700
"stateMutability": "view",

contracts/core/lib/EngineBlox.sol

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ import "./interfaces/IEventForwarder.sol";
3232
library EngineBlox {
3333
// ============ VERSION INFORMATION ============
3434
bytes32 public constant PROTOCOL_NAME_HASH = keccak256("Bloxchain");
35-
uint8 public constant VERSION_MAJOR = 1;
36-
uint8 public constant VERSION_MINOR = 0;
37-
uint8 public constant VERSION_PATCH = 0;
35+
string public constant VERSION = "1.0.0";
3836

3937
// ============ SYSTEM SAFETY LIMITS ============
4038
// These constants define the safety range limits for system operations
@@ -206,10 +204,25 @@ library EngineBlox {
206204
bytes32 public constant NATIVE_TRANSFER_OPERATION = keccak256("NATIVE_TRANSFER");
207205

208206
// EIP-712 Type Hashes (selective meta-tx payload: MetaTxRecord = txId + params + payment only)
209-
// These follow the standard EIP-712 convention so that eth_signTypedData_v4 and equivalent
207+
// These follow the canonical EIP-712 convention so that eth_signTypedData_v4 and equivalent
210208
// wallet typed-data signers can reproduce the same hashes when given matching type definitions.
211-
bytes32 private constant META_TX_TYPE_HASH = keccak256("MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)");
212-
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256("MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)");
209+
//
210+
// Canonical primary type string for MetaTransaction (primary type + all referenced types,
211+
// appended in alphabetical order by type name
212+
bytes32 private constant META_TX_TYPE_HASH = keccak256(
213+
"MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)"
214+
"MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)"
215+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
216+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
217+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
218+
);
219+
220+
// Canonical primary type string for MetaTxRecord (primary type + its referenced types).
221+
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256(
222+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
223+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
224+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
225+
);
213226
bytes32 private constant TX_PARAMS_TYPE_HASH = keccak256("TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)");
214227
bytes32 private constant META_TX_PARAMS_TYPE_HASH = keccak256("MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)");
215228
bytes32 private constant PAYMENT_DETAILS_TYPE_HASH = keccak256("PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
@@ -1713,13 +1726,15 @@ library EngineBlox {
17131726
* @return The EIP-712 digest (no prefix; use standard recovery)
17141727
*/
17151728
function generateMessageHash(MetaTransaction memory metaTx) private view returns (bytes32) {
1716-
bytes32 domainSeparator = keccak256(abi.encode(
1717-
DOMAIN_SEPARATOR_TYPE_HASH,
1718-
PROTOCOL_NAME_HASH,
1719-
keccak256(abi.encodePacked(VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH)),
1720-
block.chainid,
1721-
address(this)
1722-
));
1729+
bytes32 domainSeparator = keccak256(
1730+
abi.encode(
1731+
DOMAIN_SEPARATOR_TYPE_HASH,
1732+
PROTOCOL_NAME_HASH,
1733+
keccak256(bytes(VERSION)),
1734+
block.chainid,
1735+
address(this)
1736+
)
1737+
);
17231738

17241739
TxParams memory tp = metaTx.txRecord.params;
17251740
bytes32 txParamsStructHash = keccak256(abi.encode(
@@ -1778,11 +1793,20 @@ library EngineBlox {
17781793

17791794
/**
17801795
* @dev Recovers the signer from the EIP-712 digest and signature. Uses standard EIP-712 recovery (no message prefix).
1781-
* Integrators must sign the digest returned by generateMessageHash as a raw hash—e.g.
1782-
* account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—with no
1783-
* EIP-191/personal prefix, so signatures match this ecrecover(messageHash, v, r, s) verification.
1784-
* Do NOT use personal_sign or generic eth_signTypedData_v4; the contract uses abi.encodePacked
1785-
* for the domain version and a custom META_TX_TYPE_HASH, so those produce incompatible hashes.
1796+
*
1797+
* Integrators have two equivalent options:
1798+
* - Use typed-data signing (eth_signTypedData_v4 / signTypedData) with:
1799+
* - primaryType: MetaTransaction
1800+
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
1801+
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
1802+
* In this case the wallet computes the same digest as generateMessageHash and signs it.
1803+
* - Sign the digest returned by generateMessageHash as a raw hash—e.g.
1804+
* account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—with no
1805+
* EIP-191/personal prefix.
1806+
*
1807+
* In all cases, this function applies ecrecover(messageHash, v, r, s) over the raw EIP-712 digest.
1808+
* personal_sign / EIP-191-prefixed signatures remain incompatible.
1809+
*
17861810
* @param messageHash The EIP-712 digest (keccak256("\x19\x01" || domainSeparator || structHash))
17871811
* @param signature The signature (r, s, v)
17881812
* @return The address of the signer

scripts/sync-versions.cjs

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const rootPackageJsonPath = path.join(rootDir, 'package.json');
1111
const contractsPackageJsonPath = path.join(rootDir, 'package', 'package.json');
1212
const sdkPackageJsonPath = path.join(rootDir, 'sdk', 'typescript', 'package.json');
1313
const engineBloxPath = path.join(rootDir, 'contracts', 'core', 'lib', 'EngineBlox.sol');
14+
const sdkEngineBloxPath = path.join(rootDir, 'sdk', 'typescript', 'lib', 'EngineBlox.tsx');
1415

1516
// Parse command line arguments for tag
1617
let preReleaseTag = null;
@@ -140,52 +141,20 @@ if (fs.existsSync(engineBloxPath)) {
140141
let engineBloxContent = fs.readFileSync(engineBloxPath, 'utf8');
141142
let updated = false;
142143

143-
// Update VERSION_MAJOR (uses base version, no pre-release tag)
144-
const majorRegex = /(uint8\s+public\s+constant\s+VERSION_MAJOR\s*=\s*)(\d+)/;
145-
const majorMatch = engineBloxContent.match(majorRegex);
146-
if (majorMatch) {
147-
const oldMajor = parseInt(majorMatch[2], 10);
148-
if (oldMajor !== versionMajor) {
149-
engineBloxContent = engineBloxContent.replace(majorRegex, `$1${versionMajor}`);
144+
// Update VERSION string (uses base version, no pre-release tag)
145+
const versionRegex = /(string\s+public\s+constant\s+VERSION\s*=\s*)"([^"]*)"/;
146+
const versionMatch = engineBloxContent.match(versionRegex);
147+
if (versionMatch) {
148+
const oldVersion = versionMatch[2];
149+
if (oldVersion !== baseVersion) {
150+
engineBloxContent = engineBloxContent.replace(versionRegex, `$1"${baseVersion}"`);
150151
updated = true;
151-
console.log(`✅ EngineBlox.sol VERSION_MAJOR: ${oldMajor}${versionMajor} (base version, no pre-release tag)`);
152+
console.log(`✅ EngineBlox.sol VERSION: "${oldVersion}""${baseVersion}" (base version, no pre-release tag)`);
152153
} else {
153-
console.log(`✓ EngineBlox.sol VERSION_MAJOR: ${versionMajor} (already synced)`);
154+
console.log(`✓ EngineBlox.sol VERSION: "${baseVersion}" (already synced)`);
154155
}
155156
} else {
156-
console.warn('⚠️ Could not find VERSION_MAJOR constant in EngineBlox.sol');
157-
}
158-
159-
// Update VERSION_MINOR (uses base version, no pre-release tag)
160-
const minorRegex = /(uint8\s+public\s+constant\s+VERSION_MINOR\s*=\s*)(\d+)/;
161-
const minorMatch = engineBloxContent.match(minorRegex);
162-
if (minorMatch) {
163-
const oldMinor = parseInt(minorMatch[2], 10);
164-
if (oldMinor !== versionMinor) {
165-
engineBloxContent = engineBloxContent.replace(minorRegex, `$1${versionMinor}`);
166-
updated = true;
167-
console.log(`✅ EngineBlox.sol VERSION_MINOR: ${oldMinor}${versionMinor} (base version, no pre-release tag)`);
168-
} else {
169-
console.log(`✓ EngineBlox.sol VERSION_MINOR: ${versionMinor} (already synced)`);
170-
}
171-
} else {
172-
console.warn('⚠️ Could not find VERSION_MINOR constant in EngineBlox.sol');
173-
}
174-
175-
// Update VERSION_PATCH (uses base version, no pre-release tag)
176-
const patchRegex = /(uint8\s+public\s+constant\s+VERSION_PATCH\s*=\s*)(\d+)/;
177-
const patchMatch = engineBloxContent.match(patchRegex);
178-
if (patchMatch) {
179-
const oldPatch = parseInt(patchMatch[2], 10);
180-
if (oldPatch !== versionPatch) {
181-
engineBloxContent = engineBloxContent.replace(patchRegex, `$1${versionPatch}`);
182-
updated = true;
183-
console.log(`✅ EngineBlox.sol VERSION_PATCH: ${oldPatch}${versionPatch} (base version, no pre-release tag)`);
184-
} else {
185-
console.log(`✓ EngineBlox.sol VERSION_PATCH: ${versionPatch} (already synced)`);
186-
}
187-
} else {
188-
console.warn('⚠️ Could not find VERSION_PATCH constant in EngineBlox.sol');
157+
console.warn('⚠️ Could not find VERSION constant in EngineBlox.sol');
189158
}
190159

191160
if (updated) {
@@ -199,4 +168,22 @@ if (fs.existsSync(engineBloxPath)) {
199168
console.warn('⚠️ EngineBlox.sol not found');
200169
}
201170

171+
// Update SDK EngineBlox.tsx VERSION string (same as contract, base version only)
172+
if (fs.existsSync(sdkEngineBloxPath)) {
173+
let sdkContent = fs.readFileSync(sdkEngineBloxPath, 'utf8');
174+
const sdkVersionRegex = /(static readonly VERSION: string = )"([^"]*)"/;
175+
const sdkMatch = sdkContent.match(sdkVersionRegex);
176+
if (sdkMatch && sdkMatch[2] !== baseVersion) {
177+
sdkContent = sdkContent.replace(sdkVersionRegex, `$1"${baseVersion}"`);
178+
fs.writeFileSync(sdkEngineBloxPath, sdkContent, 'utf8');
179+
console.log(`✅ SDK EngineBlox.tsx VERSION: "${sdkMatch[2]}" → "${baseVersion}"`);
180+
} else if (sdkMatch) {
181+
console.log(`✓ SDK EngineBlox.tsx VERSION: "${baseVersion}" (already synced)`);
182+
} else {
183+
console.warn('⚠️ Could not find VERSION in SDK EngineBlox.tsx');
184+
}
185+
} else {
186+
console.warn('⚠️ SDK EngineBlox.tsx not found');
187+
}
188+
202189
console.log('\n✅ Version sync complete!');

sdk/typescript/abi/EngineBlox.abi.json

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -689,38 +689,12 @@
689689
},
690690
{
691691
"inputs": [],
692-
"name": "VERSION_MAJOR",
692+
"name": "VERSION",
693693
"outputs": [
694694
{
695-
"internalType": "uint8",
696-
"name": "",
697-
"type": "uint8"
698-
}
699-
],
700-
"stateMutability": "view",
701-
"type": "function"
702-
},
703-
{
704-
"inputs": [],
705-
"name": "VERSION_MINOR",
706-
"outputs": [
707-
{
708-
"internalType": "uint8",
695+
"internalType": "string",
709696
"name": "",
710-
"type": "uint8"
711-
}
712-
],
713-
"stateMutability": "view",
714-
"type": "function"
715-
},
716-
{
717-
"inputs": [],
718-
"name": "VERSION_PATCH",
719-
"outputs": [
720-
{
721-
"internalType": "uint8",
722-
"name": "",
723-
"type": "uint8"
697+
"type": "string"
724698
}
725699
],
726700
"stateMutability": "view",

sdk/typescript/lib/EngineBlox.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const keccak256 = (str: string): Hex => {
2323
*
2424
* // Access constants
2525
* const protocolName = EngineBlox.PROTOCOL_NAME_HASH;
26-
* const version = `${EngineBlox.VERSION_MAJOR}.${EngineBlox.VERSION_MINOR}.${EngineBlox.VERSION_PATCH}`;
26+
* const version = EngineBlox.VERSION;
2727
*
2828
* // Use pure functions
2929
* const signer = EngineBlox.recoverSigner(messageHash, signature);
@@ -41,19 +41,9 @@ export class EngineBlox {
4141
static readonly PROTOCOL_NAME_HASH: Hex = keccak256("Bloxchain");
4242

4343
/**
44-
* Major version number
44+
* Protocol version string (semver, e.g. "1.0.0"). Matches EngineBlox.sol VERSION and EIP-712 domain version.
4545
*/
46-
static readonly VERSION_MAJOR: number = 1;
47-
48-
/**
49-
* Minor version number
50-
*/
51-
static readonly VERSION_MINOR: number = 0;
52-
53-
/**
54-
* Patch version number
55-
*/
56-
static readonly VERSION_PATCH: number = 0;
46+
static readonly VERSION: string = "1.0.0";
5747

5848
// ============ FUNCTION SELECTORS ============
5949

sdk/typescript/utils/metaTx/metaTransaction.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ import {
1414
} from '../../interfaces/lib.index';
1515
import { TxAction } from '../../types/lib.index';
1616
import BaseStateMachineABI from '../../abi/BaseStateMachine.abi.json';
17+
import { EngineBlox } from '../../lib/EngineBlox';
1718

1819
/** EIP-712 domain and types matching EngineBlox (selective MetaTxRecord: txId, params, payment only) */
1920
export const META_TX_DOMAIN = {
2021
name: 'Bloxchain' as const,
21-
version: '1.0.0' as const,
22+
version: EngineBlox.VERSION,
2223
chainId: 0, // set per sign
2324
verifyingContract: '0x' as Address // set per sign
2425
};

test/foundry/helpers/TestHelpers.sol

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,19 @@ library TestHelpers {
9292
contract MetaTxSigner is Test {
9393
// EIP-712 type hashes matching EngineBlox (selective MetaTxRecord: txId, params, payment only)
9494
// These must stay in sync with EngineBlox.META_TX_TYPE_HASH and EngineBlox.META_TX_RECORD_TYPE_HASH.
95-
bytes32 private constant META_TX_TYPE_HASH = keccak256("MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)");
96-
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256("MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)");
95+
bytes32 private constant META_TX_TYPE_HASH = keccak256(
96+
"MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)"
97+
"MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)"
98+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
99+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
100+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
101+
);
102+
103+
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256(
104+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
105+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
106+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
107+
);
97108
bytes32 private constant TX_PARAMS_TYPE_HASH = keccak256("TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)");
98109
bytes32 private constant META_TX_PARAMS_TYPE_HASH = keccak256("MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)");
99110
bytes32 private constant PAYMENT_DETAILS_TYPE_HASH = keccak256("PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
@@ -120,13 +131,15 @@ contract MetaTxSigner is Test {
120131
EngineBlox.MetaTransaction memory metaTx,
121132
address verifyingContract
122133
) public view returns (bytes32) {
123-
bytes32 domainSeparator = keccak256(abi.encode(
124-
DOMAIN_SEPARATOR_TYPE_HASH,
125-
PROTOCOL_NAME_HASH,
126-
keccak256(abi.encodePacked(uint8(1), ".", uint8(0), ".", uint8(0))),
127-
block.chainid,
128-
verifyingContract
129-
));
134+
bytes32 domainSeparator = keccak256(
135+
abi.encode(
136+
DOMAIN_SEPARATOR_TYPE_HASH,
137+
PROTOCOL_NAME_HASH,
138+
keccak256(bytes("1.0.0")),
139+
block.chainid,
140+
verifyingContract
141+
)
142+
);
130143

131144
EngineBlox.TxParams memory tp = metaTx.txRecord.params;
132145
bytes32 txParamsStructHash = keccak256(abi.encode(

0 commit comments

Comments
 (0)