Skip to content

Commit d35ccb7

Browse files
feat: bytecode ID helpers (#3641)
* feat: bytecode id helpers * chore: changeset * chore: test groups * chore: add doc --------- Co-authored-by: Sérgio Torres <[email protected]>
1 parent e5f33df commit d35ccb7

5 files changed

+111
-15
lines changed

.changeset/gorgeous-plants-watch.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@fuel-ts/account": patch
3+
---
4+
5+
feat: bytecode ID helpers

packages/account/src/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ export * from './predicate';
1111
export * from './providers';
1212
export * from './connectors';
1313
export { deployScriptOrPredicate } from './utils/deployScriptOrPredicate';
14+
export {
15+
getBytecodeId,
16+
getLegacyBlobId,
17+
getBytecodeConfigurableOffset,
18+
getBytecodeDataOffset,
19+
} from './utils/predicate-script-loader-instructions';

packages/account/src/utils/deployScriptOrPredicate.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { JsonAbi } from '@fuel-ts/abi-coder';
22
import { FuelError, ErrorCode } from '@fuel-ts/errors';
3-
import { hash } from '@fuel-ts/hasher';
43
import { bn } from '@fuel-ts/math';
54
import { arrayify } from '@fuel-ts/utils';
65

76
import type { Account } from '../account';
87
import { BlobTransactionRequest, calculateGasFee, TransactionStatus } from '../providers';
98

109
import {
11-
getConfigurableOffset,
10+
getBytecodeConfigurableOffset,
11+
getBytecodeId,
1212
getPredicateScriptLoaderInstructions,
1313
} from './predicate-script-loader-instructions';
1414

@@ -63,11 +63,10 @@ export async function deployScriptOrPredicate<T>({
6363
abi,
6464
loaderInstanceCallback,
6565
}: Deployer<T>) {
66-
const configurableOffset = getConfigurableOffset(arrayify(bytecode));
67-
const byteCodeWithoutConfigurableSection = bytecode.slice(0, configurableOffset);
66+
const blobId = getBytecodeId(arrayify(bytecode));
6867

69-
// Generate the associated create tx for the loader contract
70-
const blobId = hash(byteCodeWithoutConfigurableSection);
68+
const configurableOffset = getBytecodeConfigurableOffset(arrayify(bytecode));
69+
const byteCodeWithoutConfigurableSection = bytecode.slice(0, configurableOffset);
7170

7271
const blobTxRequest = new BlobTransactionRequest({
7372
blobId,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { concat } from '@fuel-ts/utils';
2+
3+
import {
4+
getBytecodeConfigurableOffset,
5+
getBytecodeDataOffset,
6+
getBytecodeId,
7+
getLegacyBlobId,
8+
} from './predicate-script-loader-instructions';
9+
10+
/**
11+
* @group node
12+
* @group browser
13+
*/
14+
describe('Predicate Script Loader Instructions', () => {
15+
const suffixBytes = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
16+
const dataBytes = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3]);
17+
const configurableBytes = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 5]);
18+
const bytecode = concat([suffixBytes, dataBytes, configurableBytes]);
19+
20+
it('gets the data offset', () => {
21+
const offset = getBytecodeDataOffset(bytecode);
22+
expect(offset).toBe(3);
23+
});
24+
25+
it('gets the configurable offset', () => {
26+
const offset = getBytecodeConfigurableOffset(bytecode);
27+
expect(offset).toBe(5);
28+
});
29+
30+
it('gets the bytecode id', () => {
31+
const id = getBytecodeId(bytecode);
32+
expect(id).toBe('0x8855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4');
33+
});
34+
35+
it('gets the legacy blob id', () => {
36+
const id = getLegacyBlobId(bytecode);
37+
expect(id).toBe('0x709e80c88487a2411e1ee4dfb9f22a861492d20c4765150c0c794abd70f8147c');
38+
});
39+
});

packages/account/src/utils/predicate-script-loader-instructions.ts

+56-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { BigNumberCoder } from '@fuel-ts/abi-coder';
2+
import { sha256 } from '@fuel-ts/hasher';
13
import { concat } from '@fuel-ts/utils';
24
import * as asm from '@fuels/vm-asm';
35

@@ -7,16 +9,61 @@ const REG_START_OF_LOADED_CODE = 0x11;
79
const REG_GENERAL_USE = 0x12;
810
const WORD_SIZE = 8; // size in bytes
911

10-
export function getConfigurableOffset(binary: Uint8Array): number {
11-
// Extract 8 bytes starting from index 16 (similar to binary[16..24] in Rust)
12-
const OFFSET_INDEX = 16;
13-
const dataView = new DataView(binary.buffer, OFFSET_INDEX, 8);
12+
export const DATA_OFFSET_INDEX = 8;
13+
export const CONFIGURABLE_OFFSET_INDEX = 16;
14+
15+
/**
16+
* Get the offset of the data section in the bytecode
17+
*
18+
* @param bytecode - The bytecode to get the offset from
19+
* @returns The offset of the data section
20+
*/
21+
export function getBytecodeDataOffset(bytecode: Uint8Array): number {
22+
const [offset] = new BigNumberCoder('u64').decode(bytecode, DATA_OFFSET_INDEX);
23+
return offset.toNumber();
24+
}
25+
26+
/**
27+
* Get the offset of the configurable section in the bytecode
28+
*
29+
* @param bytecode - The bytecode to get the offset from
30+
* @returns The offset of the configurable section
31+
*/
32+
export function getBytecodeConfigurableOffset(bytecode: Uint8Array): number {
33+
const [offset] = new BigNumberCoder('u64').decode(bytecode, CONFIGURABLE_OFFSET_INDEX);
34+
return offset.toNumber();
35+
}
1436

15-
// Read the value as a 64-bit big-endian unsigned integer
16-
const dataOffset = dataView.getBigUint64(0, false); // false means big-endian
37+
/**
38+
* Takes bytecode and generates it's associated bytecode ID.
39+
*
40+
* The bytecode ID is a hash of the bytecode when sliced at the configurable offset. This
41+
* superseded legacy blob IDs when uploading blobs for scripts and predicates so that
42+
* the bytecode ID is equal to the legacy blob ID. Therefore blobs can be used for ABI verification.
43+
*
44+
* @param bytecode - The bytecode to get the id from
45+
* @returns The id of the bytecode
46+
*/
47+
export function getBytecodeId(bytecode: Uint8Array): string {
48+
const configurableOffset = getBytecodeConfigurableOffset(bytecode);
49+
const byteCodeWithoutConfigurableSection = bytecode.slice(0, configurableOffset);
50+
51+
return sha256(byteCodeWithoutConfigurableSection);
52+
}
1753

18-
// Convert the BigInt to a regular number (safe as long as the offset is within Number.MAX_SAFE_INTEGER)
19-
return Number(dataOffset);
54+
/**
55+
* Takes bytecode and generates it's associated legacy blob ID.
56+
*
57+
* The legacy blob ID is a hash of the bytecode when sliced at the data section offset.
58+
*
59+
* @param bytecode - The bytecode to get the id from
60+
* @returns The id of the bytecode
61+
*/
62+
export function getLegacyBlobId(bytecode: Uint8Array): string {
63+
const dataOffset = getBytecodeDataOffset(bytecode);
64+
const byteCodeWithoutDataSection = bytecode.slice(0, dataOffset);
65+
66+
return sha256(byteCodeWithoutDataSection);
2067
}
2168

2269
export function getPredicateScriptLoaderInstructions(
@@ -100,7 +147,7 @@ export function getPredicateScriptLoaderInstructions(
100147
asm.jmp(REG_START_OF_LOADED_CODE),
101148
];
102149

103-
const offset = getConfigurableOffset(originalBinary);
150+
const offset = getBytecodeConfigurableOffset(originalBinary);
104151

105152
// if the binary length is smaller than the offset
106153
if (originalBinary.length < offset) {

0 commit comments

Comments
 (0)