aptos
Version:
227 lines (197 loc) • 6.49 kB
text/typescript
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0
/**
* Do fuzzing tests with test vectors. The test vectors are produced by the same code
* used by the Aptos Blockchain. The test vectors are arrays of JSON objects.
* Each JSON object contains randomized inputs to Transaction Builder and BCS and
* the expected outputs.
*/
import path from "path";
import nacl from "tweetnacl";
import fs from "fs";
import { bytesToHex } from "@noble/hashes/utils";
import {
AccountAddress,
ChainId,
RawTransaction,
EntryFunction,
StructTag,
TypeTag,
TypeTagVector,
TransactionPayloadEntryFunction,
Identifier,
TypeTagStruct,
TypeTagAddress,
TypeTagBool,
TypeTagU8,
TypeTagU64,
TypeTagU128,
TypeTagSigner,
Ed25519Signature,
TransactionPayloadScript,
Script,
TransactionArgument,
TransactionArgumentBool,
TransactionArgumentU8,
TransactionArgumentU64,
TransactionArgumentAddress,
TransactionArgumentU8Vector,
TransactionArgumentU128,
} from "../../aptos_types";
import { HexString } from "../../utils";
import { TransactionBuilderEd25519 } from "../../transaction_builder/builder";
// eslint-disable-next-line operator-linebreak
const VECTOR_FILES_ROOT_DIR =
process.env.VECTOR_FILES_ROOT_DIR || path.resolve(__dirname, "..", "..", "..", "..", "..", "..", "api", "goldens");
const ENTRY_FUNCTION_VECTOR = path.join(
VECTOR_FILES_ROOT_DIR,
"aptos_api__tests__transaction_vector_test__test_entry_function_payload.json",
);
const SCRIPT_VECTOR = path.join(
VECTOR_FILES_ROOT_DIR,
"aptos_api__tests__transaction_vector_test__test_script_payload.json",
);
function parseTypeTag(typeTag: any): TypeTag {
if (typeTag.vector) {
return new TypeTagVector(parseTypeTag(typeTag.vector));
}
if (typeTag.struct) {
const {
address,
module,
name,
// eslint-disable-next-line @typescript-eslint/naming-convention
type_args,
}: {
address: string;
module: string;
name: string;
type_args: any[];
} = typeTag.struct;
const typeArgs = type_args.map((arg) => parseTypeTag(arg));
const structTag = new StructTag(
AccountAddress.fromHex(address),
new Identifier(module),
new Identifier(name),
typeArgs,
);
return new TypeTagStruct(structTag);
}
switch (typeTag) {
case "bool":
return new TypeTagBool();
case "u8":
return new TypeTagU8();
case "u64":
return new TypeTagU64();
case "u128":
return new TypeTagU128();
case "address":
return new TypeTagAddress();
case "signer":
return new TypeTagSigner();
default:
throw new Error("Unknown type tag");
}
}
function parseTransactionArgument(arg: any): TransactionArgument {
const argHasOwnProperty = (propertyName: string) => Object.prototype.hasOwnProperty.call(arg, propertyName);
if (argHasOwnProperty("U8")) {
// arg.U8 is a number
return new TransactionArgumentU8(arg.U8);
}
if (argHasOwnProperty("U64")) {
// arg.U64 is a string literal
return new TransactionArgumentU64(BigInt(arg.U64));
}
if (argHasOwnProperty("U128")) {
// arg.U128 is a string literal
return new TransactionArgumentU128(BigInt(arg.U128));
}
if (argHasOwnProperty("Address")) {
// arg.Address is a hex string
return new TransactionArgumentAddress(AccountAddress.fromHex(arg.Address));
}
if (argHasOwnProperty("U8Vector")) {
// arg.U8Vector is a hex string
return new TransactionArgumentU8Vector(new HexString(arg.U8Vector).toUint8Array());
}
if (argHasOwnProperty("Bool")) {
return new TransactionArgumentBool(arg.Bool);
}
throw new Error("Invalid Transaction Argument");
}
function sign(rawTxn: RawTransaction, privateKey: string): string {
const privateKeyBytes = new HexString(privateKey).toUint8Array();
const signingKey = nacl.sign.keyPair.fromSeed(privateKeyBytes.slice(0, 32));
const { publicKey } = signingKey;
const txnBuilder = new TransactionBuilderEd25519(
(signingMessage) => new Ed25519Signature(nacl.sign(signingMessage, signingKey.secretKey).slice(0, 64)),
publicKey,
);
return bytesToHex(txnBuilder.sign(rawTxn));
}
type IRawTxn = {
// hex string for an AccountAddress
sender: string;
// u64 string literal
sequence_number: string;
// u64 string literal
max_gas_amount: string;
// u64 string literal
gas_unit_price: string;
// u64 string literal
expiration_timestamp_secs: string;
chain_id: number;
};
function verify(
raw_txn: IRawTxn,
payload: TransactionPayloadEntryFunction | TransactionPayloadScript,
private_key: string,
expected_output: string,
) {
const rawTxn = new RawTransaction(
AccountAddress.fromHex(raw_txn.sender),
BigInt(raw_txn.sequence_number),
payload,
BigInt(raw_txn.max_gas_amount),
BigInt(raw_txn.gas_unit_price),
BigInt(raw_txn.expiration_timestamp_secs),
new ChainId(raw_txn.chain_id),
);
const signedTxn = sign(rawTxn, private_key);
expect(signedTxn).toBe(expected_output);
}
describe("Transaction builder vector test", () => {
it("should pass on entry function payload", () => {
const vector: any[] = JSON.parse(fs.readFileSync(ENTRY_FUNCTION_VECTOR, "utf8"));
vector.forEach(({ raw_txn, signed_txn_bcs, private_key }) => {
const payload = raw_txn.payload.EntryFunction;
const entryFunctionPayload = new TransactionPayloadEntryFunction(
EntryFunction.natural(
`${payload.module.address}::${payload.module.name}`,
payload.function,
payload.ty_args.map((tag: any) => parseTypeTag(tag)),
payload.args.map((arg: any) => new HexString(arg).toUint8Array()),
),
);
verify(raw_txn, entryFunctionPayload, private_key, signed_txn_bcs);
});
});
it("should pass on script payload", () => {
const vector: any[] = JSON.parse(fs.readFileSync(SCRIPT_VECTOR, "utf8"));
vector.forEach(({ raw_txn, signed_txn_bcs, private_key }) => {
const payload = raw_txn.payload.Script;
// payload.code is hex string
const code = new HexString(payload.code).toUint8Array();
const scriptPayload = new TransactionPayloadScript(
new Script(
code,
payload.ty_args.map((tag: any) => parseTypeTag(tag)),
payload.args.map((arg: any) => parseTransactionArgument(arg)),
),
);
verify(raw_txn, scriptPayload, private_key, signed_txn_bcs);
});
});
});