Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: accept Uint8Arrays as inputs for Vecs, add docs for bytecode inputs #2018

Merged
merged 19 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/heavy-elephants-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/abi-coder": patch
---

feat: accept `Uint8Array`s as inputs for `Vec`s, add docs for bytecode inputs
28 changes: 27 additions & 1 deletion apps/docs-snippets/src/guide/types/vector.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { readFile } from 'fs/promises';
import type { Contract } from 'fuels';
import { BN, getRandomB256 } from 'fuels';
import { BN, arrayify, getRandomB256 } from 'fuels';
import { join } from 'path';

import { DocSnippetProjectsEnum } from '../../../test/fixtures/forc-projects';
import { createAndDeployContractFromProject } from '../../utils';
Expand Down Expand Up @@ -52,4 +54,28 @@ describe(__filename, () => {
expect(value.ratings).toEqual(employees[1].ratings);
expect(value.isActive).toEqual(employees[1].isActive);
});

it('should successfully execute a contract call with a bytecode input', async () => {
const bytecodeContract = await createAndDeployContractFromProject(
DocSnippetProjectsEnum.BYTECODE_INPUT
);
const bytecodePath = join(
__dirname,
'../../../test/fixtures/forc-projects/bytecode-input/out/release/bytecode-input.bin'
);

// #region vector-bytecode-input-ts
// #import { arrayify };
// #context import { readFile } from 'fs/promises';
Dhaiwat10 marked this conversation as resolved.
Show resolved Hide resolved

const bytecode = await readFile(bytecodePath);

const { value: bytecodeRoot } = await bytecodeContract.functions
.compute_bytecode_root(arrayify(bytecode))
.call();
// #endregion vector-bytecode-input-ts

expect(bytecodeRoot).toBeDefined();
expect(bytecodeRoot.length).toBe(66);
});
});
1 change: 1 addition & 0 deletions apps/docs-snippets/test/fixtures/forc-projects/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ members = [
"predicate-signing",
"script-signing",
"input-output-types",
"bytecode-input",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
name = "bytecode-input"

[dependencies]
bytecode = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.19.0" }
danielbate marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
contract;

use bytecode::*;

abi MyContract {
fn compute_bytecode_root(bytecode_input: Vec<u8>) -> b256;
}

impl MyContract for Contract {
// #region vector-bytecode-input-sway
fn compute_bytecode_root(bytecode_input: Vec<u8>) -> b256 {
let root = compute_bytecode_root(bytecode_input);
return root;
}
// #endregion vector-bytecode-input-sway
}
1 change: 1 addition & 0 deletions apps/docs-snippets/test/fixtures/forc-projects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum DocSnippetProjectsEnum {
PREDICATE_SIGNING = 'predicate-signing',
SCRIPT_SIGNING = 'script-signing',
INPUT_OUTPUT_TYPES = 'input-output-types',
BYTECODE_INPUT = 'bytecode-input',
}

export const getDocsSnippetsForcProject = (project: DocSnippetProjectsEnum) =>
Expand Down
12 changes: 12 additions & 0 deletions apps/docs/src/guide/types/vectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ The code snippet below demonstrates how to call this Sway contract method, which

<<< @/../../docs-snippets/src/guide/types/vector.test.ts#vector-4{ts:line-numbers}

## Working with Bytecode in the SDK

Some Sway functions require you to pass in bytecode to the function. The type of the bytecode parameter is usually `Vec<u8>`.

Take the `compute_bytecode_root` function from the [`bytecode` Sway library](https://github.com/FuelLabs/sway-libs/tree/master/libs/src/bytecode.sw), for example.

<<< @/../../docs-snippets/test/fixtures/forc-projects/bytecode-input/src/main.sw#vector-bytecode-input-sway{ts:line-numbers}

To pass bytecode to this function, you can make use of the `arrayify` function to convert the bytecode file contents into a `UInt8Array`, the TS compatible type for Sway's `Vec<u8>` type and pass it the function like so:

<<< @/../../docs-snippets/src/guide/types/vector.test.ts#vector-bytecode-input-ts{ts:line-numbers}

## Returning vectors

Currently, returning vectors is not supported by Sway. If you try returning a type that is or contains a Vector, you will get a compile-time error.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ describe('VecCoder', () => {
const coder = new VecCoder(new BooleanCoder(options));
await expectToThrowFuelError(
() => coder.encode('Nope' as never),
new FuelError(ErrorCode.ENCODE_ERROR, 'Expected array value.')
new FuelError(
ErrorCode.ENCODE_ERROR,
'Expected array value, or a Uint8Array. You can use arrayify to convert a value to a Uint8Array.'
)
);
});

Expand Down
14 changes: 11 additions & 3 deletions packages/abi-coder/src/encoding/coders/v0/VecCoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { bn } from '@fuel-ts/math';

import { MAX_BYTES } from '../../../utils/constants';
import type { Uint8ArrayWithDynamicData } from '../../../utils/utilities';
import { concatWithDynamicData, BASE_VECTOR_OFFSET, chunkByLength } from '../../../utils/utilities';
import {
concatWithDynamicData,
BASE_VECTOR_OFFSET,
chunkByLength,
isUint8Array,
} from '../../../utils/utilities';
import type { TypesOfCoder } from '../AbstractCoder';
import { Coder } from '../AbstractCoder';

Expand All @@ -24,8 +29,11 @@ export class VecCoder<TCoder extends Coder> extends Coder<
}

encode(value: InputValueOf<TCoder>): Uint8Array {
if (!Array.isArray(value)) {
throw new FuelError(ErrorCode.ENCODE_ERROR, `Expected array value.`);
if (!Array.isArray(value) && !isUint8Array(value)) {
throw new FuelError(
ErrorCode.ENCODE_ERROR,
`Expected array value, or a Uint8Array. You can use arrayify to convert a value to a Uint8Array.`
);
}

const parts: Uint8Array[] = [];
Expand Down
2 changes: 2 additions & 0 deletions packages/abi-coder/src/utils/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,5 @@ export const rightPadToWordSize = (encoded: Uint8Array) => {
const padding = new Uint8Array(WORD_SIZE - (encoded.length % WORD_SIZE));
return concatBytes([encoded, padding]);
};

export const isUint8Array = (value: unknown): value is Uint8Array => value instanceof Uint8Array;
69 changes: 69 additions & 0 deletions packages/fuel-gauge/src/bytecode-sway-lib.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { FUEL_NETWORK_URL, Predicate, Provider, arrayify } from 'fuels';

import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures';
import { defaultPredicateAbi } from '../test/fixtures/abi/predicate';
import { defaultPredicateBytecode } from '../test/fixtures/bytecode/predicate';

import { getSetupContract } from './utils';

/**
* @group node
*/
test('compute_bytecode_root', async () => {
const { binHexlified: bytecodeFromFile } = getFuelGaugeForcProject(
FuelGaugeProjectsEnum.CALL_TEST_CONTRACT
);

const setupContract = getSetupContract(FuelGaugeProjectsEnum.BYTECODE_SWAY_LIB);
const contract = await setupContract();

const { logs } = await contract.functions
.compute_bytecode_root(arrayify(bytecodeFromFile))
.call();

const bytecodeRoot: string = logs[0];

expect(bytecodeRoot).toBeDefined();
expect(bytecodeRoot.length).toBe(66);
});

test('verify_contract_bytecode', async () => {
const { binHexlified: bytecodeFromFile } = getFuelGaugeForcProject(
FuelGaugeProjectsEnum.BYTECODE_SWAY_LIB
);

const setupContract = getSetupContract(FuelGaugeProjectsEnum.BYTECODE_SWAY_LIB);
const contract = await setupContract();

const { value } = await contract.functions
.verify_contract_bytecode(
{
value: contract.id.toB256(),
},
arrayify(bytecodeFromFile)
)
.call();

expect(value).toBeTruthy();
});

test('compute_predicate_address', async () => {
const provider = await Provider.create(FUEL_NETWORK_URL);

const predicate = new Predicate({
bytecode: defaultPredicateBytecode,
abi: defaultPredicateAbi,
provider,
});

const address = predicate.address;

const setupContract = getSetupContract(FuelGaugeProjectsEnum.BYTECODE_SWAY_LIB);
const contract = await setupContract();

const { value } = await contract.functions
.compute_predicate_address(arrayify(defaultPredicateBytecode))
.call();

expect(value.value).toEqual(address.toB256());
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"advanced-logging",
"auth_testing_abi",
"auth_testing_contract",
"bytecode-sway-lib",
"bytes",
"call-test-contract",
"collision_in_fn_names",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
name = "bytecode-sway-lib"

[dependencies]
bytecode = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.19.0" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
contract;

use bytecode::*;

abi MyContract {
fn compute_bytecode_root(bytecode_input: Vec<u8>);

fn verify_contract_bytecode(contract_id: ContractId, bytecode: Vec<u8>) -> bool;

fn compute_predicate_address(bytecode: Vec<u8>) -> Address;
}

impl MyContract for Contract {
fn compute_bytecode_root(bytecode_input: Vec<u8>) {
let mut bytecode = bytecode_input;
let root = compute_bytecode_root(bytecode);
log(root);
}

fn verify_contract_bytecode(contract_id: ContractId, bytecode: Vec<u8>) -> bool {
verify_contract_bytecode(contract_id, bytecode);
return true;
}

fn compute_predicate_address(bytecode: Vec<u8>) -> Address {
return compute_predicate_address(bytecode);
}
}
1 change: 1 addition & 0 deletions packages/fuel-gauge/test/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum FuelGaugeProjectsEnum {
AUTH_TESTING_ABI = 'auth_testing_abi',
AUTH_TESTING_CONTRACT = 'auth_testing_contract',
BYTES = 'bytes',
BYTECODE_SWAY_LIB = 'bytecode-sway-lib',
CALL_TEST_CONTRACT = 'call-test-contract',
CONFIGURABLE_CONTRACT = 'configurable-contract',
COMPLEX_SCRIPT = 'complex-script',
Expand Down