.gitlab-ci.yml 17.2 KB
Newer Older
Denis_P's avatar
Denis_P committed
1
2
3
4
5
6
7
8
# .gitlab-ci.yml
#
# ink
#
# pipelines can be triggered manually in the web


stages:
Denis_P's avatar
Denis_P committed
9
10
  - check
  - workspace
Denis_P's avatar
Denis_P committed
11
  - examples
12
  - ink-waterfall
13
  - fuzz
14
  - publish
Denis_P's avatar
Denis_P committed
15
16
17

variables:
  GIT_STRATEGY:                    fetch
Denis_P's avatar
Denis_P committed
18
  GIT_DEPTH:                       100
19
  CARGO_INCREMENTAL:               0
Denis_P's avatar
Denis_P committed
20
  CARGO_TARGET_DIR:                "/ci-cache/${CI_PROJECT_NAME}/targets/${CI_COMMIT_REF_NAME}/${CI_JOB_NAME}"
Denis_P's avatar
Denis_P committed
21
  CI_IMAGE:                        "paritytech/ink-ci-linux:production"
22
23
  PURELY_STD_CRATES:               "lang/codegen metadata"
  ALSO_WASM_CRATES:                "env storage storage/derive allocator prelude primitives lang lang/macro lang/ir"
Denis_P's avatar
Denis_P committed
24
25
  # this var is changed to "-:staging" when the CI image gets rebuilt
  # read more https://github.com/paritytech/scripts/pull/244
Michael Müller's avatar
Michael Müller committed
26
  ALL_CRATES:                      "${PURELY_STD_CRATES} ${ALSO_WASM_CRATES}"
27
  DELEGATOR_SUBCONTRACTS:          "accumulator adder subber"
28
29
30
  VAULT_SERVER_URL:                "https://vault.parity-mgmt-vault.parity.io"
  VAULT_AUTH_PATH:                 "gitlab-parity-io-jwt"
  VAULT_AUTH_ROLE:                 "cicd_gitlab_parity_${CI_PROJECT_NAME}"
Denis_P's avatar
Denis_P committed
31

32
33
34
35
36
workflow:
  rules:
    - if: $CI_COMMIT_TAG
    - if: $CI_COMMIT_BRANCH

Denis_P's avatar
Denis_P committed
37
38
39
40
41
42
43
44
.collect-artifacts:                &collect-artifacts
  artifacts:
    name:                          "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
    when:                          on_success
    expire_in:                     7 days
    paths:
      - artifacts/

45
46
47
48
49
.rust-info-script:                 &rust-info-script
  - rustup show
  - cargo --version
  - rustup +nightly show
  - cargo +nightly --version
50
  - cargo spellcheck --version
51
52
53
54
  - bash --version
  - sccache -s

.test-refs:                        &test-refs
55
56
57
58
59
  rules:
    - if: $CI_PIPELINE_SOURCE == "web"
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_COMMIT_REF_NAME == "master"
    - if: $CI_COMMIT_REF_NAME == "tags"
Denis_P's avatar
Denis_P committed
60
    - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/                         # PRs
61
    - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/              # i.e. v1.0, v2.1rc1
62
63
64
65
66
67
68

.docker-env:                       &docker-env
  image:                           "${CI_IMAGE}"
  before_script:
    - *rust-info-script
    - ./scripts/.ci/pre_cache.sh
    - sccache -s
Denis_P's avatar
Denis_P committed
69
  interruptible:                   true
70
71
  retry:
    max: 2
Denis_P's avatar
Denis_P committed
72
    when:
73
74
75
      - runner_system_failure
      - unknown_failure
      - api_failure
Denis_P's avatar
Denis_P committed
76
  tags:
77
    - linux-docker
Denis_P's avatar
Denis_P committed
78

79
80
81
82
83
84
85
86
87
88
.kubernetes-env:                   &kubernetes-env
  interruptible:                   true
  retry:
    max: 2
    when:
      - runner_system_failure
      - unknown_failure
      - api_failure
  tags:
    - kubernetes-parity-build
Denis_P's avatar
Denis_P committed
89

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#### Vault secrets
.vault-secrets:                    &vault-secrets
  secrets:
    CODECOV_P_TOKEN:
      vault:                       cicd/gitlab/$CI_PROJECT_PATH/CODECOV_P_TOKEN@kv
      file:                        false
    CODECOV_TOKEN:
      vault:                       cicd/gitlab/$CI_PROJECT_PATH/CODECOV_TOKEN@kv
      file:                        false
    GITHUB_EMAIL:
      vault:                       cicd/gitlab/$CI_PROJECT_PATH/GITHUB_EMAIL@kv
      file:                        false
    GITHUB_USER:
      vault:                       cicd/gitlab/$CI_PROJECT_PATH/GITHUB_USER@kv
      file:                        false
    GITHUB_TOKEN:
      vault:                       cicd/gitlab/$CI_PROJECT_PATH/GITHUB_TOKEN@kv
      file:                        false
    PIPELINE_TOKEN:
      vault:                       cicd/gitlab/$CI_PROJECT_PATH/PIPELINE_TOKEN@kv
      file:                        false

Denis_P's avatar
Denis_P committed
112
#### stage:                        check
Denis_P's avatar
Denis_P committed
113

Hero Bird's avatar
Hero Bird committed
114
check-std:
Denis_P's avatar
Denis_P committed
115
  stage:                           check
Denis_P's avatar
Denis_P committed
116
  <<:                              *docker-env
117
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
118
  script:
Hero Bird's avatar
Hero Bird committed
119
    - for crate in ${ALL_CRATES}; do
120
        cargo check --verbose --all-features --manifest-path crates/${crate}/Cargo.toml;
Hero Bird's avatar
Hero Bird committed
121
      done
Denis_P's avatar
Denis_P committed
122

Hero Bird's avatar
Hero Bird committed
123
check-wasm:
Denis_P's avatar
Denis_P committed
124
  stage:                           check
Denis_P's avatar
Denis_P committed
125
  <<:                              *docker-env
126
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
127
  script:
128
    - for crate in ${ALSO_WASM_CRATES}; do
129
        cargo check --verbose --no-default-features --target wasm32-unknown-unknown --manifest-path crates/${crate}/Cargo.toml;
Hero Bird's avatar
Hero Bird committed
130
      done
Denis_P's avatar
Denis_P committed
131

132

Denis_P's avatar
Denis_P committed
133
134
#### stage:                        workspace

Hero Bird's avatar
Hero Bird committed
135
build-std:
Denis_P's avatar
Denis_P committed
136
  stage:                           workspace
Denis_P's avatar
Denis_P committed
137
  <<:                              *docker-env
138
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
139
  needs:
140
141
    - job:                         check-std
      artifacts:                   false
Denis_P's avatar
Denis_P committed
142
  script:
Hero Bird's avatar
Hero Bird committed
143
    - for crate in ${ALL_CRATES}; do
144
        cargo build --verbose --all-features --release --manifest-path crates/${crate}/Cargo.toml;
Hero Bird's avatar
Hero Bird committed
145
      done
Denis_P's avatar
Denis_P committed
146

Hero Bird's avatar
Hero Bird committed
147
build-wasm:
Denis_P's avatar
Denis_P committed
148
  stage:                           workspace
Denis_P's avatar
Denis_P committed
149
  <<:                              *docker-env
150
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
151
  needs:
152
153
    - job:                         check-wasm
      artifacts:                   false
Denis_P's avatar
Denis_P committed
154
  script:
155
    - for crate in ${ALSO_WASM_CRATES}; do
156
        cargo build --verbose --no-default-features --release --target wasm32-unknown-unknown --manifest-path crates/${crate}/Cargo.toml;
Hero Bird's avatar
Hero Bird committed
157
      done
Denis_P's avatar
Denis_P committed
158

Denis_P's avatar
Denis_P committed
159
160
test:
  stage:                           workspace
Denis_P's avatar
Denis_P committed
161
  <<:                              *docker-env
162
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
163
  needs:
164
165
    - job:                         check-std
      artifacts:                   false
166
167
168
169
170
171
  variables:
      # Since we run the tests with `--all-features` this implies the feature
      # `ink-fuzz-tests` as well -- i.e. the fuzz tests are run.
      # There's no way to disable a single feature while enabling all features
      # at the same time, hence we use this workaround.
      QUICKCHECK_TESTS:            0
Denis_P's avatar
Denis_P committed
172
  script:
173
    - cargo test --verbose --all-features --no-fail-fast --workspace
174
    - cargo test --verbose --all-features --no-fail-fast --workspace --doc
175

176
177
178
179
180
181
      # Just needed as long as we have the `ink-experimental-engine` feature.
      # We do not invoke `--all-features` here -- this would imply the feature
      # `ink-experimental-engine`. So in order to still run the tests without the
      # experimental engine feature we need this command.
    - cargo test --verbose --features std --no-fail-fast --workspace

182
183
184
docs:
  stage:                           workspace
  <<:                              *docker-env
185
  <<:                              *test-refs
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
  variables:
    RUSTDOCFLAGS: -Dwarnings
  artifacts:
    name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc"
    when: on_success
    expire_in: 7 days
    paths:
      - ./crate-docs/
  script:
    - cargo doc --no-deps --all-features
        -p scale-info -p ink_metadata
        -p ink_env -p ink_storage -p ink_storage_derive
        -p ink_primitives -p ink_prelude
        -p ink_lang -p ink_lang_macro -p ink_lang_ir -p ink_lang_codegen
    - mv ${CARGO_TARGET_DIR}/doc ./crate-docs

202
spellcheck:
203
204
205
206
  stage:                           workspace
  <<:                              *docker-env
  <<:                              *test-refs
  script:
207
208
    - cargo spellcheck check -vvvv --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1 -- recursive .
    - cargo spellcheck check -vvvv --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1 -- recursive examples/
209

Hero Bird's avatar
Hero Bird committed
210
clippy-std:
Denis_P's avatar
Denis_P committed
211
  stage:                           workspace
Denis_P's avatar
Denis_P committed
212
  <<:                              *docker-env
213
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
214
  needs:
215
216
    - job:                         check-std
      artifacts:                   false
Denis_P's avatar
Denis_P committed
217
  script:
Hero Bird's avatar
Hero Bird committed
218
    - for crate in ${ALL_CRATES}; do
219
        cargo clippy --verbose --all-targets --all-features --manifest-path crates/${crate}/Cargo.toml -- -D warnings;
Hero Bird's avatar
Hero Bird committed
220
      done
Denis_P's avatar
Denis_P committed
221

Hero Bird's avatar
Hero Bird committed
222
clippy-wasm:
Denis_P's avatar
Denis_P committed
223
  stage:                           workspace
Denis_P's avatar
Denis_P committed
224
  <<:                              *docker-env
225
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
226
  needs:
227
228
    - job:                         check-wasm
      artifacts:                   false
Denis_P's avatar
Denis_P committed
229
  script:
230
    - for crate in ${ALSO_WASM_CRATES}; do
231
        cargo clippy --verbose --no-default-features --manifest-path crates/${crate}/Cargo.toml --target wasm32-unknown-unknown -- -D warnings;
Hero Bird's avatar
Hero Bird committed
232
233
234
      done

fmt:
Denis_P's avatar
Denis_P committed
235
  stage:                           workspace
Hero Bird's avatar
Hero Bird committed
236
  <<:                              *docker-env
237
  <<:                              *test-refs
Hero Bird's avatar
Hero Bird committed
238
  script:
Hero Bird's avatar
Hero Bird committed
239
    - cargo fmt --verbose --all -- --check
240
241
242
    # For the UI tests we need to disable the license check
    - cargo fmt --verbose --all -- --check --config=license_template_path="" crates/lang/macro/tests/ui/{pass,fail}/*.rs

Denis_P's avatar
Denis_P committed
243

Denis_P's avatar
Denis_P committed
244
#### stage:                        examples
Denis_P's avatar
Denis_P committed
245

Hero Bird's avatar
Hero Bird committed
246
examples-test:
Denis_P's avatar
Denis_P committed
247
  stage:                           examples
Denis_P's avatar
Denis_P committed
248
  <<:                              *docker-env
249
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
250
  needs:
251
252
    - job:                         clippy-std
      artifacts:                   false
Denis_P's avatar
Denis_P committed
253
  script:
254
    - for example in examples/*/; do
Denis_P's avatar
Denis_P committed
255
        cargo test --verbose --manifest-path ${example}/Cargo.toml;
Denis_P's avatar
Denis_P committed
256
      done
257
258
259
    - for contract in ${DELEGATOR_SUBCONTRACTS}; do
        cargo test --verbose --manifest-path examples/delegator/${contract}/Cargo.toml;
      done
Denis_P's avatar
Denis_P committed
260

261
262
263
examples-test-experimental-engine:
  stage:                           examples
  <<:                              *docker-env
264
  <<:                              *test-refs
265
266
267
268
269
270
271
  needs:
    - job:                         clippy-std
      artifacts:                   false
  script:
    # We test only the examples for which the tests have already been migrated to
    # use the experimental engine.
    - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/erc20/Cargo.toml
Hernando Castano's avatar
Hernando Castano committed
272
    - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/erc1155/Cargo.toml
273
274
275
    - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/contract-terminate/Cargo.toml
    - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/contract-transfer/Cargo.toml

Hero Bird's avatar
Hero Bird committed
276
examples-fmt:
Denis_P's avatar
Denis_P committed
277
  stage:                           examples
Denis_P's avatar
Denis_P committed
278
  <<:                              *docker-env
279
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
280
  script:
Michael Müller's avatar
Michael Müller committed
281
    # Note that we disable the license header check for the examples, since they are unlicensed.
282
    - for example in examples/*/; do
Michael Müller's avatar
Michael Müller committed
283
        cargo fmt --verbose --manifest-path ${example}/Cargo.toml -- --check --config=license_template_path="";
Denis_P's avatar
Denis_P committed
284
      done
285
    - for contract in ${DELEGATOR_SUBCONTRACTS}; do
Michael Müller's avatar
Michael Müller committed
286
        cargo fmt --verbose --manifest-path examples/delegator/${contract}/Cargo.toml -- --check --config=license_template_path="";
287
      done
Denis_P's avatar
Denis_P committed
288

Hero Bird's avatar
Hero Bird committed
289
examples-clippy-std:
Denis_P's avatar
Denis_P committed
290
  stage:                           examples
Denis_P's avatar
Denis_P committed
291
  <<:                              *docker-env
292
  <<:                              *test-refs
Denis_P's avatar
Denis_P committed
293
  needs:
294
295
    - job:                         clippy-std
      artifacts:                   false
Denis_P's avatar
Denis_P committed
296
  script:
297
    - for example in examples/*/; do
298
        cargo clippy --verbose --all-targets --manifest-path ${example}/Cargo.toml -- -D warnings;
Hero Bird's avatar
Hero Bird committed
299
      done
300
    - for contract in ${DELEGATOR_SUBCONTRACTS}; do
301
        cargo clippy --verbose --all-targets --manifest-path examples/delegator/${contract}/Cargo.toml -- -D warnings;
302
      done
Hero Bird's avatar
Hero Bird committed
303
304
305
306

examples-clippy-wasm:
  stage:                           examples
  <<:                              *docker-env
307
  <<:                              *test-refs
Hero Bird's avatar
Hero Bird committed
308
  script:
309
    - for example in examples/*/; do
310
        cargo clippy --verbose --manifest-path ${example}/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings;
Hero Bird's avatar
Hero Bird committed
311
      done
312
313
314
    - for contract in ${DELEGATOR_SUBCONTRACTS}; do
        cargo clippy --verbose --manifest-path examples/delegator/${contract}/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings;
      done
Hero Bird's avatar
Hero Bird committed
315
316
317
318

examples-contract-build:
  stage:                           examples
  <<:                              *docker-env
319
  <<:                              *test-refs
Hero Bird's avatar
Hero Bird committed
320
  script:
321
    - cargo contract -V
322
    - for example in examples/*/; do
Hero Bird's avatar
Hero Bird committed
323
324
325
326
        pushd $example &&
        cargo contract build &&
        popd;
      done
327
328
329
330
331
332
333
    - for contract in ${DELEGATOR_SUBCONTRACTS}; do
        pushd examples/delegator/$contract &&
        cargo contract build &&
        popd;
      done

examples-contract-build-delegator:
334
335
336
    stage:                         examples
    <<:                            *docker-env
    <<:                            *test-refs
337
338
339
    script:
        - cargo contract -V
        - cd examples/delegator/ && ./build-all.sh
Hero Bird's avatar
Hero Bird committed
340

341
examples-docs:
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
  stage:                           examples
  <<:                              *docker-env
  <<:                              *test-refs
  variables:
    RUSTDOCFLAGS: -Dwarnings
  script:
    # `--document-private-items` needs to be in here because currently our contract macro
    # puts the contract functions in a private module.
    # Once https://github.com/paritytech/ink/issues/336 has been implemented we can get rid
    # of this flag.
    - for example in examples/*/; do
        cargo doc --manifest-path ${example}/Cargo.toml --document-private-items --verbose --no-deps;
      done
    - for contract in ${DELEGATOR_SUBCONTRACTS}; do
        cargo doc --manifest-path examples/delegator/${contract}/Cargo.toml --document-private-items --verbose --no-deps;
      done
358

359
360
361
362
363

#### stage:                        ink-waterfall

ink-waterfall:
  stage:                           ink-waterfall
364
365
366
  image:                           paritytech/tools:latest
  <<:                              *kubernetes-env
  <<:                              *test-refs
367
  <<:                              *vault-secrets
368
  variables:
369
370
371
372
373
374
375
    TRGR_PROJECT:                  ${CI_PROJECT_NAME}
    TRGR_REF:                      ${CI_COMMIT_REF_NAME}
    # The `ink-waterfall` project id in GitLab
    DWNSTRM_ID:                    409
  script:
    - ./scripts/.ci/trigger_pipeline.sh
  allow_failure:                   true
376
377


Denis_P's avatar
Denis_P committed
378
379
380
381
382
#### stage:                        publish

publish-docs:
  stage:                           publish
  <<:                              *docker-env
383
  <<:                              *test-refs
384
  <<:                              *vault-secrets
385
  needs:
386
387
    - job:                         docs
      artifacts:                   true
Denis_P's avatar
Denis_P committed
388
  variables:
389
    GIT_DEPTH:                     100
390
391
392
393
394
  rules:
    - if: $CI_PIPELINE_SOURCE == "web"
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_COMMIT_REF_NAME == "master"
    - if: $CI_COMMIT_REF_NAME == "tags"
395
  # need to overwrite `before_script` from `*docker-env` here,
396
  # this branch does not have a `./scripts/.ci/pre_cache.sh`
397
  before_script:
398
    - *rust-info-script
399
    - unset CARGO_TARGET_DIR
Denis_P's avatar
Denis_P committed
400
  script:
Denis_P's avatar
Denis_P committed
401
    - rm -rf /tmp/*
Denis_P's avatar
Denis_P committed
402
    # Set git config
Denis_P's avatar
Denis_P committed
403
    - rm -rf .git/config
Denis_P's avatar
Denis_P committed
404
405
    - git config user.email "devops-team@parity.io"
    - git config user.name "${GITHUB_USER}"
Denis_P's avatar
Denis_P committed
406
    - git config remote.origin.url "https://${GITHUB_TOKEN}@github.com/paritytech/ink.git"
Denis_P's avatar
Denis_P committed
407
408
    - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
    - git fetch origin gh-pages
Denis_P's avatar
Denis_P committed
409
    # saving README and docs
410
    - cp -r ./crate-docs/ /tmp/doc/
Denis_P's avatar
Denis_P committed
411
    - cp README.md /tmp/doc/
412
    - cp -r .images/ /tmp/doc/
Denis_P's avatar
Denis_P committed
413
    - git checkout gh-pages
Denis_P's avatar
Denis_P committed
414
415
    - mv _config.yml /tmp/doc/
    # remove everything and restore generated docs, README and Jekyll config
Denis_P's avatar
Denis_P committed
416
    - rm -rf ./*
417
    - rm -rf ./.images
Denis_P's avatar
Denis_P committed
418
    - mv /tmp/doc/* .
419
    - mv /tmp/doc/.images .
Denis_P's avatar
Denis_P committed
420
    # Upload files
Denis_P's avatar
Denis_P committed
421
    - git add --all --force
Denis_P's avatar
Denis_P committed
422
    - git status
Michael Müller's avatar
Michael Müller committed
423
424
425
426
427
428
    # `git commit` has an exit code of > 0 if there is nothing to commit.
    # This unfortunately causes GitLab to exit immediately and mark this
    # job as failed subsequently.
    # We don't want to mark the entire job as failed if there's nothing to
    # publish though, hence the `|| true`.
    - git commit -m "Updated docs for ${CI_COMMIT_REF_NAME} and pushed to gh-pages" || true
Denis_P's avatar
Denis_P committed
429
    - git push origin gh-pages --force
Denis_P's avatar
Denis_P committed
430
431
  after_script:
    - rm -rf .git/ ./*
432
433
434
435

#### stage:                        fuzz

fuzz-tests:
436
437
438
  stage:                           fuzz
  <<:                              *docker-env
  <<:                              *test-refs
439
  <<:                              *vault-secrets
440
441
  variables:
    # The QUICKCHECK_TESTS default is 100
442
    QUICKCHECK_TESTS:              5000
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_COMMIT_REF_NAME == "master"
  script:
    # We fuzz-test only crates which possess the `ink-fuzz-tests` feature
    - all_tests_passed=0
    - for crate in ${ALL_CRATES}; do
        if grep "ink-fuzz-tests =" crates/${crate}/Cargo.toml;
        then
          cargo test --verbose --features ink-fuzz-tests --manifest-path crates/${crate}/Cargo.toml --no-fail-fast -- fuzz_ || exit_code=$?;
          all_tests_passed=$(( all_tests_passed | exit_code ));
        fi
      done
    - if [ $all_tests_passed -eq 0 ]; then exit 0; fi
    - |
      curl -X "POST" "https://api.github.com/repos/paritytech/ink/issues" \
        -H "Cookie: logged_in=no" \
        -H "Authorization: token ${GITHUB_TOKEN}" \
        -H "Content-Type: application/json; charset=utf-8" \
        -d $'{
            "title": "[ci] Failing fuzz tests on master ('"$( date +"%d %b %Y" )"')",
            "body": "The CI job ['"${CI_JOB_ID}"']('"${CI_JOB_URL}"') just failed.\n\nThe offending commit is ['"${CI_COMMIT_TITLE}"'](https://github.com/paritytech/ink/commit/'"${CI_COMMIT_SHA}"').",
            "assignees": [],
            "labels": [
                "P-high"
            ]
        }'
    - exit ${all_tests_passed}