Skip to content

Commit

Permalink
wip: configurable scripts working
Browse files Browse the repository at this point in the history
  • Loading branch information
maschad committed Oct 4, 2024
1 parent aee1626 commit 3d6a61f
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 20 deletions.
30 changes: 14 additions & 16 deletions packages/contract/src/contract-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import { Contract } from '@fuel-ts/program';
import type { StorageSlot } from '@fuel-ts/transactions';
import { arrayify, isDefined } from '@fuel-ts/utils';

import { getLoaderInstructions, getContractChunks } from './loader';
import {
getLoaderInstructions,
getPredicateScriptLoaderInstructions,
getContractChunks,
} from './loader';
import { getContractId, getContractStorageRoot, hexlifyWithPrefix } from './util';

/** Amount of percentage override for chunk sizes in blob transactions */
Expand Down Expand Up @@ -373,22 +377,17 @@ export default class ContractFactory {
return { waitForResult, contractId, waitForTransactionId };
}

async deployAsBlobTxForScript(
deployOptions: DeployContractOptions = {
chunkSizeMultiplier: CHUNK_SIZE_MULTIPLIER,
}
) {
async deployAsBlobTxForScript(deployOptions: DeployContractOptions = {}) {
const account = this.getAccount();
const { chunkSizeMultiplier } = deployOptions;
// if (configurableConstants) {
// this.setConfigurableConstants(configurableConstants);
// }
const { configurableConstants } = deployOptions;
if (configurableConstants) {
this.setConfigurableConstants(configurableConstants);
}

// Generate the chunks based on the maximum chunk size and create blob txs
const chunkSize = this.getMaxChunkSize(deployOptions, 1);
const chunks = getContractChunks(arrayify(this.bytecode), chunkSize).map((c) => {
const transactionRequest = this.blobTransactionRequest({
// ...deployOptions,
bytecode: c.bytecode,
});
return {
Expand All @@ -402,14 +401,13 @@ export default class ContractFactory {
const blobIds = [chunks[0].blobId];
const blobId = chunks[0].blobId;
const bloTransactionRequest = chunks[0].transactionRequest;
const loaderBytecode = getLoaderInstructions(blobIds);
const loaderBytecode = getPredicateScriptLoaderInstructions(
arrayify(this.bytecode),
arrayify(blobId)
);
const transactionRequest = new ScriptTransactionRequest({
script: loaderBytecode,
});
// const { contractId, transactionRequest: createRequest } = this.createTransactionRequest({
// bytecode: loaderBytecode,
// ...deployOptions,
// });

// BlobIDs only need to be uploaded once and we can check if they exist on chain
const uniqueBlobIds = [...new Set(blobIds)];
Expand Down
1 change: 1 addition & 0 deletions packages/contract/src/loader/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './loader-script';
export * from './predicate-script-loader-instructions';
export * from './types';
export * from './utils';
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { InstructionSet } from '@fuel-ts/program';
import { concat } from '@fuel-ts/utils';
import * as asm from '@fuels/vm-asm';

const BLOB_ID_SIZE = 32;
const REG_ADDRESS_OF_DATA_AFTER_CODE = 0x10;
const REG_START_OF_LOADED_CODE = 0x11;
const REG_GENERAL_USE = 0x12;
const REG_START_OF_DATA_SECTION = 0x13;
const WORD_SIZE = 8; // size in bytes

function getDataOffset(binary: Uint8Array): number {
const buffer = binary.buffer.slice(binary.byteOffset + 8, binary.byteOffset + 16);
const dataView = new DataView(buffer);
const dataOffset = dataView.getBigUint64(0, false); // big-endian
return Number(dataOffset);
}

export function getPredicateScriptLoaderInstructions(
originalBinary: Uint8Array,
blobId: Uint8Array
): Uint8Array {
// The final code is going to have this structure:
// 1. loader instructions
// 2. blob id
// 3. length_of_data_section
// 4. the data_section (updated with configurables as needed)
const offset = getDataOffset(originalBinary);

const dataSection = originalBinary.slice(offset);

// update the dataSection here as necessary (with configurables)

const dataSectionLen = dataSection.length;

const { RegId, Instruction } = asm;

const REG_PC = RegId.pc().to_u8();
const REG_SP = RegId.sp().to_u8();
const REG_IS = RegId.is().to_u8();

const getInstructions = (numOfInstructions: number) => [
// 1. Load the blob content into memory
// Find the start of the hardcoded blob ID, which is located after the loader code ends.
asm.move_(REG_ADDRESS_OF_DATA_AFTER_CODE, REG_PC),
// hold the address of the blob ID.
asm.addi(
REG_ADDRESS_OF_DATA_AFTER_CODE,
REG_ADDRESS_OF_DATA_AFTER_CODE,
numOfInstructions * Instruction.size()
),
// The code is going to be loaded from the current value of SP onwards, save
// the location into REG_START_OF_LOADED_CODE so we can jump into it at the end.
asm.move_(REG_START_OF_LOADED_CODE, REG_SP),
// REG_GENERAL_USE to hold the size of the blob.
asm.bsiz(REG_GENERAL_USE, REG_ADDRESS_OF_DATA_AFTER_CODE),
// Push the blob contents onto the stack.
asm.ldc(REG_ADDRESS_OF_DATA_AFTER_CODE, 0, REG_GENERAL_USE, 1),
// Move on to the data section length
asm.addi(REG_ADDRESS_OF_DATA_AFTER_CODE, REG_ADDRESS_OF_DATA_AFTER_CODE, BLOB_ID_SIZE),
// load the size of the data section into REG_GENERAL_USE
asm.lw(REG_GENERAL_USE, REG_ADDRESS_OF_DATA_AFTER_CODE, 0),
// after we have read the length of the data section, we move the pointer to the actual
// data by skipping WORD_SIZE bytes.
asm.addi(REG_ADDRESS_OF_DATA_AFTER_CODE, REG_ADDRESS_OF_DATA_AFTER_CODE, WORD_SIZE),
// extend the stack
asm.cfe(REG_GENERAL_USE),
// move to the start of the newly allocated stack
asm.sub(REG_START_OF_DATA_SECTION, REG_SP, REG_GENERAL_USE),
// load the data section onto the stack
asm.mcp(REG_START_OF_DATA_SECTION, REG_ADDRESS_OF_DATA_AFTER_CODE, REG_GENERAL_USE),
// Jump into the memory where the contract is loaded.
// What follows is called _jmp_mem by the sway compiler.
// Subtract the address contained in IS because jmp will add it back.
asm.sub(REG_START_OF_LOADED_CODE, REG_START_OF_LOADED_CODE, REG_IS),
// jmp will multiply by 4, so we need to divide to cancel that out.
asm.divi(REG_START_OF_LOADED_CODE, REG_START_OF_LOADED_CODE, 4),
// Jump to the start of the contract we loaded.
asm.jmp(REG_START_OF_LOADED_CODE),
];

const numOfInstructions = getInstructions(0).length;

const instructions = getInstructions(numOfInstructions);
const instructionSet = new InstructionSet(...instructions);
const instructionBytes = instructionSet.toBytes();

const blobBytes = blobId;

// Convert dataSectionLen to big-endian bytes
const dataSectionLenBytes = new Uint8Array(8);
const dataSectionLenDataView = new DataView(dataSectionLenBytes.buffer);
dataSectionLenDataView.setBigUint64(0, BigInt(dataSectionLen), false); // false for big-endian

return concat([instructionBytes, blobBytes, dataSectionLenBytes, dataSection]);
}
36 changes: 32 additions & 4 deletions packages/fuel-gauge/src/dummy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ describe('first try', () => {

const {
wallets: [wallet],
provider,
} = launch;

const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet);
Expand All @@ -22,9 +21,38 @@ describe('first try', () => {
const actualBytes = hexlify(
(transactionResult.transaction as unknown as TransactionScript).script
);
console.log('Original Script Bytes: ', scriptBytes);
console.log('###########################################');
console.log('Actually Script Bytes: ', actualBytes);

console.log(
'transaciton result receipts for no set configurable : ',
transactionResult.receipts
);

expect(scriptBytes).not.equal(actualBytes);
});

it('Should work with configurables', async () => {
using launch = await launchTestNode();

const {
wallets: [wallet],
} = launch;

const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet);
const configurable = {
PIN: 1000,
};
const { waitForResult } = await factory.deployAsBlobTxForScript({
configurableConstants: configurable,
});

const { transactionResult } = await waitForResult();

const scriptBytes = hexlify(ScriptDummy.bytecode);
const actualBytes = hexlify(
(transactionResult.transaction as unknown as TransactionScript).script
);

console.log('transaciton result receipts for set config: ', transactionResult.receipts);

expect(scriptBytes).not.equal(actualBytes);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
script;

configurable {
PIN: u64 = 1337,
}

fn main() -> u8 {
log(PIN);
99
}

0 comments on commit 3d6a61f

Please sign in to comment.