Skip to content

Commit e11bb15

Browse files
authored
Adapt to starknet 0.11.0 (#328)
* [skip ci] - a release-commit follows
1 parent 1ebae50 commit e11bb15

32 files changed

Lines changed: 761 additions & 57 deletions

config.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2-
"CAIRO_LANG": "0.10.3",
3-
"STARKNET_DEVNET": "0.4.6"
2+
"CAIRO_LANG": "0.11.0.1",
3+
"STARKNET_DEVNET": "0.5.0a1",
4+
"CAIRO_COMPILER": "v1.0.0-alpha.6"
45
}

scripts/ensure-python.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
set -eu
66

7-
PY_VERSION=3.8.9
7+
PY_VERSION=3.9.10
88

99
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
1010
which "/opt/circleci/.pyenv/versions/$PY_VERSION/bin/python" || pyenv install "$PY_VERSION"

scripts/setup-cairo1-compiler.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
3+
set -eu
4+
5+
if [ "$TEST_SUBDIR" == "configuration-tests" ]; then
6+
CAIRO_1_COMPILER_TARGET_TAG="v1.0.0-alpha.6"
7+
8+
echo "Installing cairo compiler $CAIRO_1_COMPILER_TARGET_TAG"
9+
# need rust to install cairo-rs-py
10+
if rustc --version; then
11+
echo "rustc installed"
12+
else
13+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
14+
source ~/.cargo/env
15+
fi
16+
17+
if [ -z "${CAIRO_1_COMPILER_MANIFEST+x}" ]; then
18+
# setup cairo1 compiler
19+
mkdir cairo-compiler
20+
git clone git@github.com:starkware-libs/cairo.git cairo-compiler \
21+
--branch $CAIRO_1_COMPILER_TARGET_TAG \
22+
--single-branch
23+
export CAIRO_1_COMPILER_MANIFEST="cairo-compiler/Cargo.toml"
24+
fi
25+
26+
cargo run --bin starknet-compile \
27+
--manifest-path "$CAIRO_1_COMPILER_MANIFEST" \
28+
-- \
29+
--version
30+
fi
31+

scripts/test.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fi
3030

3131
# used by some cases
3232
../scripts/setup-venv.sh
33+
source ../scripts/setup-cairo1-compiler.sh
3334

3435
total=0
3536
success=0

scripts/update-oz-account.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ GIT_VERSION="v$VERSION"
77

88
# create a venv
99
rm -rf tmp-venv
10-
python3.8 -m venv tmp-venv
10+
python3.9 -m venv tmp-venv
1111
source tmp-venv/bin/activate
1212

1313
# create a tmp OZ repo

src/account-utils.ts

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { iterativelyCheckStatus, StarknetContract, StringMap } from "./types";
2-
import { toBN } from "starknet/utils/number";
1+
import {
2+
Cairo1ContractClass,
3+
iterativelyCheckStatus,
4+
Numeric,
5+
StarknetContract,
6+
StringMap
7+
} from "./types";
8+
import { toBN, toHex } from "starknet/utils/number";
39
import * as ellipticCurve from "starknet/utils/ellipticCurve";
410
import { ec } from "elliptic";
511
import { HardhatRuntimeEnvironment } from "hardhat/types";
@@ -10,7 +16,8 @@ import {
1016
INTERNAL_ARTIFACTS_DIR,
1117
TransactionHashPrefix,
1218
TRANSACTION_VERSION,
13-
StarknetChainId
19+
StarknetChainId,
20+
DECLARE_VERSION
1421
} from "./constants";
1522
import { numericToHexString } from "./utils";
1623
import * as crypto from "crypto";
@@ -187,6 +194,62 @@ export async function sendDeployAccountTx(
187194
});
188195
}
189196

197+
export function calculateDeclareV2TxHash(
198+
accountAddress: string,
199+
callData: string[],
200+
maxFee: string,
201+
chainId: StarknetChainId,
202+
additionalData: string[]
203+
) {
204+
const calldataHash = hash.computeHashOnElements(callData);
205+
return hash.computeHashOnElements([
206+
TransactionHashPrefix.DECLARE,
207+
numericToHexString(DECLARE_VERSION),
208+
accountAddress,
209+
0, // entrypoint selector is implied
210+
calldataHash,
211+
maxFee,
212+
chainId,
213+
...additionalData
214+
]);
215+
}
216+
217+
export async function sendDeclareV2Tx(
218+
signatures: string[],
219+
classHash: string,
220+
maxFee: Numeric,
221+
senderAddress: string,
222+
version: Numeric,
223+
nonce: Numeric,
224+
contractClass: Cairo1ContractClass
225+
) {
226+
const hre = await import("hardhat");
227+
const resp = await axios
228+
.post(`${hre.starknet.networkConfig.url}/gateway/add_transaction`, {
229+
type: "DECLARE",
230+
contract_class: contractClass.getCompiledClass(),
231+
signature: signatures,
232+
sender_address: senderAddress,
233+
compiled_class_hash: toHex(toBN(classHash)),
234+
version: numericToHexString(version),
235+
nonce: numericToHexString(nonce),
236+
max_fee: numericToHexString(maxFee)
237+
})
238+
.catch((error: AxiosError) => {
239+
const msg = `Declaring contract failed: ${error.response.data.message}`;
240+
throw new StarknetPluginError(msg, error);
241+
});
242+
243+
return new Promise<string>((resolve, reject) => {
244+
iterativelyCheckStatus(
245+
resp.data.transaction_hash,
246+
hre.starknetWrapper,
247+
() => resolve(resp.data.transaction_hash),
248+
reject
249+
);
250+
});
251+
}
252+
190253
export async function sendEstimateFeeTx(data: unknown) {
191254
const hre = await import("hardhat");
192255
// To resolve TypeError: Do not know how to serialize a BigInt

src/account.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ import {
1919
StarknetChainId,
2020
TransactionHashPrefix,
2121
TRANSACTION_VERSION,
22+
DECLARE_VERSION,
2223
UDC_DEPLOY_FUNCTION_NAME
2324
} from "./constants";
2425
import { StarknetPluginError } from "./starknet-plugin-error";
2526
import * as ellipticCurve from "starknet/utils/ellipticCurve";
2627
import { BigNumberish, toBN } from "starknet/utils/number";
2728
import { ec } from "elliptic";
2829
import {
30+
calculateDeclareV2TxHash,
2931
calculateDeployAccountHash,
3032
CallParameters,
3133
generateKeys,
3234
handleInternalContractArtifacts,
35+
sendDeclareV2Tx,
3336
sendDeployAccountTx,
3437
sendEstimateFeeTx,
3538
signMultiCall
@@ -41,7 +44,8 @@ import {
4144
UDC,
4245
readContract,
4346
bnToDecimalStringArray,
44-
estimatedFeeToMaxFee
47+
estimatedFeeToMaxFee,
48+
readCairo1Contract
4549
} from "./utils";
4650
import { Call, hash, RawCalldata } from "starknet";
4751
import { getTransactionReceiptUtil } from "./extend-utils";
@@ -403,6 +407,10 @@ export abstract class Account {
403407
contractFactory: StarknetContractFactory,
404408
options: DeclareOptions = {}
405409
): Promise<string> {
410+
if (contractFactory.isCairo1()) {
411+
return await this.declareV2(contractFactory, options);
412+
}
413+
406414
let maxFee = options?.maxFee;
407415
if (maxFee && options?.overhead) {
408416
const msg = "maxFee and overhead cannot be specified together";
@@ -442,6 +450,51 @@ export abstract class Account {
442450
maxFee: BigInt(maxFee)
443451
});
444452
}
453+
454+
private async declareV2(
455+
contractFactory: StarknetContractFactory,
456+
options: DeclareOptions = {}
457+
): Promise<string> {
458+
const maxFee = options?.maxFee;
459+
if (!maxFee) {
460+
const msg =
461+
"maxFee must be provided to send declare transactions.\n" +
462+
"A value of '0' for 'maxFee' is not supported.";
463+
throw new StarknetPluginError(msg);
464+
}
465+
466+
const version = DECLARE_VERSION;
467+
const nonce = options.nonce == null ? await this.getNonce() : options.nonce;
468+
const hre = await import("hardhat");
469+
const chainId = hre.starknet.networkConfig.starknetChainId;
470+
471+
const compiledClassHash = await hre.starknetWrapper.getCompiledClassHash(
472+
contractFactory.casmPath
473+
);
474+
const classHash = await hre.starknetWrapper.getSierraContractClassHash(
475+
contractFactory.metadataPath
476+
);
477+
478+
const calldata = [classHash];
479+
const messageHash = calculateDeclareV2TxHash(
480+
this.address,
481+
calldata,
482+
maxFee.toString(),
483+
chainId,
484+
[nonce.toString(), compiledClassHash]
485+
);
486+
487+
const signatures = this.getSignatures(messageHash);
488+
return sendDeclareV2Tx(
489+
bnToDecimalStringArray(signatures),
490+
compiledClassHash,
491+
maxFee,
492+
this.address,
493+
version,
494+
nonce,
495+
readCairo1Contract(contractFactory.metadataPath)
496+
);
497+
}
445498
}
446499

447500
/**

src/adapt.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ import { StringMap } from "./types";
55

66
const NAMED_TUPLE_DELIMITER = ": ";
77
const ARGUMENTS_DELIMITER = ", ";
8+
const COMMON_TYPES = [
9+
"felt",
10+
"core::felt252",
11+
"core::integer::u8",
12+
"core::integer::u16",
13+
"core::integer::u38",
14+
"core::integer::u64",
15+
"core::integer::u128",
16+
"core::integer::u256",
17+
"core::starknet::contract_address::ContractAddress"
18+
];
819

920
function isNumeric(value: { toString: () => string }) {
1021
if (value === undefined || value === null) {
@@ -147,7 +158,7 @@ export function adaptInputUtil(
147158
for (let i = 0; i < inputSpecs.length; ++i) {
148159
const inputSpec = inputSpecs[i];
149160
const currentValue = input[inputSpec.name];
150-
if (inputSpec.type === "felt") {
161+
if (COMMON_TYPES.includes(inputSpec.type)) {
151162
const errorMsg =
152163
`${functionName}: Expected "${inputSpec.name}" to be a felt (Numeric); ` +
153164
`got: ${typeof currentValue}`;
@@ -220,8 +231,7 @@ function adaptComplexInput(
220231
if (input === undefined || input === null) {
221232
throw new StarknetPluginError(`${inputSpec.name} is ${input}`);
222233
}
223-
224-
if (type === "felt") {
234+
if (COMMON_TYPES.includes(type)) {
225235
if (isNumeric(input)) {
226236
adaptedArray.push(toNumericString(input));
227237
return;
@@ -339,7 +349,8 @@ export function adaptOutputUtil(
339349

340350
for (const outputSpec of outputSpecs) {
341351
const currentValue = result[resultIndex];
342-
if (outputSpec.type === "felt") {
352+
if (COMMON_TYPES.includes(outputSpec.type)) {
353+
outputSpec.name = outputSpec.name ?? "response";
343354
adapted[outputSpec.name] = currentValue;
344355
resultIndex++;
345356
} else if (outputSpec.type.endsWith("*")) {
@@ -391,7 +402,7 @@ export function adaptOutputUtil(
391402
* @returns an object consisting of the next unused index and the generated tuple/struct itself
392403
*/
393404
function generateComplexOutput(raw: bigint[], rawIndex: number, type: string, abi: starknet.Abi) {
394-
if (type === "felt") {
405+
if (COMMON_TYPES.includes(type)) {
395406
return {
396407
generatedComplex: raw[rawIndex],
397408
newRawIndex: rawIndex + 1

src/cairo1-compiler.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { ProcessResult } from "@nomiclabs/hardhat-docker";
2+
import shell from "shelljs";
3+
import { Image } from "@nomiclabs/hardhat-docker";
4+
import { DockerServer } from "./external-server/docker-server";
5+
import { CommonSpawnOptions } from "child_process";
6+
7+
export const exec = (args: string) => {
8+
const result = shell.exec(args, {
9+
silent: true
10+
});
11+
12+
return {
13+
statusCode: result.code,
14+
stdout: Buffer.from(result.stderr),
15+
stderr: Buffer.from(result.stdout)
16+
} as ProcessResult;
17+
};
18+
19+
export class DockerCairo1Compiler extends DockerServer {
20+
constructor(
21+
image: Image,
22+
private sources: string[],
23+
private cairo1CompilerArgs?: string[],
24+
stdout?: string,
25+
stderr?: string
26+
) {
27+
super(
28+
image,
29+
"127.0.0.1",
30+
null,
31+
"",
32+
"starknet-docker-cairo1-compiler",
33+
cairo1CompilerArgs,
34+
stdout,
35+
stderr
36+
);
37+
}
38+
39+
protected async getDockerArgs(): Promise<string[]> {
40+
const volumes = [];
41+
for (const source of this.sources) {
42+
volumes.push("-v", `${source}:${source}`);
43+
}
44+
45+
const dockerArgs = [...volumes];
46+
return dockerArgs;
47+
}
48+
49+
protected async getContainerArgs(): Promise<string[]> {
50+
return ["/bin/sh", "-c", `"${this.cairo1CompilerArgs.join(" ")}"`];
51+
}
52+
53+
async compileCairo1(options?: CommonSpawnOptions): Promise<ProcessResult> {
54+
const res = await this.spawnChildProcess(options);
55+
const stdout: string[] = [];
56+
const stderr: string[] = [];
57+
let statusCode;
58+
59+
res.stdout.on("data", (chunk) => {
60+
stdout.push(chunk);
61+
console.log(chunk.toString());
62+
});
63+
64+
res.stderr.on("data", (chunk) => {
65+
stderr.push(chunk);
66+
console.log(chunk.toString());
67+
});
68+
69+
await new Promise((resolve, reject) => {
70+
res.on("close", (code) => {
71+
statusCode = code;
72+
resolve(code);
73+
});
74+
75+
res.on("error", (error) => {
76+
reject(error);
77+
});
78+
});
79+
80+
return {
81+
statusCode,
82+
stdout: Buffer.from(stdout.toString()),
83+
stderr: Buffer.from(stderr.toString())
84+
} as ProcessResult;
85+
}
86+
}

0 commit comments

Comments
 (0)